Lange Meldungen liefen ueber den Monitorrahmen hinaus in die Szene. Jedes
Event belegt jetzt zwei Zeilen (Zeit + System, darunter eingerueckt die
Meldung), beide auf die Innenbreite gekuerzt. Die vollstaendige Meldung
zeigt ein <title>-Tooltip beim Hover ueber das Event.
Die Kuerzung selbst sitzt als truncateToWidth() in office-scene.js und ist
damit ohne DOM testbar; die Zeichenbreite ist an der breitesten der in
Frage kommenden Monospace-Schriften ausgerichtet, damit auf keinem System
etwas ueberlaeuft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Laeuft einem Agenten die Claude-Anmeldung aus, laeuft das Cron-Skript um ihn
herum unbeeindruckt weiter: es meldet "working", ruft claude auf, bekommt nichts
Brauchbares zurueck und meldet am Ende "idle". last_seen_at ist frisch, der
Agent sieht beschaeftigt aus - waehrend Lauf fuer Lauf nichts entsteht. Der
Ausfall fiel bisher erst auf, wenn jemand merkte, dass keine PRs mehr kommen.
Agenten melden den Fall jetzt ueber zwei neue state-Werte (auth_expiring,
auth_expired) am bestehenden Activity-Endpunkt. state ist serverseitig nicht
eingeschraenkt, daher ohne Migration und ohne Aenderung an der API.
Dargestellt in drei Stufen der Aufmerksamkeit:
- Schloss ueber dem Avatar (bei auth_expired pulsierend), entsaettigter Koerper,
eingefaerbter Bildschirm und Namensschild am Platz
- Header-Banner mit den betroffenen Agenten, gelb bei Vorwarnung, rot bei
Ausfall - auch aus einigen Metern Entfernung noch lesbar
- KPI-Kachel "Problem: n", die sich nur einfaerbt, wenn n > 0
Kern ist isAgentVisible(), bewusst getrennt von isAgentPresent(): ein
blockierter Agent bleibt sichtbar, obwohl er sich nicht mehr meldet, zaehlt aber
nicht als "Online" mit. Ohne das verschwaende er nach dem Praesenz-Timeout genau
dann vom Schreibtisch, wenn seine Warnung dort stehen soll.
MAX_KPI_TILES von 4 auf 5, sonst verdraengen Agenten/Online/Problem/Hooks die
gemeldeten KPIs (F7) vollstaendig.
Nebenbei: "npm test" lief seit Node 24 nicht mehr (das Verzeichnis-Argument wird
nicht mehr als Testverzeichnis aufgeloest), jetzt per Glob.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
renderKpiMonitor() hat die Hoehe des KPI-Monitors gleichmaessig unter den Kacheln
aufgeteilt (rowH = (frame.h - 30) / tiles.length, rund 78px pro Zeile bei vier
Kacheln). Der Platz ging dabei in die Sparkline-Kaesten - und genau die
Zaehler-Kacheln "Agenten"/"Online" (Issue #39) und "Hooks" (Issue #38) haben
history: [] und damit gar keine Linie. Zwischen den Werten standen deshalb grosse
leere Kaesten.
Die Zeilen stapeln sich jetzt mit fester Hoehe von oben, und einen Sparkline-Kasten
bekommt nur noch, wer wirklich einen Verlauf hat: eine Zaehler-Kachel braucht damit
24 statt 77,5 Pixel, vier davon zusammen 96 statt 310. Der Kasten ist ausserdem 32px
hoch - die Hoehe, fuer die buildKpiSparklinePoints() die Punkte berechnet; vorher war
er rund 51px hoch und die Linie fuellte ihn nie aus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die im Issue beschriebene Event-Anzeige (neuestes Event oben, aeltere
schieben nach, Ueberschuss wird abgeschnitten) ist bereits seit Phase 4
umgesetzt (buildEventLines()/MAX_EVENT_LINES, F5) und braucht keine
Aenderung.
Neu ist die vierte, immer sichtbare KPI-Kachel ganz rechts im
KPI-Monitor: "Hooks: n" zaehlt die seit dem letzten Start des
api-Containers empfangenen Webhooks. Der Zaehler ist dateibasiert
(Metrics\HookCounter), weil der eingebaute PHP-Server jede Anfrage als
eigene Skriptausfuehrung behandelt; docker/entrypoint.sh setzt die Datei
beim Start des api-Containers (nicht des sync-Containers) zurueck. Neuer
oeffentlicher Endpunkt GET /api/v1/webhooks/stats liefert den Stand,
das Frontend haengt ihn als eigene Kachel an (buildHooksKpi()) und
reserviert ihr dafuer eine feste Kachel statt sie der MAX_KPI_TILES-
Kappung der uebrigen KPIs zu unterwerfen. Der Wert fliesst zusaetzlich
in den SSE-Fingerabdruck (ChangeFeed) ein.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Die KPI-Kacheln "Agenten" (registrierte Agenten) und "Online" (davon
in den letzten 10 Minuten aktiv, siehe AgentRepository::isPresent())
wurden auf der Monitor-Wand nie angezeigt. Statt eine eigene
Meldepflicht dafuer einzufuehren, leitet buildAgentCountKpis() beide
Werte direkt aus der ohnehin geladenen Agentenliste ab und stellt sie
den ueber /api/v1/kpis gemeldeten Kennzahlen voran.
Schliesst #39
Das Projekte-Modal am PO laesst seit #27 je Projekt eine Farbe pflegen, und
GET /api/v1/tickets liefert sie pro Ticket mit (ticket.color) - auf dem Board
kam sie aber nie an: renderBoard() bekam nur Zaehler je Spalte, jeder Zettel
war derselbe gelbe Standardzettel.
buildBoardColumnLayout() bekommt deshalb zusaetzlich die gruppierten Tickets
und liefert die Farbe je Zettel mit. Ohne Ticketliste (Demo-Modus, der nur
Zaehler kennt) bleibt alles wie bisher.
Ein Stapel steht fuer mehr Tickets als er Zettel hat und zeigt die ersten
verschiedenen Farben darin - so ist auch an "Done" (immer ein Stapel) zu
sehen, aus welchen Projekten die Tickets stammen. Reichen die Farben nicht
fuer alle drei Zettel, werden sie zyklisch wiederholt: ein ungefaerbter
Rest-Zettel saehe sonst wie ein weiteres Projekt aus, denn --color-note ist
von einem gelben Projekt kaum zu unterscheiden.
Gefaerbte Zettel bekommen ausserdem eine neutrale statt der gelben Kontur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Der Konfig-Dialog liess sich in der Praxis kaum oeffnen (Fehlerbericht in
Issue #27). Drei Ursachen, alle reproduziert:
- Ein Klick auf den PO selbst tat gar nichts: der Klick-Handler hing nur am
Schreibtisch, die Figur liegt aber in der Avatar-Schicht darueber. Genau sie
meint das Ticket ("bei Klick auf den PO"). Der Avatar ist jetzt klickbar,
auch waehrend er am Board steht.
- Klicks auf den Platz trafen nur, wenn sie zufaellig auf einem gefuellten
Moebelstueck landeten - eine <g> faengt in ihren Luecken keine Klicks. Der
Platz hat jetzt eine eigene Trefferflaeche, wie die Board-Spalten.
- Dass hinter dem PO ueberhaupt etwas steckt, war nicht zu sehen. Ein kleines
Zahnrad auf seinem Tisch (mit Tooltip) zeigt es an.
Ausserdem oeffnet der Dialog jetzt auch bei nicht erreichbarer API - er meldet
dann selbst, dass er die Projekte nicht laden konnte, statt tot zu wirken.
Und genau dieser Fall trat leicht ein: der eingebaute PHP-Server beantwortet
Anfragen nacheinander, eine offene SSE-Verbindung blockiert damit alles andere
(gemessen 16s fuer ein GET /api/v1/tickets, ab dem zweiten offenen Monitor
dauerhaft). Das Frontend faellt dann auf Demo-Daten zurueck. Mit
PHP_CLI_SERVER_WORKERS=4 sind es 0,006s; PRAGMA busy_timeout deckt die
Schreibzugriffe ab, die sich jetzt begegnen koennen.
ApplicationTest::testFullFlowCreateUpdateAndDeleteRepoConfigWithAdminKey war
seit dem Seed in 003_repo_configs.sql rot (legte fds2610/monitor ein zweites
Mal an) - der Test nutzt jetzt ein nicht geseedetes Repo.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Klick auf den PO oeffnet ein Modal, in dem sich die Projekte (Owner/Repo +
Farbe), aus denen der Codeberg-Sync liest, hinzufuegen, bearbeiten und
loeschen lassen - Icon-Schaltflaechen mit Tooltip statt Textbuttons. Die
Projektliste war bisher eine Umgebungsvariable (CODEBERG_REPOS, Issue #26)
und wandert dafuer in eine neue Tabelle repo_configs (Migration
003_repo_configs.sql, geseedet mit den bisherigen drei Default-Projekten);
CodebergSyncService liest sie ueber RepoConfigRepository bei jedem Lauf
frisch ein, damit eine Aenderung im laufenden --watch-Prozess ohne Neustart
wirkt. Neue Endpunkte GET/POST/PATCH/DELETE /api/v1/repo-configs, Schreiben
hinter X-Admin-Key wie die Agenten-Registrierung; das Frontend fragt den Key
bei der ersten Aenderung per Prompt ab und merkt ihn sich im lokalen
Speicher. office-modal.js bekommt dafuer einen Oeffnen-Token
(isCurrentModal()), damit ein verspaetet aufloesender Request nach dem
Oeffnen eines anderen Modals nicht dessen Inhalt ueberschreibt.
Schliesst #27
Der Client verglich last_seen_at bislang selbst gegen Date.now() - je nach
Client-Zeitzone/-Uhr konnte das falsche Ergebnisse liefern und liess Agenten
faelschlich als abwesend erscheinen (Issue #20, Folge von #17). Die API
liefert die Anwesenheit jetzt direkt als is_present-Feld mit, berechnet
serverseitig via gmdate()/time() (AgentRepository::isPresent()). Das Frontend
liest das Feld nur noch aus, ohne eigene Zeit-Arithmetik.
Der Product Owner ("PO") ist keine ueber die API registrierte Rolle, sondern
repraesentiert die Produktverantwortung des Systems selbst: eigener, fester
Platz ausserhalb des dynamischen 8er-Schreibtisch-Rasters, anwesend genau
dann, wenn die API erreichbar ist (statt ueber last_seen_at). Ausserdem laeuft
er zum Board, sobald sich dort ein Ticket-Status aendert, der noch keiner
anderen Rolle zugeordnet ist (z.B. Wechsel nach "bereit" durchs Label
"automate", wie es diesem Issue selbst passiert ist) - Wechsel nach
fertig/done bleiben weiterhin Sache der Rolle "user" (Tuerbesuche).
Schliesst #19
Ein registrierter Agent gilt im Bueromonitor nur noch als anwesend,
solange last_seen_at hoechstens zehn Minuten zurueckliegt (isAgentPresent,
office-scene.js). Meldet er sich laenger nicht, bleibt sein Schreibtisch
frei und er erscheint auch nicht mehr im Ueberlauf-Ruheplatz vor der Tuer,
statt weiterhin faelschlich als anwesend dargestellt zu werden.
Schliesst #17
Der fett gedruckte Titel "Buero" ueber der Visualisierung heisst jetzt
"Agent-Status", der laengere erklaerende Fliesstext darunter entfaellt.
Stattdessen zeigt der Header rechtsbuendig einen rein kosmetischen
Sekunden-Countdown, der von MONITOR_SYNC_INTERVAL herunterzaehlt und nach
Erreichen von 0 wieder von vorn beginnt (Issue #12). Der Wert wird dazu wie
MONITOR_API_BASE per config.js/envsubst ans Frontend durchgereicht.
Die Basis-URL der API wurde bisher aus dem Port der Seite abgeleitet
(office.html: `if (location.port === "8090")`). Laeuft das Frontend hinter
anderen Ports - in Produktion 4443 (web) / 4444 (api) - griff die Bedingung
nicht, der relative Default /api/v1 lief gegen den Webserver ins Leere, und
die Szene fiel still auf Demo-Daten zurueck: fremde Agenten-Namen und
erfundene Ticketzahlen bei gleichzeitig leeren Detail-Modals.
Die URL kommt jetzt aus MONITOR_API_BASE (.env, Default passend zum
Port-Mapping in docker-compose.yml). Der Webserver liefert den Wert zur
Laufzeit als /config.js aus; das nginx-Image setzt ihn beim Start per
envsubst in die Config ein (default.conf -> default.conf.template). Damit
bleibt html/ ein rein statisches, read-only gemountetes Verzeichnis - kein
Build-Schritt, kein eigenes Entrypoint-Skript. Ein leerer Wert bedeutet
"gleicher Origin" und faellt auf /api/v1 zurueck.
Zusaetzlich macht ein roter Banner "API nicht erreichbar!" den Demo-Fallback
sichtbar, statt ihn wie bisher stillschweigend als echte Daten auszugeben.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 6 (F2): detectDoorVisits() erkennt die drei Anlaesse aus den Detailantworten
(neues per Codeberg-Sync angelegtes Backlog-Ticket, Ticket erreicht "fertig", Ticket
erreicht "done") durch Diff zweier Ticket-Snapshots. office-render.js laesst dafuer
einen "user"-Besucher durch die linke Tuer hereinkommen, zum Board laufen, kurz
interagieren und wieder hinausgehen - unabhaengig vom Daten-Refresh-Takt, damit ein
Besuch auch bei einem zwischenzeitlichen Refresh sauber zu Ende animiert.
Phase 7 (F16): html/mon.html, script.js und styles.css sind entfallen ("kann
entfallen" laut Detailantworten); check_fds.sh/mail_fds.py/status.txt speisen
weiterhin unveraendert den System-Monitor. Unit-Tests fuer die neue Tuer-Logik
ergaenzt (weiterhin 100% Line-Coverage, node --test). E2E-Tests bleiben bewusst
ausserhalb dieser Phase.
Ausserdem: update.sh (holt die neueste Version vom konfigurierten Git-Remote und
faehrt die Docker-Compose-Entwicklungsumgebung hoch) und register-agent.sh
(Hilfsskript zur Agenten-Registrierung per API) ergaenzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Setzt Visualization.md Abschnitt 9, Phasen 3-5 vollstaendig um:
Phase 3 (Kanban-Board live): Board zeigt echte Ticket-Daten aus GET /api/v1/tickets
(office-api.js), Klick auf eine Spalte oeffnet ein Modal mit Titel/Beschreibung/Typ und
einem Link auf die Codeberg-Quelle bei per Sync angelegten Tickets (F4).
Phase 4 (Monitor-Wand live): Event-, System- und KPI-Monitor zeigen echte Daten,
Klick-Modals fuer alle drei. Neuer SSE-Endpoint GET /api/v1/stream (F13) meldet per
ChangeFeed-Fingerabdruck, welche Ressourcen sich geaendert haben; office-realtime.js nutzt
EventSource mit Polling-Fallback. KPI-Monitor zeichnet eine eigene SVG-Sparkline (F10, kein
Chart-Lib) sobald KPIs gemeldet wurden, sonst weiterhin der Phase-2-Platzhalter (F7 bleibt
inhaltlich offen). API-Antworten tragen jetzt CORS-Header, da Frontend (:8090) und API
(:8091) im Docker-Compose-Setup unterschiedliche Origins sind.
Phase 5 (Agenten & Animation): resolveAgentVisualState bildet gemeldete Agenten-Zustaende
auf Ort/Pose ab; Agenten-Avatare sind eine per CSS transition animierte, von Board/
Schreibtisch-Moebeln entkoppelte Schicht, die zwischen Schreibtisch, Board, Monitor-Wand und
einer neuen Kaffee-Ecke laufen. Idle-Varianten (Zeitung/Kaffee/Schwaetzchen) werden rein
kosmetisch client-seitig zufaellig ausgeloest (F8), eine AnimationQueue serialisiert
Update-Animationen bei vielen gleichzeitigen Ereignissen (F9).
29 Node-Tests (100% Line-Coverage office-scene.js) und neue PHPUnit-Tests fuer
Response::stream(), ChangeFeed, StreamController und die neuen Repository::fingerprint()-
Methoden. PHP/Docker-Ausfuehrung konnte in dieser Umgebung nicht verifiziert werden (kein
PHP verfuegbar) - vor dem Merge bitte `docker compose run --rm api composer test` pruefen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Setzt Phase 2 aus Visualization.md um: Vanilla-SVG-Buero-Szene (Waende, 2 Tueren,
8 Schreibtische, Kanban-Board-Rahmen mit 7 Spalten, Monitor-Wand mit Event-/System-/
KPI-Monitor) ohne Animation, gespeist mit statischen Demo-Daten im Format der
Phase-1-API-Responses. Board-Stapel-Logik (>5 Tickets, Done-Spalte als Dauerstapel)
folgt Detailantworten.md F2/F3.
Reine Layout-Logik (html/js/office-scene.js) ist ausgelagert und mit dem Node-
Testrunner abgedeckt (100% Zeilenabdeckung, tests/frontend/), das DOM-Rendering
(office-render.js) bleibt bewusst duenne Glue-Logik. Docker-Compose um web- (nginx,
serviert html/) und frontend-Service (node, "npm test") erweitert, analog zum
bestehenden api-Service und zum NC-Gantt-Vorbild: kein PHP/Node auf dem Host noetig.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>