FE-830: Align with design params of main branch#187
Conversation
PR SummaryLow Risk Overview Root workspace UX now loads Process/docs: Reviewed by Cursor Bugbot for commit f098d77. Bugbot is set up for automated code reviews on this repo. Configure here. |
1374003 to
90e3ca6
Compare
473ab2e to
c8276d8
Compare
90e3ca6 to
8feecdc
Compare
c8276d8 to
987861d
Compare
| return queryOptions({ | ||
| queryKey: queryKeys.workspace.selectionState(), | ||
| queryFn: () => rpcClient.request<WorkspaceSelectionState>('workspace.selectionState'), | ||
| }); |
There was a problem hiding this comment.
Spec list never refreshes
Medium Severity
The root route now loads and renders specs from workspace.selectionState, but brunch.updated handling still only invalidates workspace.state and graph/session topics—not workspace.selectionState. After the initial load, the Specs nav can stay stale while workspace chrome refetches on live updates.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8feecdc. Configure here.
| <main className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-4 px-5 py-8 sm:px-8 lg:px-10"> | ||
| <p className="text-hint font-mono text-xs">Brunch workspace</p> | ||
| <WorkspaceChrome state={state} /> | ||
| <SpecList specs={selection.specs} /> |
There was a problem hiding this comment.
Spec list cache not invalidated
Medium Severity
The root route now renders the specs nav from a cached workspace.selectionState query, but brunch.updated handling still invalidates only workspace.state. After workspace activation or other inventory changes, chrome can refresh while the specs list and links keep stale data until a full reload.
Reviewed by Cursor Bugbot for commit 8feecdc. Configure here.
There was a problem hiding this comment.
Pull request overview
Ports the prior-trunk web design system into src/web (tokens + card primitives) and re-skins the existing read-only workspace/spec views to use the restrained Inter/Geist + neutral-ramp styling, replacing the prior “warm brunch” aesthetic. Also adds a root-route Specs list powered by the workspace.selectionState RPC so spec navigation is discoverable from /.
Changes:
- Ported new design tokens (typography scale, neutral ramp, shadow tokens) and added Inter/Geist Mono font packages.
- Introduced ported UI primitives (
DrawerCard, node badges/ref chips) and updated GraphOverview/WorkspaceChrome/SessionPanel styling accordingly. - Added
workspace.selectionStatequery key + query options and rendered a navigable Specs list on the root route; updated web tests to match the new UI behavior.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| src/web/styles.css | Replaces theme tokens + base styles; imports Inter/Geist Mono fontsource packages. |
| src/web/routes/spec.tsx | Restyles spec route pages and invalid-spec banner to new token palette/shadows. |
| src/web/routes/root.tsx | Adds workspace.selectionState prefetch + query usage; renders Specs nav list; restyles chrome/session panels. |
| src/web/query-keys.ts | Adds workspace.selectionState query key. |
| src/web/queries/workspace.ts | Adds workspaceSelectionStateQueryOptions and exported WorkspaceSelectionState type. |
| src/web/features/graph/GraphOverview.tsx | Rebuilds graph overview presentation with new badges/chips; removes Focus-node placeholder UI. |
| src/web/components/node-card.tsx | Adds node presentation primitives (plane accent map + badges + ref code helper). |
| src/web/components/node-card.test.tsx | Adds unit tests for node-card primitives (labels, ref codes, plane accent coverage). |
| src/web/components/drawer-card.tsx | Ports DrawerCard component (collapsible card-with-drawer). |
| src/web/app.test.tsx | Updates tests for removed Focus-node placeholder; adds coverage for Specs link list; updates expected RPC calls. |
| package.json | Adds @fontsource-variable/inter and @fontsource-variable/geist-mono. |
| package-lock.json | Locks new fontsource deps (and includes incidental lockfile changes). |
| memory/SPEC.md | Records new web design-system decision (D72-L) and invariant (I43-L). |
| memory/PLAN.md | Marks web-design-system-port as done and documents its acceptance/verification. |
| memory/cards/web-design-system-port--restyle.md | Adds execution record card for the web design-system port. |
| .agents/skills/ln-scope/SKILL.md | Updates scoping guidance to emphasize cold-start reads. |
| .agents/skills/ln-build/SKILL.md | Updates ln-build extraction guidance to include cold-start reads. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const canToggle = !!children && !locked; | ||
| const [expanded, setExpanded] = useState(defaultExpanded); | ||
|
|
||
| const showDrawer = expanded || !!summary; | ||
| const drawerContent = expanded && children ? children : summary; |
| <button | ||
| type="button" | ||
| onClick={() => setExpanded((prev) => !prev)} | ||
| className={`border-rule -m-px w-[calc(100%+2px)] cursor-pointer overflow-hidden rounded-xl border bg-white ${headerPadding} text-left shadow-[var(--shadow-card)]`} | ||
| > |
| // Ported / adapted from the prior trunk's knowledge-card.tsx. The old UI | ||
| // keyed accents per KnowledgeKind; this trunk groups accents by the node's | ||
| // conceptual plane (intent / oracle / design / plan) — D67-L. Reference-code | ||
| // labels remain canonical: NODE_KIND_METADATA + kindOrdinal (D62-L). |
| // Accent per plane — exhaustive over NodePlane (I42-L). Adding a plane without | ||
| // an accent is a compile error via `satisfies`. |
| function WorkspaceStatePage() { | ||
| const { rpcClient } = indexRoute.useRouteContext(); | ||
| const { data: state } = useSuspenseQuery(workspaceStateQueryOptions(rpcClient)); | ||
| const { data: selection } = useSuspenseQuery(workspaceSelectionStateQueryOptions(rpcClient)); | ||
|
|
||
| return ( | ||
| <main className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-6 px-5 py-8 sm:px-8 lg:px-10"> | ||
| <p className="text-brunch-muted font-mono text-xs tracking-[0.35em] uppercase">Brunch workspace</p> | ||
| <main className="mx-auto flex min-h-screen w-full max-w-6xl flex-col gap-4 px-5 py-8 sm:px-8 lg:px-10"> | ||
| <p className="text-hint font-mono text-xs">Brunch workspace</p> | ||
| <WorkspaceChrome state={state} /> | ||
| <SpecList specs={selection.specs} /> |
| /** Read-only workspace inventory: the spec/session list shown on the root route. */ | ||
| export type WorkspaceSelectionState = WorkspaceLaunchInventory & { | ||
| status: string; | ||
| requiresSelection: boolean; | ||
| }; |
|
|
||
| Single sentence: what this work changes for the user, operator, or codebase. | ||
|
|
||
| ### Cold-start reads |
Replace the agent-invented "warm brunch" web aesthetic with the prior trunk's restrained design language (D67-L): - styles.css: ported token system (Inter + Geist Mono, ink/sub/hint/rule/ wash/tint ramp, compact 11-16px type scale, --shadow-card family) - components/: ported DrawerCard + node primitives (KindBadge, CountBadge, RefBadge) with plane-organized accent map exhaustive over NodePlane (I42-L); reference codes stay canonical via NODE_KIND_METADATA - restyle WorkspaceChrome, GraphOverviewPanel, SessionPanel, spec route; remove non-functional "Focus node" placeholder, keep Edge categories - root route: navigable Specs list via workspace.selectionState (read-only) rendering TanStack Router Links to /spec/$specId 29 web tests green; oxlint type-aware + build clean; no native module in bundle. Amp-Thread-ID: https://ampcode.com/threads/T-019eabad-db95-764e-8351-f87a87e69882 Co-authored-by: Amp <amp@ampcode.com>
8feecdc to
f098d77
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f098d77. Configure here.
| NODE_KIND_METADATA, | ||
| type NodeKind, | ||
| type NodePlane, | ||
| } from '../../graph/schema/nodes.js'; |
There was a problem hiding this comment.
Web import pulls Drizzle
Medium Severity
node-card.tsx value-imports formatGraphNodeCode and NODE_KIND_METADATA from graph/schema/nodes.js, which value-imports enum arrays from db/schema.js and evaluates Drizzle sqliteTable setup. That pulls persistence code into the browser bundle, contradicting the standalone drizzle-free web/ build target.
Reviewed by Cursor Bugbot for commit f098d77. Configure here.
| import { useState } from 'react'; | ||
|
|
||
| // ── Drawer card — reusable card-with-collapsible-drawer ───────────── | ||
| // | ||
| // Ported from the prior trunk (../brunch/src/client/components/drawer-card.tsx). |
| const headerEl = canToggle ? ( | ||
| <button | ||
| type="button" | ||
| onClick={() => setExpanded((prev) => !prev)} | ||
| className={`border-rule -m-px w-[calc(100%+2px)] cursor-pointer overflow-hidden rounded-xl border bg-white ${headerPadding} text-left shadow-[var(--shadow-card)]`} | ||
| > | ||
| {header} |
| function CountStat({ label, value }: { label: string; value: number }) { | ||
| return ( | ||
| <div className="text-right"> | ||
| <dd className="text-ink text-base font-medium">{value}</dd> | ||
| <dt className="text-xxs text-hint font-mono">{label}</dt> | ||
| </div> |
| <dl | ||
| aria-label="Workspace chrome" | ||
| className="border-brunch-rule/80 bg-brunch-card/85 grid gap-3 rounded-[2rem] border p-5 shadow-[0_24px_80px_rgb(73_50_24_/_0.12)] backdrop-blur sm:grid-cols-2 lg:grid-cols-5" | ||
| className="border-rule overflow-hidden rounded-xl border bg-white p-4 shadow-[var(--shadow-card)]" | ||
| > | ||
| <div className="rounded-2xl bg-white/45 p-4 lg:col-span-2"> | ||
| <dt className="text-brunch-muted font-mono text-[0.68rem] tracking-[0.22em] uppercase">cwd</dt> | ||
| <dd className="text-brunch-ink mt-2 font-mono text-sm break-all">{state.cwd}</dd> | ||
| </div> | ||
| <div className="rounded-2xl bg-white/45 p-4"> | ||
| <dt className="text-brunch-muted font-mono text-[0.68rem] tracking-[0.22em] uppercase">spec</dt> | ||
| <dd className="text-brunch-ink mt-2 text-lg leading-tight font-semibold">{specLabel}</dd> | ||
| </div> | ||
| <div className="rounded-2xl bg-white/45 p-4"> | ||
| <dt className="text-brunch-muted font-mono text-[0.68rem] tracking-[0.22em] uppercase">session</dt> | ||
| <dd className="text-brunch-ink mt-2 font-mono text-sm break-all"> | ||
| {state.session?.id ?? 'No session selected'} | ||
| </dd> | ||
| </div> | ||
| <div className="rounded-2xl bg-white/45 p-4"> | ||
| <dt className="text-brunch-muted font-mono text-[0.68rem] tracking-[0.22em] uppercase">phase</dt> | ||
| <dd className="text-brunch-ink mt-2 text-sm font-medium">{state.chrome.phase}</dd> | ||
| <div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-4"> | ||
| <div className="lg:col-span-2"> |
| @@ -10,3 +11,16 @@ export function workspaceStateQueryOptions(rpcClient: WebSocketRpcClient) { | |||
| queryFn: () => rpcClient.request<WorkspaceState>('workspace.state'), | |||
| }); | |||
| } | |||
|
|
|||
| /** Read-only workspace inventory: the spec/session list shown on the root route. */ | |||
| export type WorkspaceSelectionState = WorkspaceLaunchInventory & { | |||
| status: string; | |||
| requiresSelection: boolean; | |||
| }; | |||
| import { | ||
| formatGraphNodeCode, | ||
| NODE_KIND_METADATA, | ||
| type NodeKind, | ||
| type NodePlane, | ||
| } from '../../graph/schema/nodes.js'; |
| - **D19-L — Keep product RPC/read architecture thin: named method families over projection handlers.** Brunch exposes concrete named methods, not vague feature buckets or generic records. The canonical public RPC vocabulary is maintained in [`src/rpc/README.md`](file:///Users/lunelson/Code/hashintel/brunch-next/src/rpc/README.md): `rpc.discover`; `workspace.state`, `workspace.selectionState`, `workspace.activate`; current selected-spec graph reads `graph.overview` and `graph.nodeNeighborhood`; and session methods `session.triggerExchange`, `session.pendingExchange`, `session.submitExchangeResponse`, `session.submitMessage`, `session.exchanges`, and `session.runtimeState`. Reserved future target names such as `graph.changesSince` and graph-adjacent `graph.coherenceSummary` must not appear in discovery until real behavior exists. Each read handler projects from the canonical store that owns the fact (Pi JSONL, `.brunch/workspace.json`, or SQLite graph/change log), and each mutation handler routes to the Brunch-owned command/session layer. `brunch.updated` notifications are first-class JSON-RPC records over WebSocket and stdio, but they are process-local invalidation hints carrying `{topic, specId?, sessionId?, nodeId?, lsn?}`, not canonical truth and not a durable cross-store event spine. Brunch must not create a generic read-gateway platform, REST read model, DB-backed chat/turn projection, or canonical cross-store event spine merely to keep clients in sync. Dev-only RPC harness methods may exist under `dev.*` when explicitly enabled for local fixture curation or seam testing; they are absent from normal product discovery, absent from read-only sidecars, and still route mutations through the owning command/session layer. They are not public product capabilities and must not occupy `graph.*`, `session.*`, or `workspace.*` names. Retired public names are quarantined in `src/rpc/README.md` §Names absent from current public RPC and must not re-enter product discovery. Depends on: D5-L, D6-L, D10-L, D16-L. Supersedes: the heavier “unified read gateway” mental model, vague `elicitation.*` / `command.*` public families, and any discovery/dispatch split where a surface describes methods it rejects. | ||
| - **D23-L — Transport modes, operational modes, agent roles, strategies, and lenses are separate axes.** TUI, RPC, print, and web are transport modes: ways of driving or observing the same Brunch host through Pi/Brunch harness seams. Operational modes are top-level authority/tooling postures such as `elicit` and future `execute`. Agent roles are active workers within an operational mode (`elicitor`, `reviewer`, `reconciler`, future `executor/orchestrator`, `scout`, `researcher`, and any deferred observer/auditor). Strategies are interaction plans; lenses are narrower interpretive/extraction/review framings. M1 print mode is therefore only a transport proof-of-life: it boots through the same host/coordinator, renders current product-shaped state, and exits without running an agent turn. A future single-turn headless print run is deferred until runtime bundle selection/defaults are explicit. Depends on: D1-L, D5-L, D19-L, D21-L, D40-L. Supersedes: overloading “mode” to mean both transport and agent strategy, or using “agent mode” for role/preset/lens interchangeably. | ||
| - **D33-L — Transport connections are client attachments, not Brunch sessions.** A Brunch session is a durable linear Pi JSONL transcript bound to exactly one spec; WebSocket connections, stdio streams, TUI instances, and browser tabs are ephemeral presentation attachments to product resources. Session-specific RPC methods should name their target spec/session explicitly or operate through an explicit client attachment; they must not infer durable session identity merely from the transport connection. `.brunch/workspace.json` remains launch/default acceleration, not concurrency authority. During the POC, Brunch targets a one-writer/many-observer local model: one interactive driver (typically TUI/agent) may write while web clients attach read-only for visual projections. Depends on: D5-L, D10-L, D11-L, D19-L, D21-L, D24-L. Supersedes: treating `/rpc`, a WebSocket, or workspace default state as the active session itself. | ||
| - **D72-L — The web client's visual design system is ported from the prior trunk (`../brunch/src/client`), not freshly invented.** The browser surface adopts the earlier product's restrained design language: Inter (sans) + Geist Mono (mono) fonts; the neutral gray ramp (`ink #202020`, `sub #5b5b5b`, `hint #a6a6a6`, `rule #e3e3e3`, `wash #f0f0f0`, `tint #fafafa`) plus link/per-kind accents; a compact 11–16px type scale; and the subtle `--shadow-card` family. Card primitives are ported too — `DrawerCard` (collapsible card-with-drawer), `KindBadge`, `CountBadge`, and the kind-grouped knowledge-card pattern (white header card over `tint` body via `-m-px` overlap). Because the two trunks are independent checkouts with no shared package, the tokens and primitives are **copied** into `src/web/` (new `src/web/components/`), adapted from the old `KnowledgeKind` vocabulary to this trunk's `NodeKind` model: node reference codes reuse `NODE_KIND_METADATA` labels + `kindOrdinal` (D62-L) — which already match the old `G1`/`CTX1`/`AC1` codes — and the per-kind accent is organized **by plane** (`intent`/`oracle`/`design`/`plan`). The graph overview groups `GraphSlice.nodes` by kind and renders `edges` as inline "Links to:" reference badges. Scope is read-only presentation only: no data, RPC, query, subscription, or routing changes, and no new layout surfaces — only re-skinning the three existing views (`WorkspaceChrome`, `GraphOverviewPanel`, `SessionPanel`). Full first-trunk layout fidelity (phase-navigation rail, center acceptance-criteria column, three-pane spec workspace) is explicitly out of scope. Depends on: D10-L, D52-L, D62-L. Supersedes: the agent-invented "warm brunch" web aesthetic — paper/card warm palette, body radial/linear gradients, `backdrop-blur`, oversized radii (`rounded-[2rem]`) and shadows, translucent surfaces (`bg-white/45`), wide-tracked uppercase mono labels, hover-lift "Focus node" cards, and the invented "Edge categories" chip cluster. |
| - **Why now / unlocks:** The current web UI's visual language was invented wholesale by the agent that built it and does not match the product's established look. Realigning now keeps the read-only observer surface presentable for manual/observer testing and stops the invented aesthetic from being copied forward into future web views. Independent of the delivery spine — touches no data, RPC, query, subscription, or routing code. | ||
| - **Acceptance:** | ||
| - `src/web/styles.css` carries the ported token system; no warm-palette tokens, body gradients, or `backdrop-blur` remain. | ||
| - `src/web/components/` holds the ported primitives; the accent map is exhaustive over `NodePlane`, with a compile-time `satisfies Record<NodePlane, PlaneAccent>` guard, while reference-code labels stay canonical via `NODE_KIND_METADATA` + `kindOrdinal` (I43-L). | ||
| - The three views render in the ported language: quiet metadata-row chrome, `plane / kind`-grouped node cards with canonical reference codes (`NODE_KIND_METADATA` labels + `kindOrdinal`) and plane-accented `KindBadge`/`CountBadge`, plain session card. The "Edge categories" summary is kept (restyled as `RefBadge` chips); the non-functional "Focus node" placeholder is removed. | ||
| - Read-only contract preserved: no change to queries, RPC client, subscriptions, routes, or projection inputs. | ||
| - Existing web tests preserved; only the two Focus-node assertions removed; `npm run verify` is green (28 web tests, oxlint type-aware clean, build clean). |
| ``` | ||
| ✓ WorkspaceChrome: opaque metadata-row card (rounded-xl, border-rule, bg-white, shadow-card; no rounded-[2rem], no bg-white/45, no backdrop-blur, no tracking-[0.35em] uppercase mono labels) | ||
| ✓ GraphOverviewPanel: groups GraphSlice.nodes by `plane / kind`; each node renders a compact card (canonical reference code + title + body); plane-accented KindBadge + CountBadge per group; Edge-categories summary retained as RefBadge chips; Focus-node button + focused-read placeholder removed; no hover-lift animation | ||
| ✓ SessionPanel + spec.tsx invalid banner: plain bordered cards in the new palette | ||
| ✓ read-only contract intact: no edits to queries/, rpc-client, subscriptions/, routes loaders/params, or projection inputs (GraphSlice/WorkspaceState consumed as-is) | ||
| ✓ src/web/app.test.tsx behavior preserved; only the two Focus-node assertions (+ now-unused fireEvent import) removed; all other assertions pass unchanged; npm run verify green (28 web tests, oxlint type-aware clean, build:web clean, no better-sqlite3 in bundle) | ||
| ``` | ||
|
|
||
| ### Verification Approach | ||
|
|
||
| ``` | ||
| - Inner: npm run verify (vitest over updated web tests; build). | ||
| - Outer: manual browser check of / and /spec/$specId against a seeded spec (npm run seed, launch web mode) — chrome, kind-grouped graph cards, session panel match the prior trunk's look. | ||
| ``` | ||
|
|
||
| ### Cross-cutting obligations | ||
|
|
||
| ``` | ||
| - No new RPC/query/route behavior — presentation only. | ||
| - Edge badges resolve target reference codes through NODE_KIND_METADATA + kindOrdinal (D62-L). | ||
| - empty-state ("No graph nodes yet…") preserved in the new styling. | ||
| ``` |



Ports the prior-trunk restrained web design language into the current read-only web app, and tightens scope/build skill cold-start-read guidance.
workspace.selectionStateand removes the nonfunctional Focus-node placeholder.ln-scope/ln-buildguidance for cold-start reads.Verification: web tests, type-aware lint, and build.