feat(web): chat-first shell redesign — runs rail, composer-first Home, warm reskin #50

Merged
dominik.polakovics merged 6 commits from lab/20260708-1248 into main 2026-07-08 15:45:26 +02:00

Implements the full maintainer spec from the #41 design interview in five commits that mirror the suggested slices.

Closes #41

What changed

① App shell (d9c46f8) — the runs rail is the app's spine on every authenticated page: persistent 260px rail ≥1024px (collapsible via chevron / Ctrl+Cmd+B, state in localStorage), the same SideNav as a left overlay drawer below that (hamburger + left-edge swipe to open; scrim / leftward swipe / Escape / route change to close; iOS-standalone-PWA edge-gesture caveat documented in code). Rail = live instances only, attention-first (needs_input/question → accent dot → working → rest, newest started_at within groups), no destructive actions in rows. Dashboard dissolved: repo cards → /repos, Runs → /history (/runs redirects), ClaudeAuthCard → Credentials. TopBar deleted.

② New-run composer (f5c10c7)/ is a big centered composer: repo chip (last-used remembered, cloning/error repos disabled with live progress), model/effort chips through the existing resolution chain, optional label behind . Send = startInstance → navigate; typed text becomes a client-side queued first message rendered as a pending user bubble that auto-sends exactly once when the transcript is confirmed available (take-before-POST idempotence; failure seeds the composer draft; tab close loses it — documented and accepted; no backend change). Empty send = plain spawn. The AFK machinery re-homes under the composer as a compact per-repo strip (same greyed-hint/paused/Reset semantics); a slim banner links to Credentials while Claude is logged out.

③ Warm reskin (108e2d2, 277142b) — token-level swap: bone/ivory light, warm-charcoal dark, terracotta #d97757 accent. Source Serif 4 variable (OFL) vendored as static woff2 (no npm dep) for headings + assistant prose. Manifest/theme-color follow. Theme stays OS-driven.

④ Chat restyle (60a9ab4) — assistant messages lose the bubble (serif prose on the background, full width); user messages become a soft warm-neutral right-aligned bubble; tool chips flatten to quiet bordered mono rows. All #35 behaviors preserved — RunChat.tsx had zero edits in this slice (pure CSS) and lib/chatStream.ts is untouched.

⑤ Review fixes + docs (0d37595) — from an adversarial review pass: closed drawer now leaves the a11y tree/tab order (was focusable off-screen — worse than the old menu, which unmounted); AFK budget countdown re-homed onto rail rows (it lived only in the deleted InstanceList); rail rows carry the state word in their accessible name (dot is color-only); 44px targets restored on two controls. ADR-0025 records the pivot; CONTEXT.md updated.

Decisions worth reviewer attention

  • --accent-text is warm coffee-brown, not white — white on #d97757 measures 3.12:1 (AA fail); the brown clears 5.36:1. Accent buttons therefore have dark glyphs, which deviates from claude.ai's white-on-coral.
  • --link — terracotta as text (links, active nav, tinted chips) uses a per-theme darker/lighter cut; raw accent on bone is 2.96:1.
  • popover holds only the label: manual spawn accepts label/model/effort (internal/httpapi/instances.go has no options bag), so #21 stays open on its own surfaces.
  • #39 caveat inherited: the rail's state dots read the same tailer state as today's badges, so a running workflow can show as idle in the rail until #39 is fixed — no new derivation added.
  • Auto-vs-manual AFK distinction is no longer chip-labeled on live rows (History still shows kind); flag if you want an auto marker back in the rail.

Verification

  • tsc --noEmit, eslint, prettier --check, vitest run (426 passed / 34 files), vite build — all green.
  • Adversarial Opus review over the full diff: queued-message races, transcript rotation, SSE subscription parity vs the old Dashboard, z-index layering (drawer 30 > chat overlays ≤3), breakpoint boundaries, and the #35 contract all verified OK; its four real findings are fixed in ⑤.
  • No screenshots: this sandbox cannot run a browser (NixOS blocks the Playwright binary; no chromium in the store). Please eyeball before merging: Home composer (light+dark), a chat with tool groups (serif prose / user bubble / quiet tool rows / jump pill), mobile drawer + edge swipe, /repos, /history, Login, and the collapsed-rail state.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX

Implements the full maintainer spec from the #41 design interview in five commits that mirror the suggested slices. Closes #41 ## What changed **① App shell (`d9c46f8`)** — the runs rail is the app's spine on every authenticated page: persistent 260px rail ≥1024px (collapsible via chevron / Ctrl+Cmd+B, state in `localStorage`), the same SideNav as a left overlay drawer below that (hamburger + left-edge swipe to open; scrim / leftward swipe / Escape / route change to close; iOS-standalone-PWA edge-gesture caveat documented in code). Rail = live instances only, attention-first (`needs_input`/`question` → accent dot → `working` → rest, newest `started_at` within groups), no destructive actions in rows. Dashboard dissolved: repo cards → `/repos`, Runs → `/history` (`/runs` redirects), ClaudeAuthCard → Credentials. TopBar deleted. **② New-run composer (`f5c10c7`)** — `/` is a big centered composer: repo chip (last-used remembered, cloning/error repos disabled with live progress), model/effort chips through the existing resolution chain, optional label behind `…`. Send = `startInstance` → navigate; typed text becomes a **client-side queued first message** rendered as a pending user bubble that auto-sends exactly once when the transcript is confirmed available (take-before-POST idempotence; failure seeds the composer draft; tab close loses it — documented and accepted; no backend change). Empty send = plain spawn. The AFK machinery re-homes under the composer as a compact per-repo strip (same greyed-hint/paused/Reset semantics); a slim banner links to Credentials while Claude is logged out. **③ Warm reskin (`108e2d2`, `277142b`)** — token-level swap: bone/ivory light, warm-charcoal dark, terracotta `#d97757` accent. Source Serif 4 variable (OFL) vendored as static woff2 (no npm dep) for headings + assistant prose. Manifest/theme-color follow. Theme stays OS-driven. **④ Chat restyle (`60a9ab4`)** — assistant messages lose the bubble (serif prose on the background, full width); user messages become a soft warm-neutral right-aligned bubble; tool chips flatten to quiet bordered mono rows. **All #35 behaviors preserved — `RunChat.tsx` had zero edits in this slice** (pure CSS) and `lib/chatStream.ts` is untouched. **⑤ Review fixes + docs (`0d37595`)** — from an adversarial review pass: closed drawer now leaves the a11y tree/tab order (was focusable off-screen — worse than the old menu, which unmounted); AFK budget countdown re-homed onto rail rows (it lived only in the deleted InstanceList); rail rows carry the state word in their accessible name (dot is color-only); 44px targets restored on two controls. ADR-0025 records the pivot; CONTEXT.md updated. ## Decisions worth reviewer attention - **`--accent-text` is warm coffee-brown, not white** — white on `#d97757` measures 3.12:1 (AA fail); the brown clears 5.36:1. Accent buttons therefore have dark glyphs, which deviates from claude.ai's white-on-coral. - **`--link`** — terracotta as *text* (links, active nav, tinted chips) uses a per-theme darker/lighter cut; raw accent on bone is 2.96:1. - **`…` popover holds only the label**: manual spawn accepts label/model/effort (`internal/httpapi/instances.go` has no options bag), so **#21 stays open** on its own surfaces. - **#39 caveat inherited**: the rail's state dots read the same tailer state as today's badges, so a running workflow can show as idle in the rail until #39 is fixed — no new derivation added. - Auto-vs-manual AFK distinction is no longer chip-labeled on live rows (History still shows kind); flag if you want an `auto` marker back in the rail. ## Verification - `tsc --noEmit`, `eslint`, `prettier --check`, `vitest run` (**426 passed / 34 files**), `vite build` — all green. - Adversarial Opus review over the full diff: queued-message races, transcript rotation, SSE subscription parity vs the old Dashboard, z-index layering (drawer 30 > chat overlays ≤3), breakpoint boundaries, and the #35 contract all verified OK; its four real findings are fixed in ⑤. - **No screenshots**: this sandbox cannot run a browser (NixOS blocks the Playwright binary; no chromium in the store). Please eyeball before merging: Home composer (light+dark), a chat with tool groups (serif prose / user bubble / quiet tool rows / jump pill), mobile drawer + edge swipe, `/repos`, `/history`, Login, and the collapsed-rail state. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
The runs rail becomes the app's spine on every authenticated page: persistent
260px rail >=1024px (collapsible, Ctrl/Cmd+B, localStorage), overlay drawer
below that (hamburger + left-edge swipe; scrim/Escape/route-change close).
Rail lists live instances attention-first via lib/railOrder. Dashboard
dissolves: repo cards -> /repos, runs list -> /history (/runs redirects),
ClaudeAuthCard -> Credentials; TopBar dies. / is a placeholder for the
composer-first New-run surface (next slice).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Home (/) becomes a big centered composer: repo chip (last-used in
localStorage, not-ready repos disabled with clone progress), model/effort
chips through the existing resolution chain, optional label behind a
popover (manual spawn accepts label/model/effort only — no options bag, so
issue #21 stays open on its own surfaces). Send spawns, then parks the text
as a client-side queued first message that renders as a pending user bubble
in the chat and auto-sends exactly once when the transcript is confirmed
available (take-before-POST idempotence; failure seeds the composer draft).
The AFK machinery re-homes under the composer as a compact per-repo strip;
a slim banner points at Credentials while Claude is logged out. Dashboard-only
components (AFKSection, StartInstanceForm, InstanceList) die.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Token-level swap in base.css: bone light surfaces, warm-charcoal (not
blue-black) dark, terracotta accent (#d97757). accent-text goes warm
coffee-brown instead of white — white-on-terracotta measures 3.12:1 and
fails AA; the brown clears 5.36:1 in both themes. Source Serif 4 variable
(OFL, vendored woff2) covers headings and the brand via --font-serif; UI
chrome stays system sans, code stays mono. Theme-color metas and the PWA
manifest follow the palette; Login/Setup get a centered auth card. Fonts
ride the service worker's existing lazy network-first fallback rather than
eager precache (decorative; the serif fallback stack keeps offline legible).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Assistant messages lose the bubble and read as Source Serif prose directly
on the background at full column width; user messages become a soft
warm-neutral right-aligned bubble (idle-bg light / chip-bg dark — accent is
reserved for actions and attention, never message surfaces); thinking goes
serif-italic muted; tool chips and groups flatten to quiet bordered mono
rows with unchanged disclosure behavior. The queued-first-message pending
bubble keeps its dim + tag on the new bubble. All issue-#35 behaviors
(quick-return header, jump pill, merged composer/Interrupt morph,
in-stream needs-input note) are untouched — RunChat.tsx has zero edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Adversarial-review findings on the redesign: the closed mobile drawer now
leaves the a11y tree and tab order (visibility flips after the slide-out;
the old TopBar menu unmounted entirely, so off-screen-but-focusable was a
regression); AFK rows re-home the budget countdown from the deleted
InstanceList onto the rail's secondary line (30s display tick — the spec's
one sanctioned interval); rail rows carry the state word in their accessible
name (the dot is color-only); rail-logout and the AFK Reset return to 44px
targets. Terracotta-as-text moves to a per-theme --link cut (burnt
terracotta light / lighter dark) — the raw accent measures 2.96:1 on bone.

Plus docs: ADR-0025 records the chat-first pivot; CONTEXT.md's
conversational-state entry now names the rail instead of Dashboard rows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7KA3oRdeGYu8ExvkQReyX
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab!50
No description provided.