Manchmal werden bei Codeberg geloeschte oder anderweitig verschwundene Issues
vom periodischen Sync nicht bereinigt (er upsertet nur, ruehrt fehlende Issues
nie an). Neuer Knopf neben der "Kanban-Board"-Ueberschrift stoesst dafuer einen
synchronen Voll-Resync an (POST /api/v1/tickets/resync, X-Admin-Key): jedes
konfigurierte Projekt wird neu gelesen, Tickets zu inzwischen verschwundenen
Issues werden je Projekt entfernt (auch wenn sie manuell auf eine nicht vom
Sync verwaltete Spalte wie "fertig" bewegt wurden), Tickets zu weiterhin
existierenden Issues bleiben unangetastet.
Der PO geht dabei sichtbar ans Board und steht dort, bis die Anfrage
abgeschlossen ist - anders als beim automatischen Board-Besuch (feste
Animationsdauer je Stage) macht das die tatsaechliche Sync-Dauer sichtbar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Der Agent (fds2610/Agent#9) setzt "help wanted" auf Issues, waehrend er auf
die Erfuellung einer externen Voraussetzung wartet. Solche Issues sollen im
Monitor-Board in der Spalte "QA" auftauchen statt im Backlog zu verschwinden.
"qa" wird dafuer wie "backlog"/"bereit"/"in_arbeit"/"testing"/"done" zur
sync-verwalteten Spalte; nur "fertig" bleibt weiterhin fuer ein zukuenftiges
Label reserviert und rein manuell.
Bislang hat RepoConfigController::delete() nur die Projektkonfiguration
geloescht - die Tickets des Projekts blieben (nur ohne Projektfarbe,
Issue #34) dauerhaft im Kanban sichtbar. Jetzt loescht der Endpunkt
zusaetzlich alle Tickets, deren repo-Feld auf das entfernte Projekt
zeigt.
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>
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>
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 Codeberg-Sync liest nun Issues aus mehreren konfigurierbaren Projekten
(Config::codebergRepos(), Default: fds2610/monitor gelb, fds2610/Steuerkram
gruen, fds2610/nc-gantt blau) statt nur aus einem einzigen Repo. Damit
external_ref (Issue-Nummer) projektuebergreifend nicht kollidiert, ist es
jetzt zusammen mit dem neuen Ticket-Feld "repo" eindeutig; Migration
002_multi_repo_tickets.sql ergaenzt die Spalte und den Index. Da Migrationen
dadurch nicht mehr rein additiv sind, trackt Database::migrate() jetzt
angewandte Dateien in schema_migrations, damit ALTER TABLE nicht bei jedem
Prozessstart erneut laeuft. Das Frontend baut Codeberg-Links jetzt aus dem
repo-Feld jedes Tickets statt aus einer global fixen owner/repo-Angabe.
Schliesst #26
Auf der Produktionsmaschine traten vereinzelt Timeouts nach codeberg.org auf
("Operation timed out after 15003 milliseconds with 0 bytes received"), waehrend
die Verbindung sonst in ~0,1s antwortet.
Bisher fiel der Dauerlaeufer nach so einem Fehlschlag in den regulaeren Takt
zurueck. Weil SyncTrigger::consume() die Webhook-Anforderung schon vor dem Lauf
quittiert, war eine per Webhook angeforderte Aktualisierung damit verloren und
kam erst bis zu MONITOR_SYNC_INTERVAL Sekunden spaeter - genau die Verzoegerung,
die der Webhook abschaffen sollte.
Neu wird der naechste Versuch nach einem Fehlschlag vorgezogen: 5s, 10s, 20s, …
gedeckelt auf das regulaere Intervall (Sync\SyncSchedule). Ein kurzer Aussetzer
ist damit binnen Sekunden ueberstanden, eine laengere Codeberg-Stoerung laeuft
trotzdem nicht in einen Dauerbeschuss. Nach einem erfolgreichen Lauf gilt sofort
wieder das regulaere Intervall.
Die Wartezeit-Berechnung liegt in einer eigenen Klasse, weil die Schleife in
bin/sync-codeberg.php selbst nicht testbar ist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Aenderungen an Codeberg-Issues landeten bisher erst beim naechsten Lauf des
sync-Services auf dem Board (MONITOR_SYNC_INTERVAL, Default 600s). Neu meldet
Codeberg jede Aenderung aktiv per Webhook, der Weg aufs Board dauert damit
wenige Sekunden (Webhook -> Sync -> SSE-Push ans Frontend).
POST /api/v1/webhooks/codeberg wertet den Payload bewusst nicht aus, sondern
fordert nur einen regulaeren Sync an - CodebergSyncService bleibt die einzige
Stelle, die Issues auf Tickets abbildet, und ein verpasster Webhook ist
folgenlos, weil der periodische Lauf denselben Stand ohnehin nachholt.
Der Sync passiert nicht im Request: Forgejo bricht die Zustellung nach wenigen
Sekunden ab (DELIVER_TIMEOUT, Default 5s), ein Sync macht aber mehrere Requests
nach codeberg.org. Der Endpunkt setzt daher nur eine Trigger-Datei im
gemeinsamen ./api-Volume (Sync\SyncTrigger) und antwortet sofort mit 202; der
sync-Service laeuft jetzt im Watch-Modus und greift sie im Sekundentakt auf.
Authentifiziert wird per HMAC-SHA256 ueber den rohen Request-Body
(X-Forgejo-Signature bzw. X-Gitea-Signature) - Codeberg kennt unsere API-Keys
nicht. Ohne gesetztes CODEBERG_WEBHOOK_SECRET weist der Endpunkt grundsaetzlich
mit 401 ab, damit ein vergessenes Secret keinen offenen Sync-Ausloeser im
Internet hinterlaesst. Request traegt dafuer neu den unveraenderten rawBody.
Anders als der sync-Service braucht der api-Service damit eine eingehende
Freigabe in Proxy und Firewall (POST /api/v1/webhooks/codeberg).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Seit dem Praesenz-Timeout aus Ticket #17 verschwindet ein Agent aus der
Buero-Visualisierung, sobald last_seen_at aelter als zehn Minuten ist. Damit sich das auf
der Produktionsmaschine nachvollziehen laesst, dokumentiert GET /api/v1/agents jetzt, dass
der Endpunkt unauthentifiziert ist (last_seen_at also per curl abrufbar ist) und wie sich
derselbe Wert notfalls direkt aus der SQLite-Datei auslesen laesst.
Schliesst #20
Bisher landeten alle offenen Issues unabhaengig von ihren Labels in
Backlog. Jetzt bestimmt das Label die Spalte: wip -> in_arbeit,
testing -> testing, automate -> bereit, sonst backlog. Geschlossene
Issues bleiben in Done. Die "manuell verschoben"-Sperre im
TicketRepository schuetzt jetzt nur noch qa/fertig (zukuenftige
Labels) statt aller Nicht-Backlog/Done-Spalten, damit der Sync
Tickets weiterhin zwischen den automatisierten Spalten verschieben
kann, wenn sich Labels aendern.
Schliesst #14
Auf der Produktion blieb das Kanban-Board leer, obwohl die API lief und der
registrierte Agent angezeigt wurde: Tickets entstehen nur aus Agenten-Meldungen
oder aus bin/sync-codeberg.php, und das Skript war zwar als "per Cron oder
manuell" dokumentiert, wurde aber nirgends automatisch ausgefuehrt.
Neuer Compose-Service "sync": nutzt Image, Volume und SQLite-Datei des
api-Service, macht sofort beim Start einen Lauf und wiederholt ihn alle
MONITOR_SYNC_INTERVAL Sekunden (Default 600). Ein fehlgeschlagener Lauf bricht
die Schleife nicht ab, damit ein Codeberg-Ausfall keinen Restart-Loop ausloest.
Der Service verbindet sich ausschliesslich ausgehend nach codeberg.org und
braucht daher kein Port-Mapping - in Produktion sind weder Proxy- noch
Firewall-Freigaben noetig.
update.sh startet den Dienst mit, die Doku beschreibt den Service statt des
Cron-Hinweises; der manuelle Aufruf bleibt als Debug-Weg dokumentiert, weil dort
die Klartext-Fehler des Codeberg-Clients sichtbar werden.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
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 1 aus Visualization.md um: Vanilla-PHP-REST-API mit SQLite-Persistenz,
statischen API-Keys pro Agent, Codeberg-Sync fuer Backlog/Done, PHPUnit-Tests und
vollstaendiger API.md-Dokumentation - Vorgaben aus Detailantworten.md (F1-F16)
umgesetzt. Entwicklung/Build/Test laufen komplett in Docker-Compose-Containern
(PHP 8.2 + SQLite + Composer + PHPUnit/pcov), analog zum NC-Gantt-Projekt ist auf
dem Host kein PHP/Composer noetig.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>