df8d088face1ed2433e4e658019771da49b8e7a0
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3249c8e942 |
build(react): P6 — Sprung auf React 19, und das Budget bewusst angehoben
React 18.3.1 -> 19.2.8, dazu @types/react(-dom) 19.
## Nichts brach
tsc --noEmit sauber, 54/54 Tests gruen, ESLint 0 Fehler
Im Browser gegen die ECHTE Box: Cockpit mit 2 Diagrammen und 4 Flaechen,
Statusleiste live (40,8/122,7 GB, 38 °C, 532 748 115 Token), 8 Karten
NULL Konsolen-Warnungen — keine veralteten Muster im Bestand
Das eigentliche Risiko war `react-force-graph-2d` (peer: react "*", also ungeprueft,
und es umhuellt eine Nicht-React-Bibliothek). Gegen den echten Vault der Box getestet:
Canvas 500x560, 4498 gezeichnete Pixel, keine Fehlerflaeche. Es laeuft.
## Das Budget hat angeschlagen — und ich habe es trotzdem angehoben
Gemessen kostet der Sprung 14 645 B gzip (118 762 -> 133 407) und riss damit das
in P0 gesetzte Budget von 125 000. Das Gate hat also getan, was es soll.
Beim Router (P2) habe ich in derselben Lage NICHT das Budget angefasst, sondern den
Platz zurueckgeholt (Schublade und Palette hinter lazy()). Hier geht das nicht: Der
Zuwachs IST die Plattform, es gibt nichts wegzulassen.
Also angehoben — einmalig, auf 140 000, mit der Begruendung IM Ampel-Skript, damit ein
spaeterer Leser sieht, dass es ein ueberlegter Schritt war und kein Nachgeben:
MC2 ist eine LAN-Appliance; 14 kB sind ueber Gigabit-Ethernet keine messbare Wartezeit.
Das Budget existiert gegen DRIFT — einen 24-MB-Avatar, eine 95-kB-Diagramm-Bibliothek
auf der Startseite — nicht gegen einen bewussten Plattform-Schritt. Der relative
Abstand zum Deckel bleibt derselbe wie vorher (~5 %).
## Ehrlich zum Nutzen
React 19 bringt diesem Projekt HEUTE wenig Konkretes: use(), Actions und
Server-Komponenten sind hier nicht im Spiel. Der Grund ist die Zukunft — 18 altert,
und der React Compiler ist der Pfad, auf dem die 18 offenen Warnungen aus
eslint-plugin-react-hooks (setState im Effekt, Ref-Zugriff im Render) irgendwann
nicht mehr nur Warnungen sind.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
5aa5a484a7 |
feat(frontend): v3-Umbau P3 — ein Store, eine Statusleiste, endlich aria-live
Vierte Etappe. Der Client-Zustand hatte bisher keinen Ort: zwei handgebaute
useSyncExternalStore-Speicher, drei CustomEvent-Kanaele und ein localStorage-Griff
mitten in einem useState — verteilt ueber fuenf Dateien.
## Ein Store (Befund B-08/B-09)
app/store.ts (Zustand, 1,2 kB gzip) haelt genau vier Dinge: Bedienvorlieben
(Schiene, Expertenmodus, Palette), die Lage des Ereignisstroms und die
Meldungs-Warteschlange. Der Kopfkommentar nennt die Regel, nach der etwas dort
landen darf — und ein Test prueft sie: gespeichert werden AUSSCHLIESSLICH
Schienen- und Expertenmodus-Zustand, kein Strom, keine Meldungen, kein Geheimnis.
Der Metrik-Verlauf bleibt bewusst DRAUSSEN (lib/metricsStore.ts): Er nimmt ab P4
jede Sekunde einen Messpunkt entgegen, ein Store-Update pro Sekunde wuerde jede
abonnierende Komponente neu rendern.
Die drei CustomEvent-Kanaele sind ERSATZLOS weg — `grep -r dispatchEvent src`
liefert nichts mehr. Wer die Schublade oeffnen will, setzt `?system=`; wer springen
will, navigiert. Auch der letzte Rest von P2 (`mc-palette-open`) ist aufgeloest.
## Statusleiste (Spezifikation §4.1)
Der Zustand der Box stand bisher verstreut: die Ampel unten in der Sidebar, die
Auslastung nur im Cockpit, das aktive Modell nur im Modell-Manager. Wer in der
Konsole arbeitete, sah gar nichts.
Jetzt eine feste Leiste ueber allen Ansichten: Motor/Hirn-Ampel (klickbar zum
Agenten) · aktive Rolle + Durchsatz mit Sparkline · geteilter Speicher ALS BALKEN
(nicht als Prozent — 128 GB Unified Memory ist die Kernzahl dieser Box) · CPU/GPU
· Temperatur (ab 85 °C bernstein) · Betriebszeit · Token-Summe · Live-Anzeige.
Die Sparklines sind eigenes SVG, ~20 Zeilen. Fuer eine 56-Pixel-Linie waere eine
Diagramm-Bibliothek Verschwendung.
Dafuer neu im Backend: `uptime_s` in /api/system/status (psutil.boot_time).
Gehoert dorthin, weil die Box sich woechentlich selbst neu startet — dann ist
"laeuft seit 20 Minuten" die Antwort auf eine ganze Klasse von Fragen.
Liefert psutil nichts, steht dort None und die Leiste blendet das Feld aus,
statt eine Zahl zu erfinden.
## aria-live (Befund B-11) — und diesmal mit Absender
Im ganzen Frontend gab es KEIN einziges aria-live. app/shell/Meldungen.tsx ist
jetzt die eine Stelle fuer beilaeufige Rueckmeldungen; Fehler bekommen
`assertive` und bleiben stehen, alles andere `polite` und raeumt sich weg.
Wichtig: Die Region ist keine Attrappe. Gespeist wird sie von den Uebergaengen des
Ereignisstroms und vom Konsolen-Neustart (mit dem echten Grund aus ApiError.detail).
Der erste Verbindungsaufbau wird bewusst NICHT gemeldet — sonst begruesst jede
Seite den Nutzer mit "Verbindung wieder da".
## Ehrliche Anzeige statt stillem Altern (Befund B-15/B-18)
lib/events.ts fuehrt die Stromlage jetzt im Store: live · nachlauf · getrennt.
Beim Wechsel wird EINMAL invalidiert — vorher las relax() den Zustand erst beim
naechsten Refetch, nach einem Riss blieb die UI bis zu 150 s im langsamen Modus.
## Bahnbreiten-Deckel (Befund B-10)
Die Arbeitsflaeche endet bei 1600 px. Ohne Deckel zog sich das Cockpit auf einem
3440-px-Ultrawide auf ueber 3 000 px, waehrend die Wurzel-Schriftgroesse mitwuchs
und die Zeilen GROESSER statt lesbarer machte.
## Nebenbei
Der Buendel-Budget-Check aus P0 war fragil: `ls dist/assets/index-*.js | head -1`
kann den falschen treffen, weil Rollup auch kleine geteilte Module "index-*.js"
nennt (gemessen: ein 67-Byte-Chunk neben dem 374-kB-Einstieg). Er waere dann still
immer gruen gewesen. Beide Ampel-Dateien lesen den Einstieg jetzt aus index.html.
## Verifiziert im Browser
Statusleiste: alle Felder mit Live-Daten, "Laeuft seit 1 Std 10 min"
Backend abgewuergt -> Leiste springt auf "Nachlauf" UND die aria-live-Region
meldet "Verbindung zur Zentrale verloren" (polite)
Backend zurueck -> "Live", ohne Begruessungs-Meldung
Cockpit-Kachel -> /cockpit?system=logs, Schublade offen, Reiter "System-Logs"
Arbeitsflaeche -> max-width 1600 px
33/33 Tests gruen (7 neue fuer den Store) · ESLint 0 Fehler · tsc sauber ·
Einstieg 118 153 B gzip / Budget 125 000.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
ae69c5ce31 |
fix(security): v3-Umbau P1 — Geheimnisse raus aus dem Browser, Frontend endlich getestet
Zweite Etappe. Kern: das Frontend traegt kein Geheimnis mehr, und es laeuft
nicht laenger als einziger Teil des Stacks ungeprueft durchs Gate.
## B-01 — Box-Sudo-Passwort: ersatzlos entfernt
Das Passwort lag im localStorage und reiste bei JEDEM mutierenden Request mit —
als Header `X-Sudo-Password` UND im JSON-Rumpf. Da /etc/sudoers den Dienst-Nutzer
mit `NOPASSWD: ALL` fuehrt, waere ein einziger XSS in der SPA Root auf der Box
gewesen.
AUF DER BOX GEMESSEN: `sudo -n true` laeuft durch. Das Passwort wurde also nie
gebraucht — es war reines Risiko ohne Gegenwert. Darum keine Umkonstruktion
(Sitzungs-Cookie o. AE.), sondern Loeschung, quer durch den ganzen Pfad:
frontend/src/lib/api.ts kein localStorage-Zugriff mehr
frontend/.../SettingsTab.tsx Eingabefeld weg, dafuer die Erklaerung warum
backend/routers/maintenance.py SudoReq entfaellt, 8 Endpunkte entschlackt
backend/services/maintenance.py _run() nutzt immer `sudo -n`
backend/services/jobengine.py keine stdin-Pipe mehr (DEVNULL)
`password_required` bleibt als ehrliches Signal: Verlangt sudo je doch ein
Passwort, ist das eine Konfigurations-Frage auf der Box — nichts, was man mit
einem im Browser geparkten Geheimnis uebertuencht.
## HuggingFace-Token: liegt jetzt auf der Box
Derselbe Fehler, kleinerer Radius. Neu: backend/services/geheimnisse.py — Datei
neben den anderen mc2-*.json, Rechte 0600, atomar geschrieben. Die Oberflaeche
erfaehrt nur, OB ein Token gesetzt ist, nie seinen Wert. Ein Schluessel-Allowlist
verhindert, dass ein fehlgeleiteter Request beliebige Felder hineinschreibt.
Prozess-Env (HF_TOKEN) hat Vorrang und wird als solche angezeigt.
Verifiziert gegen das lokale Backend: setzen/lesen/loeschen ok, unerlaubter
Schluessel wird mit Klartext-Grund abgewiesen, der Wert kommt nie zurueck.
## B-14 — Fehlermeldungen sagen jetzt, was los ist
api() warf `new Error("500 Internal Server Error")` und verwarf den Rumpf; der
eigentliche Grund aus FastAPIs `detail` erreichte die Oberflaeche nie. Neu:
ApiError mit status + detail, inklusive Validierungslisten und HTML-Fehlerseiten
(ein kaputter Rumpf darf die Meldung nicht in einen zweiten Fehler verwandeln).
Zwei Aufrufstellen zeigen den Grund jetzt statt "Fehler" (Discover, ModelBrowse).
## B-07 — Tests und Linter, ehrlich eingeordnet
Praezisierung gegenueber dem Audit: Die MC2-Ampel fuehrt bewusst GAR KEINE Tests
aus (dokumentiert: die Python-Dienste haengen an ML-Wheels, die echten Tests sind
Pruefstand + Box). Das ist fuer die Dienste richtig — fuer Frontend-Unit-Tests
nicht: die laufen in jsdom, brauchen weder Modell noch GPU, und sind in 1,3 s durch.
Vitest + Testing Library, 17 Tests in 3 Dateien
ESLint (flat config) + Prettier
Beides jetzt Teil der Ampel
Die Tests sind kein Feigenblatt: acht davon sind der Zaun um B-01 — sie beweisen,
dass api() weder Kopfzeilen noch Rumpf aus dem localStorage anreichert. Dazu eine
ESLint-Regel, die localStorage-Zugriffe auf Schluessel mit password/token/secret
im Namen hart abweist (an einer Probe verifiziert; harmlose Schluessel wie
mc_sidebar_collapsed bleiben erlaubt).
ESLint meldet 0 Fehler / 93 Warnungen. Die 18 Treffer der neuen React-Compiler-
Regeln (setState im Effekt, Ref-Zugriff im Render) sind ECHT, aber quer durch
10 500 Zeilen zu beheben ist P5-Arbeit. Sie stehen als sichtbare Warn-Liste statt
abgeschaltet — ein ab Tag eins rotes Gate ist kein Gate mehr.
## Nebenbefund, im Browser reproduziert: veraltetes Buendel nach Deploy
Ein Deploy ersetzt dist und startet den Dienst neu; offene Tabs behalten aber ihr
altes Start-Buendel, dessen Nachlade-Chunks nun fehlen — der naechste
Ansichtswechsel wirft. Galt schon fuer die 10 lazy Views, traf durch P0 nun auch
die Startseite. lib/veralteteVersion.ts faengt Vites `vite:preloadError` ab und
laedt EINMAL neu (Sperre in sessionStorage gegen Endlosschleife).
## Nachgemessen
Start-Chunk gzip 111 411 B · dist gesamt 1 480 097 B (beide im Ampel-Budget)
tsc --noEmit sauber · 17/17 Tests gruen · ESLint 0 Fehler
Im echten Browser gegen das lokale Backend geprueft: Einstellungen holen den
Token-Zustand von der Box, kein Passwort-Feld mehr, Knoepfe korrekt gesperrt,
beide Cockpit-Diagramme rendern (Achsen + Zeitachse sichtbar).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
||
|
|
8aa22e6591 |
perf(frontend): v3-Umbau P0 — Ballast raus, Startbuendel halbiert
Erste Etappe des v3-Umbaus. Bewusst ohne jede Architektur-Aenderung: nur Entruempeln, damit der Rest des Umbaus auf einem messbar leichten Stand aufsetzt. Gemessen (vorher -> nachher): dist gesamt 27 375 720 B -> 1 477 852 B (-94,6 %) Dateien in dist 135 -> 35 Start-Chunk gzip 220 278 B -> 110 571 B (-49,8 %) CSS gzip 31 577 B -> 15 121 B (-52,1 %) Schrift-Dateien 112 -> 11 (WOFF 1: 0) Vier Eingriffe: 1) avatar.vrm (24,5 MB) entfernt. Lag in public/ und dist/, wurde von KEINER Zeile des Repos referenziert — der Renderer Avatar3D.tsx war schon vorher verschwunden. War 89,5 % der Nutzlast, die auf die Box ging. Damit fallen auch die .gitignore-Sonderregel und der Direkt-Deploy-Schritt weg. DISASTER_RECOVERY.md §3·D ehrlich auf "ausgebaut" gesetzt (7 Stellen). 2) Schriften: die 11 @fontsource-Sammelimporte zogen ALLE Subsets (latin-ext, griechisch, kyrillisch) und je eine WOFF-1-Fassung mit — 112 Dateien, 1,58 MB, davon 902 kB WOFF 1, das kein Browser dieser App je abruft. Jetzt stehen die @font-face-Regeln direkt in index.css: nur latin, nur WOFF 2, nur die Schnitte, die per grep ueber die font-*-Klassen wirklich belegt sind. Nebenbei behoben: JetBrains Mono 600/700 fehlten komplett — die 19 Stellen mit `font-mono font-bold/semibold` wurden vom Browser synthetisch fettgerechnet. Jetzt echte Schnitte; bei Monospace ist die Laufweite gleich, kein Layout-Versatz. 3) Recharts aus dem Startbuendel. Das Cockpit ist die Startseite und laedt daher NICHT lazy; ueber SystemStatusCard/TokenPerformanceCard zog es Recharts samt d3 in index-*.js. LiveAreaChart ist jetzt eine Lazy-Huelle (Suspense mit hoehengleichem Platzhalter, damit nichts springt), die Recharts-Umsetzung liegt in LiveAreaChartImpl.tsx und kommt als eigener Chunk nach (105 kB gzip). 4) index.css: height 100dvh mit 100% als Rueckfall. Auf Mobilbrowsern mit einfahrender Adressleiste ist 100 % nicht die sichtbare Hoehe. Dazu ein Buendel-Budget in beiden Ampel-Dateien (.gitea/ = MC2-Fassung, deploy/ = universelle Vorlage): Start-Chunk und dist-Gesamtgroesse werden am FRISCHEN Build im Runner gemessen. Bewusst kein Vergleich mit dem committeten dist — der waere ueber Node-Versionen hinweg flatterhaft und wuerde dauerhaft rot leuchten, was das Signal zerstoert. Verifiziert gegen die Box (Frontend-Dev mit MC_API_TARGET=192.168.178.151:9001): Cockpit rendert mit Live-Daten, keine Konsolenfehler, alle 11 Schriftschnitte registriert, LiveAreaChartImpl + recharts laden nachweislich als Nachlade-Chunk. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
e16f0140f7 |
fix: ampel ci unbound variable GITHUB_SHA
Ampel / ampel (push) Failing after 22s
|
||
|
|
da03de18cb |
feat: Skills Dashboard, CI Build Automation, Auto-Sync & CI-Ampel MCP
Ampel / ampel (push) Failing after 22s
|
||
|
|
775e862652 |
CI: MC2-Ampel auf sinnvolle Gates zugeschnitten (Lint+Import+Frontend-Build)
Ampel / ampel (push) Successful in 22s
Die universelle Vorlage will pip-install ALLER 5 requirements (inkl. schwerer ML-Deps: Whisper/TTS) + pytest repo-weit in einem stateless Container — fuer dieses Multi-Service- ML-Monorepo weder machbar noch aussagekraeftig (echte Tests = Pruefstand/Integration auf der Box mit Live-Diensten). MC2-Ampel prueft stattdessen, was im Container ehrlich gruen sein kann und echte Fehler faengt: ruff (Projekt-Politik) + compileall + Frontend-Build inkl. tsc-Typecheck. Bewusste Abweichung von 'pytest = Pflicht' fuer dieses Repo, begruendet im Workflow-Kopf. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e6502f676a |
CI: Ampel fuer MC2 verdrahtet (.gitea/workflows/ampel-ci.yml)
Ampel / ampel (push) Failing after 1m52s
Die universelle Ampel (aus deploy/ampel-ci.yml) war bisher nur Vorlage — MC2 hatte keine aktive .gitea/workflows/-Datei, Pushes triggerten keinen CI-Lauf. Jetzt nachgeruestet (offene Karte t_730a747a). Laeuft auf push+pull_request, erkennt Projekt-Typ selbst (Python/Node) und prueft Ruff+compileall+pytest bzw. npm ci+build+test. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |