command hq: web frontend v1 — nav, grid, detail

Builds the Workspace/Panel screen the design doc's "Layout, v1" described but
nothing had implemented yet: /command-hq and /command-hq/:sessionId, three
always-mounted panels (officerdev/src/apps/CommandHQ/) — a flat category nav
filtering the grid via ?category=, a commander grid reading GET /commanders,
and a detail pane that is either an empty state, the new-commander composer, or
the selected commander's resumed transcript.

Follows the frontend route conventions in CLAUDE.md throughout: Workspace/Panel
framework (locked, three fixed panels, no standalone screen), category and
session both addressable in the URL rather than a channel, real <Link>s for
every row and card, a route pair (/command-hq + /command-hq/:sessionId) the
same shape as /chat's.

The composer reuses the exact mechanism /chat already uses to create a
commander — an ordinary turn with context: 'command-hq' via useChat/
EmbeddableChat — rather than the mobile-only POST /commanders/new HTTP route.
Once the harness reports a real transcript id the URL is replaced onto it
(useChat's own permalink writes /chat/<id>, which is the wrong screen here, so
that's done by hand). fileAsCommander always files a new commander
uncategorised — it has no way to know a category was selected in the composer
— so if one was, a client-side PATCH sets it after the fact, the same race
POST /commanders/new already resolves server-side for mobile, just resolved
here on the client instead.

The detail panel's transcript resume mirrors ChatPanelWrapper's fetch-then-
mount pattern (GET /chat/sessions/:id first, then useChat with
resumeSessionId + initialMessages + paginate) rather than reusing it directly
— Command HQ's version is simpler (no dashboard/agent-panel concerns) and
scopes rename/category/disarm through Command HQ's own PATCH route rather
than /chat's, since a commander lives in a different directory than an
ordinary chat session.

Card status is the three-state read COMMS/command-hq/mobile-4.md proposed and
the design record adopted: green running, amber armed-but-not-running (the
watcherArmed && !running pair that makes a silently-dead watcher visible),
grey neither.

Registered in AppRegistry.tsx, re-exported from officerdev's barrel (route
helpers + types, per CLAUDE.md's rule for anything an officer-web screen
needs), a CORE_DOCK_ITEMS tile (not in DEFAULT_DOCK_PATHS — opt-in via the
Dock settings screen, same as Jobs/Monitor/Activity), and a usePageTitle rule.

bunx tsgo clean, suite at its 7 pre-existing baseline failures, none of them
this feature's. NOT yet clicked through against a running server — the
composer's redirect-on-real-id, the disarm button, the category-race PATCH
have all typechecked and nothing more. That's the next real test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 09:41:13 +00:00
co-authored by Claude Opus 5
parent 7b7d8bbf36
commit a9be3a92c9
18 changed files with 796 additions and 10 deletions
+26 -10
View File
@@ -5,14 +5,27 @@ it, kept current as answers landed.
**Status:** data model, the full REST API including both creation paths (web and `POST
/commanders/new`), built and mounted 2026-08-18 (`bunx tsgo` clean, suite at its 7 pre-existing baseline
failures — none of them this feature's). **Needs `bun db:push`** before any route answers with real data;
without it every route throws a Postgres "relation does not exist" 500. A member also needs
`confineUserTree` re-run once if their account predates tonight — see "A member could not reach it" under
"Working directory". **Frontend not built yet on the web side.** The mobile side is further along —
`apps/commandhq` exists and typechecks against this router (see `COMMS/command-hq/mobile.md`) — and as of
this revision has what it asked for to create a commander, though it has not yet been exercised against a
running server on either side. Every claim in this file is checked against the code, not aspirational —
see "API reference" for what a client can call today.
failures — none of them this feature's). `bun db:push` and a restart of `officer` have both since run
against the dev machine, so the backend is live. A member still needs `confineUserTree` re-run once if
their account predates that — see "A member could not reach it" under "Working directory".
**Web frontend v1 built 2026-08-18**, same session: `/command-hq` and `/command-hq/:sessionId`, three
panels per "Layout, v1" below (`command-hq-categories`, `command-hq-grid`, `command-hq-detail`) under
`officerdev/src/apps/CommandHQ/`, registered in `AppRegistry.tsx`, a `CORE_DOCK_ITEMS` tile, and a
`usePageTitle` rule. **Not yet exercised against a running server** — typechecks and the suite is at
baseline, but nobody has clicked through it live. The composer creates a commander the same way `/chat`
always has (an ordinary turn with `context: 'command-hq'`); once the harness reports a transcript id the
URL is replaced onto it, and if a category was selected when composing, a `PATCH` sets it client-side —
`fileAsCommander` itself always files uncategorised, the same race `POST /commanders/new` resolves
server-side for mobile, resolved here the client-side equivalent. Rename, category and disarm all go
through Command HQ's own `PATCH` route, never `/chat`'s — the two are scoped to different directories. Tag
UI is still deliberately absent, per "Tags — infrastructure now, UI not specified" below.
The mobile side is further along on live exercise — `apps/commandhq` exists and typechecks against this
router (see `COMMS/command-hq/mobile.md`) — and as of this revision has what it asked for to create a
commander, though it has not yet been exercised against a running server on either side either. Every claim
in this file is checked against the code, not aspirational — see "API reference" for what a client can call
today.
## API reference — build against this
@@ -527,5 +540,8 @@ scope for tonight."_
## Still open
Nothing blocking as of this revision. `POST /command-hq/commanders/new` is built — see "How a commander is
created" — with `prompt` required, confirmed with the owner 2026-08-18. Neither path (web or mobile) has
been exercised against a running server yet; that is the next real test, not a design question.
created" — with `prompt` required, confirmed with the owner 2026-08-18. The web frontend v1 is built too
(see the status line at the top). **Neither client has actually been clicked through against a running
server yet** — that is the next real test, not a design question, and it is the thing most likely to turn
up something this file gets wrong: the composer's redirect-on-real-id, the watcher-disarm button, the
category-race PATCH, none of them have run once outside a typecheck.