Frontend redesign: Claude-Code-web look, mobile revamp, and a side nav of active runs with quick switching #41

Closed
opened 2026-07-08 01:25:21 +02:00 by dominik.polakovics · 3 comments

This was generated by AI during triage.

Summary

Reskin / restructure the web UI to feel more like the Claude Code web interface,
including a mobile revamp, and add a side navigation that lists active runs and
lets you switch between them quickly
.

Motivation

Today the app uses a horizontal top bar (web/src/components/TopBar.tsx:
Repos · Credentials · Runs · Tokens · Settings) that collapses to a hamburger
dropdown on mobile (<640px). There is no persistent side navigation and no quick
switcher between live runs — to move between runs you go via the Runs list
(web/src/routes/Runs.tsx) and open each /runs/:id in turn.

Requested direction (to be refined in triage)

  • Overall look closer to the Claude Code web interface.
  • A side navigation showing active runs, with quick switching between them
    (e.g. a persistent left rail — the example the reporter gave).
  • The mobile interface changes along with it.

Notes for triage

  • Current shell / nav: web/src/App.tsx (Shell) + web/src/components/TopBar.tsx
    (top bar + mobile hamburger dropdown). Live run state is already available
    client-side (SSE via web/src/events.tsx; per-run convo state via the messages
    endpoint and web/src/lib/conversation.ts), which a side rail could drive.
  • Runs list today: web/src/routes/Runs.tsx; chat view: web/src/routes/RunChat.tsx.
  • Overlaps but is distinct from #35 (chat-view redesign: mobile header,
    jump-to-latest pill, merged composer), which is scoped to the /runs/:id view
    and already ready-for-agent. This request is broader (app shell, global side
    nav, run switcher, overall Claude-Code-web look) — triage should decide how to
    slice it and how it relates to #35.
  • Large / cross-cutting: likely wants a design pass and splitting into slices
    (shell + side nav, run switcher, mobile, chat-view convergence with #35).
> *This was generated by AI during triage.* ## Summary Reskin / restructure the web UI to feel more like the Claude Code web interface, including a mobile revamp, and add a **side navigation that lists active runs and lets you switch between them quickly**. ## Motivation Today the app uses a horizontal top bar (`web/src/components/TopBar.tsx`: Repos · Credentials · Runs · Tokens · Settings) that collapses to a hamburger dropdown on mobile (<640px). There is no persistent side navigation and no quick switcher between live runs — to move between runs you go via the Runs list (`web/src/routes/Runs.tsx`) and open each `/runs/:id` in turn. ## Requested direction (to be refined in triage) - Overall look closer to the Claude Code web interface. - A side navigation showing **active runs**, with quick switching between them (e.g. a persistent left rail — the example the reporter gave). - The mobile interface changes along with it. ## Notes for triage - Current shell / nav: `web/src/App.tsx` (Shell) + `web/src/components/TopBar.tsx` (top bar + mobile hamburger dropdown). Live run state is already available client-side (SSE via `web/src/events.tsx`; per-run convo state via the messages endpoint and `web/src/lib/conversation.ts`), which a side rail could drive. - Runs list today: `web/src/routes/Runs.tsx`; chat view: `web/src/routes/RunChat.tsx`. - **Overlaps but is distinct from #35** (chat-view redesign: mobile header, jump-to-latest pill, merged composer), which is scoped to the `/runs/:id` view and already `ready-for-agent`. This request is broader (app shell, global side nav, run switcher, overall Claude-Code-web look) — triage should decide how to slice it and how it relates to #35. - Large / cross-cutting: likely wants a design pass and splitting into slices (shell + side nav, run switcher, mobile, chat-view convergence with #35).
Author
Owner

Resolved direction (design interview with the maintainer, 2026-07-08)

The reporter and the maintainer are the same person; the open questions in this issue were resolved one-by-one in a design interview. This comment is the binding spec. Remaining triage work is slicing (see the end), not direction.

Decisions

  1. Full pivot to a chat-first shell. The runs sidebar becomes the app's spine on every authenticated page. The Dashboard dissolves; its pieces are re-homed (below). This is the Claude Code web IA, not a rail bolted onto the current app.
  2. + New run is composer-first. The New-run surface is a big centered composer (Claude-Code-web style): repo chip + model/effort chips + text box. Send = startInstance → navigate to the chat → the typed text is a client-side queued first message that auto-sends when the transcript becomes ready. Sending empty = plain spawn (today's semantics). No backend change.
  3. Home (/) = the New-run composer. Predictable, never auto-navigates; anything urgent is one tap away in the rail. Same surface serves the zero-runs empty state.
  4. Rail contents: live instances only, flat, attention-first. needs-input/question runs float to the top with an accent dot; working runs below by last activity; idle last. Ended runs drop out of the rail; history stays a footer link. Driven by the existing listInstances() + run.changed SSE refetch (same data as today's InstanceList).
  5. AFK machinery lives on the New-run surface, as a compact per-repo strip under the composer, following the composer's repo chip: claimable count · Run one · Auto toggle · three-strikes paused banner with Reset inline. One surface for both ways of dispatching work.
  6. Mobile (<640px): left drawer, same sidebar component. Hamburger top-left and edge-swipe-to-open (explicit maintainer requirement); swipe/scrim/Escape to close. Known caveat to document in code: iOS standalone PWA can still win the left-edge gesture for history-back on some versions — the hot-zone handler (non-passive, preventDefault on horizontal intent) is best-effort; the button is the reliable path. Android/desktop are clean.
  7. Full warm reskin, both themes. Token-level swap in base.css: bone/ivory surfaces, warm near-black text, terracotta accent (~#d97757 family), warm-charcoal dark (not blue-black). One bundled open-license variable serif (Source Serif 4 or Lora, woff2, self-hosted static asset — not an npm dep) for headings + assistant prose; UI chrome stays system sans; code stays system mono.
  8. Chat restyle, Claude-style: assistant messages lose the bubble — serif prose directly on the background, full column width. User messages become a soft warm-neutral bubble, right-aligned (accent is reserved for actions/attention, so no solid coral bubble). Tool chips flatten to quiet bordered mono rows, same expand behavior. All #35 behaviors are preserved (hide-on-scroll header, jump-to-latest pill, merged composer, in-stream needs-input note).
  9. Theme stays OS-driven. The documented "no toggle, no persisted theme state" principle stands; a Light/Dark/System setting is an explicit non-goal for this redesign (clean later add-on).

Shell spec (defaults chosen by the designer — veto in comments)

  • Breakpoints: keep 640px; add ~1024px. <1024 = drawer pattern (tablet included); ≥1024 = persistent rail (~260px), user-collapsible (chevron + Ctrl/Cmd+B, collapsed state in localStorage). Content column stays a centered max-width (~760px) in the remaining space.
  • Rail anatomy: + New run pinned top → ACTIVE list (state dot · title · repo secondary text; title = label or AFK #N) → footer: Repos · History · Credentials · Tokens · Settings → user + logout + SSE live dot at the very bottom. No destructive actions in rail rows (Stop stays in the chat header ••• from #35).
  • Attention when the drawer is closed (mobile): a dot/count badge on the hamburger whenever any run is needs-input/question.
  • Queued first message mechanics: held in an in-memory per-run map (survives SPA route changes); rendered in the stream as a pending user bubble ("sends when Claude is ready"); spawn failure returns the text to the composer with an error; tab close loses it — accepted and documented.
  • Composer extras: optional label + provider spawn-options behind a popover (interacts with #21 — resolve that checkbox's 3-state there); model/effort chips prefilled from the existing resolution chain; repo chip remembers the last-used repo (localStorage).
  • Dashboard re-homing: ClaudeAuthCard → Credentials page, plus a slim "Claude is logged out — reconnect" banner on the New-run surface (the spawn 409 path); parked strip, stop-all, clone status/progress → the Repos page (per-repo cards; the composer's repo chip shows not-ready repos disabled with progress); Runs page becomes History, restyled, otherwise unchanged; repo sub-pages keep their routes and gain the rail.
  • Login/Setup: same warm skin, centered card, no rail (unauthenticated).
  • PWA: manifest theme_color moves to the warm palette.
  • A11y: rail is a nav landmark; drawer closes on Escape/route-change (existing TopBar pattern carries over); prefers-reduced-motion respected for drawer/rail animation.
  • Constraints unchanged: SolidJS + plain global CSS, no new npm dependencies (fonts are static assets), SSE-driven — no polling.

Non-goals (this redesign)

Theme toggle · command-palette quick switcher (Cmd+K) · run-switch keyboard shortcuts · recently-ended runs in the rail · bottom-sheet menus. All are clean later add-ons.

  • #35 (landed): its chat behaviors are load-bearing inputs to §8 — preserve, don't regress.
  • #21: the spawn-options 3-state problem should be fixed in/with the composer's popover.
  • #39: attention-first sorting inherits the tailer's state — the workflow-reads-as-idle bug makes the rail lie the same way the badge does today; worth fixing alongside or before.
  • #40: unrelated to the shell (dialog rendering), no interaction.

Remaining triage

Slice into tracer-bullet, independently-landable issues (suggested order: ① rail/shell + drawer skeleton with re-homed nav → ② New-run composer + queued first message + AFK strip → ③ warm reskin tokens + serif asset → ④ chat restyle → ⑤ Dashboard dissolution/re-homing + History rename). Keeping needs-triage until sliced; the slices become ready-for-agent, this umbrella then closes in favor of them.

## Resolved direction (design interview with the maintainer, 2026-07-08) The reporter and the maintainer are the same person; the open questions in this issue were resolved one-by-one in a design interview. This comment is the binding spec. Remaining triage work is **slicing** (see the end), not direction. ### Decisions 1. **Full pivot to a chat-first shell.** The runs sidebar becomes the app's spine on every authenticated page. The Dashboard dissolves; its pieces are re-homed (below). This is the Claude Code web IA, not a rail bolted onto the current app. 2. **`+ New run` is composer-first.** The New-run surface is a big centered composer (Claude-Code-web style): repo chip + model/effort chips + text box. Send = `startInstance` → navigate to the chat → the typed text is a **client-side queued first message** that auto-sends when the transcript becomes ready. Sending empty = plain spawn (today's semantics). No backend change. 3. **Home (`/`) = the New-run composer.** Predictable, never auto-navigates; anything urgent is one tap away in the rail. Same surface serves the zero-runs empty state. 4. **Rail contents: live instances only, flat, attention-first.** needs-input/question runs float to the top with an accent dot; working runs below by last activity; idle last. Ended runs drop out of the rail; history stays a footer link. Driven by the existing `listInstances()` + `run.changed` SSE refetch (same data as today's InstanceList). 5. **AFK machinery lives on the New-run surface**, as a compact per-repo strip under the composer, following the composer's repo chip: claimable count · Run one · Auto toggle · three-strikes paused banner with Reset inline. One surface for both ways of dispatching work. 6. **Mobile (<640px): left drawer, same sidebar component.** Hamburger top-left **and edge-swipe-to-open (explicit maintainer requirement)**; swipe/scrim/Escape to close. Known caveat to document in code: iOS standalone PWA can still win the left-edge gesture for history-back on some versions — the hot-zone handler (non-passive, `preventDefault` on horizontal intent) is best-effort; the button is the reliable path. Android/desktop are clean. 7. **Full warm reskin, both themes.** Token-level swap in `base.css`: bone/ivory surfaces, warm near-black text, terracotta accent (~#d97757 family), **warm-charcoal dark** (not blue-black). One bundled open-license variable serif (Source Serif 4 or Lora, woff2, self-hosted static asset — not an npm dep) for headings + assistant prose; UI chrome stays system sans; code stays system mono. 8. **Chat restyle, Claude-style:** assistant messages lose the bubble — serif prose directly on the background, full column width. User messages become a soft warm-neutral bubble, right-aligned (accent is reserved for actions/attention, so no solid coral bubble). Tool chips flatten to quiet bordered mono rows, same expand behavior. All #35 behaviors are preserved (hide-on-scroll header, jump-to-latest pill, merged composer, in-stream needs-input note). 9. **Theme stays OS-driven.** The documented "no toggle, no persisted theme state" principle stands; a Light/Dark/System setting is an explicit non-goal for this redesign (clean later add-on). ### Shell spec (defaults chosen by the designer — veto in comments) - **Breakpoints:** keep 640px; add ~1024px. `<1024` = drawer pattern (tablet included); `≥1024` = persistent rail (~260px), user-collapsible (chevron + Ctrl/Cmd+B, collapsed state in localStorage). Content column stays a centered max-width (~760px) in the remaining space. - **Rail anatomy:** `+ New run` pinned top → ACTIVE list (state dot · title · repo secondary text; title = label or `AFK #N`) → footer: Repos · History · Credentials · Tokens · Settings → user + logout + SSE live dot at the very bottom. No destructive actions in rail rows (Stop stays in the chat header `•••` from #35). - **Attention when the drawer is closed (mobile):** a dot/count badge on the hamburger whenever any run is needs-input/question. - **Queued first message mechanics:** held in an in-memory per-run map (survives SPA route changes); rendered in the stream as a pending user bubble ("sends when Claude is ready"); spawn failure returns the text to the composer with an error; tab close loses it — accepted and documented. - **Composer extras:** optional label + provider spawn-options behind a `…` popover (interacts with #21 — resolve that checkbox's 3-state there); model/effort chips prefilled from the existing resolution chain; repo chip remembers the last-used repo (localStorage). - **Dashboard re-homing:** ClaudeAuthCard → Credentials page, plus a slim "Claude is logged out — reconnect" banner on the New-run surface (the spawn 409 path); parked strip, stop-all, clone status/progress → the Repos page (per-repo cards; the composer's repo chip shows not-ready repos disabled with progress); Runs page becomes **History**, restyled, otherwise unchanged; repo sub-pages keep their routes and gain the rail. - **Login/Setup:** same warm skin, centered card, no rail (unauthenticated). - **PWA:** manifest `theme_color` moves to the warm palette. - **A11y:** rail is a `nav` landmark; drawer closes on Escape/route-change (existing TopBar pattern carries over); `prefers-reduced-motion` respected for drawer/rail animation. - **Constraints unchanged:** SolidJS + plain global CSS, no new npm dependencies (fonts are static assets), SSE-driven — no polling. ### Non-goals (this redesign) Theme toggle · command-palette quick switcher (Cmd+K) · run-switch keyboard shortcuts · recently-ended runs in the rail · bottom-sheet menus. All are clean later add-ons. ### Related issues - #35 (landed): its chat behaviors are load-bearing inputs to §8 — preserve, don't regress. - #21: the spawn-options 3-state problem should be fixed in/with the composer's `…` popover. - #39: attention-first sorting inherits the tailer's state — the workflow-reads-as-idle bug makes the rail lie the same way the badge does today; worth fixing alongside or before. - #40: unrelated to the shell (dialog rendering), no interaction. ### Remaining triage Slice into tracer-bullet, independently-landable issues (suggested order: ① rail/shell + drawer skeleton with re-homed nav → ② New-run composer + queued first message + AFK strip → ③ warm reskin tokens + serif asset → ④ chat restyle → ⑤ Dashboard dissolution/re-homing + History rename). Keeping `needs-triage` until sliced; the slices become `ready-for-agent`, this umbrella then closes in favor of them.
Author
Owner

Implemented in full on PR #50 (#50) rather than sliced into separate issues — the five commits on the branch mirror the slicing order suggested here (shell → composer/queued-first-message → reskin tokens+serif → chat restyle → fixes/docs), so each slice is still independently reviewable in the commit history. ADR-0025 records the pivot. Two scope notes: #21 stays open (manual spawn has no options bag, so the composer's … popover carries only the label), and the rail inherits #39's tailer staleness the same way today's badges do.

Implemented in full on PR #50 (https://git.cloonar.com/Cloonar/coding-lab/pulls/50) rather than sliced into separate issues — the five commits on the branch mirror the slicing order suggested here (shell → composer/queued-first-message → reskin tokens+serif → chat restyle → fixes/docs), so each slice is still independently reviewable in the commit history. ADR-0025 records the pivot. Two scope notes: #21 stays open (manual spawn has no options bag, so the composer's … popover carries only the label), and the rail inherits #39's tailer staleness the same way today's badges do.
Author
Owner

This was generated by AI while landing a PR.

Land-PR validation of PR #50 — audit posted on the issue because labctl has no PR-comment verb.

Verdict: PASS, pending the maintainer's free-text merge go-ahead.

What was checked and relied on:

  • Mergeable: clean test-merge against origin/main at head 0d37595.
  • Verification signal: the forge's CI conclusion is not readable from the lab sandbox (no forge token, no tea), so the native gate's SPA checks were re-run locally: eslint clean, prettier --check clean, vitest run 426/426, tsc --noEmit + vite build clean. The diff touches no Go/nix/dependency files (51 files: web/ + CONTEXT.md + ADR-0025), so ci-nix legitimately skips and the Go gates are unaffected. Branch protection still enforces the required native check server-side at merge.
  • Conventions: Conventional Commits title; Closes #41 present in the body.
  • Diff review against the binding #41 spec comment: shell rail/drawer (intent-gated edge swipe, Escape/scrim/route-close, Ctrl/Cmd+B collapse persisted), composer-first Home (queue-only-on-successful-spawn, take-before-POST exactly-once auto-send, failed send seeds the composer draft), attention-first rail order (documented started_at fallback — API has no last-activity), Dashboard re-homing (repo cards→/repos, Runs→/history with /runs redirect, ClaudeAuthCard→Credentials), warm token swap, chat restyle CSS-only (RunChat.tsx untouched by that slice, lib/chatStream.ts untouched everywhere — verified via per-file git log). The ⑤ review fixes are present: closed drawer is visibility: hidden (out of a11y tree/tab order, restored ≥1024px), rail rows carry the state word in their accessible name, AFK budget countdown on rail rows.
  • Contrast claims recomputed (WCAG relative luminance): accent-text 5.36:1, light link 4.83:1, white-on-accent 3.12:1 fail — all match the comments. One nit: dark --link measures 5.17:1, not the claimed 5.3:1; still clears AA 4.5:1.

Non-blocking nits: the 5.17-vs-5.3 comment figure above; the PR body says '34 test files' where vitest counts 33 (426 tests match).

Not verified here: visual QA — this sandbox cannot run a browser; the PR body lists the surfaces to eyeball before/after merging.

> *This was generated by AI while landing a PR.* **Land-PR validation of PR #50** — audit posted on the issue because labctl has no PR-comment verb. **Verdict: PASS**, pending the maintainer's free-text merge go-ahead. What was checked and relied on: - **Mergeable**: clean test-merge against `origin/main` at head `0d37595`. - **Verification signal**: the forge's CI conclusion is not readable from the lab sandbox (no forge token, no `tea`), so the `native` gate's SPA checks were **re-run locally**: `eslint` clean, `prettier --check` clean, `vitest run` **426/426**, `tsc --noEmit` + `vite build` clean. The diff touches no Go/nix/dependency files (51 files: `web/` + CONTEXT.md + ADR-0025), so `ci-nix` legitimately skips and the Go gates are unaffected. Branch protection still enforces the required `native` check server-side at merge. - **Conventions**: Conventional Commits title; `Closes #41` present in the body. - **Diff review against the binding #41 spec comment**: shell rail/drawer (intent-gated edge swipe, Escape/scrim/route-close, Ctrl/Cmd+B collapse persisted), composer-first Home (queue-only-on-successful-spawn, take-before-POST exactly-once auto-send, failed send seeds the composer draft), attention-first rail order (documented `started_at` fallback — API has no last-activity), Dashboard re-homing (repo cards→/repos, Runs→/history with /runs redirect, ClaudeAuthCard→Credentials), warm token swap, chat restyle CSS-only (`RunChat.tsx` untouched by that slice, `lib/chatStream.ts` untouched everywhere — verified via per-file git log). The ⑤ review fixes are present: closed drawer is `visibility: hidden` (out of a11y tree/tab order, restored ≥1024px), rail rows carry the state word in their accessible name, AFK budget countdown on rail rows. - **Contrast claims recomputed** (WCAG relative luminance): accent-text 5.36:1, light link 4.83:1, white-on-accent 3.12:1 fail — all match the comments. One nit: dark `--link` measures **5.17:1**, not the claimed 5.3:1; still clears AA 4.5:1. Non-blocking nits: the 5.17-vs-5.3 comment figure above; the PR body says '34 test files' where vitest counts 33 (426 tests match). Not verified here: **visual QA** — this sandbox cannot run a browser; the PR body lists the surfaces to eyeball before/after merging.
Sign in to join this conversation.
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#41
No description provided.