Skip to content

feat(redesign): msc-redesign foundation (theme tokens + Plan tab)#37

Open
kevinthelago wants to merge 20 commits into
developfrom
feature/msc-redesign
Open

feat(redesign): msc-redesign foundation (theme tokens + Plan tab)#37
kevinthelago wants to merge 20 commits into
developfrom
feature/msc-redesign

Conversation

@kevinthelago
Copy link
Copy Markdown
Owner

Foundation commit for the msc-redesign work. Source mockups live under design/msc-redesign/ (committed in b8abbe2).

This PR lands the lowest-risk layer so subsequent screen migrations compose against a stable base. Per-screen migrations are deliberately deferred to follow-up PRs — see docs/redesign-status.md for the full per-screen list.

What's here

  • src/theme.tsTheme gains elev, elev2, borderStrong, accentDim, success, info, warn, danger, pink. dawn exactly mirrors design/msc-redesign/.../styles.css :root (which the design notes "marries dawn"). The other four themes get palette-coherent values so primitives read correctly regardless of which theme is active.
  • src/components/TabIcons.tsx + src/components/ui/BottomTabBar.tsx — new PlanIcon (kanban-board glyph matching the design's mark) + wired plan into the ICONS/LABELS maps.
  • app/(tabs)/_layout.tsx — registered the 6th tab (Files · Find · Edit · Run · Git · Plan).
  • app/(tabs)/plan.tsx — minimal themed placeholder, two states keyed on useTunnel(): a tunnel offline → pair via Run tab empty state, and a coming soon state when connected.
  • docs/redesign-status.md — per-screen migration list + open items.

What's not here (and where it lives)

Item Status Notes
Visual primitives (PageHeader, Card, Tag, SectionLabel, Btn) not started next layer; pure additive
Files screen migration not started follow-up PR
Find screen migration not started follow-up PR
Edit screen migration (incl. inline chat dock) not started folds the chat into Edit
Git screen migration not started AI-drafted commit message card
Run screen already shipped run.tsx is the redesigned Run; only needs visual alignment
Plan sub-screens (5) not started depend on tunnel exposing planning state
SessionStrip styling refresh not started the strip itself ships on develop already
Session-switcher overlay not started drawer drop-down
JetBrains Mono font asset not loaded currently Menlo
CLAUDE.md cleanup open finding "Run tab is a placeholder" is stale; belongs in #12

Verification

  • tsc --noEmit is green.
  • Adding plan to the existing BottomTabBar maps is additive; the maps already had ?? FilesIcon fallback for unknown routes, so this can't break the other 5 tabs.

Notes

  • The redesign assumes the tunnel + multi-session model are the primary surface — a shift from CLAUDE.md's standalone-first ethos. Worth surfacing when docs: document standalone + optional tunnel architecture #12 (architecture docs) lands.
  • The CLAUDE.md description of run.tsx as a placeholder is stale; run.tsx already ships the full pairing + pane grid + terminal view that the redesign mockup shows.

🤖 Generated with Claude Code

kevinthelago and others added 20 commits May 28, 2026 16:14
Drops the new redesign mockups under design/msc-redesign/ so they are versioned
source-of-truth for the implementation work that follows. Includes a Babel-
transformed HTML preview, the shared scaffold (SessionStrip + PageHeader +
BottomTabs primitives), shared CSS tokens, and five mockup screens:

  scaffold.jsx                  - shared Screen wrapper, 6 tabs incl. new Plan
  styles.css                    - CSS tokens; reads as 'marries dawn palette'
  screen-files-find.jsx         - Files + Find mockups
  screen-edit-run-git.jsx       - Edit + Run (multi-session) + Git mockups
  screen-plan.jsx               - new Plan tab w/ 5 sub-screens (tunneled)
  screen-session-switcher.jsx   - drop-down session switcher overlay

Implementation lives in subsequent commits.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Foundation commit for the msc-redesign work (source in design/msc-redesign/).
Adds the lowest-risk pieces so subsequent screen migrations compose against a
stable base.

- theme.ts: extend Theme with elev, elev2, borderStrong, accentDim, success,
  info, warn, danger, pink. dawn's values exactly mirror the design's
  styles.css :root (which the design notes 'marries dawn'); the other four
  themes get coherent palette-aligned values so the redesign primitives read
  correctly regardless of which theme is active.

- TabIcons.tsx + BottomTabBar.tsx: add PlanIcon (kanban-board glyph matching
  the design's '▩' mark) and wire 'plan' into the ICONS/LABELS maps. The maps
  already had a fallback for unknown routes, so this is additive.

- app/(tabs)/_layout.tsx: register the new Plan tab.

- app/(tabs)/plan.tsx: minimal themed placeholder. Two states keyed on
  useTunnel(): 'tunnel offline → pair via Run tab' empty state, and 'coming
  soon' for the connected state. The full Plan surface (projects, kanban,
  issue/subtask, scoping) follows in later commits.

- docs/redesign-status.md: tracks what landed and what remains, with a
  per-screen migration list and notes on stale CLAUDE.md references the
  reviewer caught earlier (run is no longer a placeholder; the standalone-first
  ethos in #12 needs to acknowledge the multi-agent pivot).

tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…l, Btn)

Pure additive components under src/components/ui/. Faithful port of the
design's .msc-* CSS classes from design/msc-redesign/.../styles.css. No
existing screen is changed yet — these are the building blocks for the
per-screen migrations that follow.

- PageHeader — github-style header: crumbs (mono, uppercase, fgDim, 0.6
  letter-spacing) > title (large mono, ellipsised) + meta + right slot.
- Card — lightweight bordered panel (surface + borderColor, radius 8).
  Distinct from Surface, which keeps its heavier blur/glass treatment.
- Tag — monospace pill, variants default/amber/green/info/warn. The design's
  color-mix(in oklch, ...) tints are approximated with a small hexAlpha()
  helper that emits rgba() at the same 12% / 30% alpha steps.
- SectionLabel — uppercase letter-spaced section header with count + optional
  pressable action.
- Btn — sized + variant button (default / primary / ghost) × (md / sm).
  IconBtn is kept distinct for icon-only round buttons.

Refs the design's bundled CSS at design/msc-redesign/msc-redesign/styles.css.
tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
First per-screen migration. Reskins app/(tabs)/index.tsx to the redesign's
github-style chrome using the new primitives; all real data wiring is
preserved unchanged (useSession, buildTree, openFile, recents, filter, folder
expand/collapse).

- PageHeader: breadcrumb (repo > branch) + leaf title + meta line with the
  modified count in accent. Replaces the old 28px eyebrow/title header.
- SectionLabel: 'Recent' and 'Files' headers; the Files label shows the file
  count and a dim 'tap folder to expand' hint (or 'N matches' when filtering).
- Card: horizontal Recents cards (accent-tinted + accentDim border for the
  current file).
- Tree: mono rows matching .msc-row — folder glyphs (▾/▸), file '·', current
  row gets an accent tint + 2px left border, modified files show a dirty dot.
  Removed the old SVG FolderChevron/FileGlyph in favor of the design's text
  glyphs.

Also adds a  prop to SectionLabel (dim, normal-case, right-aligned) so
passive helper text is visually distinct from the accent  affordance —
the design uses fgDim for 'tap folder to expand' vs accent for 'clear'/'open ›'.

Behavior change (noted in redesign-status.md): Recents now hide while a filter
is active (previously always shown) — when filtering you want matches, not
recents.

tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Second per-screen migration. Reskins app/(tabs)/find.tsx to the redesign
chrome; all real grep wiring is preserved unchanged (grepInText scan across
downloaded files, scope/ext filtering, debounced run, MAX_* caps, openFile
navigation to the Edit tab).

- PageHeader: breadcrumb (repo > branch > search), the query as the title, and
  a "N matches · M files" meta line.
- Search input restyled to the msc-input look; scanning spinner overlaid.
- Scope chips + case-sensitive toggle now render as Tag (amber when active),
  with an "in: <scope>" info Tag indicating a non-"all" scope. The design's
  whole-word/regex tags are intentionally omitted — grepInText does not support
  them, so they would be dead controls.
- Per-file groups: a mono, normal-case header (name · count · "open ›") rather
  than the uppercase SectionLabel (file paths are not section titles), and one
  Card per match line with the line number and an accent-highlighted match.

Refactor: extracted hexAlpha into src/lib/color.ts (the design's
color-mix(in oklch, ...) tint approximation) now that three call sites need it
(Tag, Files, Find). Tag and Files updated to import it; no behavior change.

tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Third per-screen migration, and the structural one: folds the chat into a
bottom dock beneath the code viewport. All real wiring is preserved unchanged
(useSession send/turns/chatBusy/saveCurrentFile/setCurrentContent, image
attach/remove, tokenized code + plain editor, save/edit-mode toggle).

- PageHeader: breadcrumb (path dirs) + filename title + "lang · ● modified"
  meta; right slot keeps the existing save (when dirty) / edit-mode toggle.
- Chat dock: a status header (dot + "Claude · idle/working" + "N tools used"
  Tag), a height-capped scroll of recent turns, the image-preview strip, and a
  "›"-prefixed input bar in the msc-input style.
- ChatTurnView reskin: Claude replies get an accent ◉ marker; tool calls render
  as compact "→ name(args) … ✓/✗" Cards (design's .msc-chat-tool) using the new
  success/danger theme tokens instead of hardcoded colors.
- Dropped the Surface/TopPill/ClaudeAvatar chrome in favor of PageHeader + the
  dock; removed those now-unused imports.

tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Fourth per-screen migration. Reskins app/(tabs)/git.tsx; all real wiring is
preserved unchanged (pull/push/pulling/pushing, anthropicDraftCommitMessage,
linked-issue ref composition, conflict reporting, M/A derivation from manifest).

- PageHeader: breadcrumb (repo > branch) + branch title + "N changed"/"clean"
  meta + a push Btn in the right slot.
- Action row: pull + switch-repo ghost Btns.
- Changes: a SectionLabel + one Card per modified file (M/A badge tinted via
  hexAlpha, tap to open in Edit).
- Commit message: SectionLabel whose action slot drives draft/regenerate (the
  former "Draft with Claude"; shows "add API key to draft" / "drafting…" hints),
  an editable message Card, linked-issue ref modes (refs/fixes/none) as Tags,
  and a commit Btn.
- Dropped the Surface/ClaudeAvatar/SVG chrome.

Honest omissions: the design's per-file +adds/-dels and "stage all" are not
implemented — this app tracks neither line diffs nor a staging area (CLAUDE.md
known issues), so rendering them would be fake controls.

tsc --noEmit green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Bump expo-updates/expo-camera/expo-image-picker to their SDK 54
  versions (expo install --fix); the mismatched majors crashed the
  iOS build at expo-updates' "Generate updates resources" script
  (@expo/cli MODULE_NOT_FOUND).
- Call messaging().registerDeviceForRemoteMessages() before getToken()
  in initFcm — iOS throws [messaging/unregistered] otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…unpair

Onboarding & credentials
- Remove the blocking setup screen; app loads straight into the tabs.
- Anthropic key is optional; prompt for it just-in-time via a modal
  (ApiKeyContext + ApiKeyModal) when the on-device agent is invoked.
- GitHub PAT entered just-in-time in Settings.

Settings (app/settings.tsx)
- New grouped Settings screen (account/GitHub, AI key, tunnel, repo,
  theme, about/sign-out), reached via the ▦ button in SessionStrip.

Navigation & visuals
- Tunnel-first launch: tabs anchor on Run (initialRouteName), which is
  the QR pairing view when disconnected.
- Unified background: ThemedFrame paints t.bg + Orbs + a BlurView frost;
  all screens render transparent on top (tab scenes set transparent).

Tunnel (#16)
- unpair() forgets the desktop (clears tunnel secrets, returns to
  standalone with repo state intact); disconnect() stays transient.
- ConnectingView gains a Cancel escape.
- Tagged tunnel logging (tunnelLog / [tunnel-tls]).

Also includes a native cert-pinning module (TlsWebSocket) + QR cert
fingerprint plumbing — superseded by the Noise-relay architecture and
removed in the following commit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The tunnel pivoted to a Noise-IK end-to-end layer over a real-TLS relay
(WAN) and plain ws:// (LAN), so trust comes from Noise rather than the
transport cert. Removes the now-dead pieces:

- delete the native TlsWebSocket module (modules/tls-websocket/)
- revert TunnelClient to the plain JS WebSocket; drop the fingerprint
  field/param
- drop fingerprint from connect()/auto-connect/unpair and the
  TUNNEL_FINGERPRINT secret
- remove QrPairingPayload (fingerprint shape) and revert the QR parser

The Noise pairing payload + transport land next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Pivot the mobile tunnel to the Noise-relay architecture (epic #210):
trust comes from a Noise IK end-to-end session, not the transport.

- src/lib/noise/noise.ts — Noise_IK_25519_ChaChaPoly_BLAKE2s state
  machine (initiator + responder) on @noble/curves/ciphers/hashes;
  injectable RNG so it stays pure/testable. MUST interop with Rust
  `snow` — verify against the live relay before trusting it.
- scripts/noise-selftest.ts + `npm run test:noise` — JS round-trip
  test: handshake, bidirectional transport, nonce increment, and
  imposter-responder-key rejection. (Proves internal consistency, not
  snow interop.)
- src/lib/types.ts — TunnelPairing payload {relayUrl, room, hostPubKey,
  psk, lanUrl?}; src/lib/tunnelPairing.ts — pure QR parser.
- src/lib/noise/random.ts — on-device CSPRNG via expo-crypto.
- deps: @noble/curves+ciphers+hashes, expo-crypto.
- tsconfig: exclude scripts/ from the app typecheck.

Next: TunnelTransport interface + WAN relay impl (binary ws frames),
session controller, then UI — pending a proven handshake vs the desktop.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Retire the snow-interop risk before building the transport/session/UI:
a fixed-key Noise test vector (cacophony format) so the desktop's Rust
`snow` can prove byte-compatibility with no live connection.

- scripts/noise-vectors.ts → src/lib/noise/noise-vectors.json (msg1/msg2
  + transport, both directions, from fixed static+ephemeral keys)
- scripts/noise-interop.ts — replays the vector through the JS impl and
  asserts byte-equality (passes); the desktop mirrors this against snow
- src/lib/noise/README.md — protocol params + what the desktop must
  assert + coordination points (prologue, framing, associated data)
- npm: test:noise:interop, test:noise:vectors

Handoff: base-studio-code asserts snow reproduces noise-vectors.json
(and confirms the empty prologue). Mobile transport/session/UI follow a
green interop on both sides.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o-pair (#16)

Wire the mobile tunnel to connect to the desktop through the zero-knowledge
Cloudflare relay over an end-to-end Noise IK session, replacing the plaintext
LAN-only client. The Noise machinery, relay-shaped TunnelPairing type, and QR
parser already existed; this lands the transport that uses them (the gap from
#197, the desktop half of which shipped in #240–#243/#251/#253).

- `noiseSession.ts` (new): framing helpers — base64→bytes (QR hostPubKey),
  binary-frame coercion, a dependency-free UTF-8 codec, and sealFrame/openFrame
  that wrap one app JSON message in one Noise transport frame (raw ciphertext,
  empty AD — byte-matching the desktop's serde_json→write_message→Binary).
- `TunnelClient` (tunnel.ts): dial `…/connect?room=<room>&role=guest`, receive
  ArrayBuffer frames, run the Noise IK initiator handshake (msg1 out → msg2 in →
  transport split), then send `auth { token: psk }` and carry every frame
  encrypted. Fresh handshake per (re)connect; a crypto/parse failure drops the
  socket so reconnect re-handshakes.
- `connect()` now takes a `TunnelPairing` (relayUrl/room/hostPubKey/psk). The
  context persists/restores it as one JSON blob (`TUNNEL_PAIRING`) and unpair
  clears it; the pairing screen parses the QR via `parseTunnelPairing` and the
  manual fallback pastes the pairing JSON (long base64 keys can't be hand-typed).
- `scripts/tunnel-selftest.ts` + `npm run test:tunnel`: in-process handshake +
  framing round-trip (auth frame, UTF-8 pane_output, nonce advance, tamper
  rejection, base64 hostPubKey decode). README coordination notes confirmed.

Transport is relay-only (no LAN), matching docs/tunnel-protocol.md. Interop with
the desktop `snow` responder still needs a live handshake to fully verify.

Test: npm run typecheck (clean), test:tunnel (7/7), test:noise + interop (green).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Build the Plan tab (M-plan-*) under src/screens/plan/, hosted by
app/(tabs)/plan.tsx via PlanRoot — an in-tab stack navigator gated on
useTunnel(). The Plan surface mirrors the desktop project-planning views
over the Noise relay; with no live tunnel it renders the pairing screen.

- PlanProjects: on-host/other-host grouping, filter, new-project CTA
- PlanBoard: horizontal column pager + in-progress card list
- PlanIssue: labels/assignees, description, Claude subtask breakdown,
  activity feed, comment composer
- PlanScoping: @Planner chat, progress strip, draft-milestone preview
- PlanPairing: tunnel-offline state wired to useTunnel().connect

Connection gating and pairing are real; the data-bearing screens use the
design's fixtures (planData.ts) as a ready-to-wire presentation layer,
since the tunnel protocol carries only PTY panes today. Shared primitives
in planShared.tsx; design oklch hues approximated as hex. Added an
optional style prop to Btn for composer flex layout.

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M-sessionstrip. Restyle the SessionStrip chips to the redesign vocabulary
(theme tokens, accent-tinted active chip, success/idle/warn/error status
dots, awaiting pip) and add the pulsing tunnel-connected indicator
(design's .msc-strip-tunnel).

Add SessionSwitcher — a drawer overlay (drops from top) that groups live
PTY panes by project (cwd basename), shows all/awaiting/running/idle
counts, and focuses a pane on tap (→ Run tab). The ▦ button now opens
this hub instead of navigating straight to Settings; Settings stays
reachable from the switcher header.

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M-run-align. Rebuild PaneGridView on the redesign primitives: PageHeader
(base-studio-code › tunnel · 'N active · M awaiting input' · disconnect),
an all/running/awaiting/idle Tag count row, and Card-based session rows
with a status dot, project Tag, and a 2-line preview box. Awaiting-input
sessions get the warn border + thicker left edge per the design mockup.
Status colors now resolve from theme tokens (shared by the terminal
header). Unpair preserved as a danger affordance in the tag row / empty
state. All tunnel wiring unchanged.

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
M-font. Add @expo-google-fonts/jetbrains-mono and point every theme's
fontMono at 'JetBrains Mono' (the design's --msc-mono), replacing Menlo.
The tab surface gates render on the font load (no fallback flash) and
theme.ts kicks off a best-effort app-wide load for the onboarding screens.

Pin expo-font to ~14.0.12 to match Expo SDK 54 (npm had pulled 56.x).

tsc --noEmit clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correct the authoritative agent reference to the architecture that shipped:

- Frame MSC's two modes — standalone (GitHub REST) and tunneled (paired
  desktop over an end-to-end-encrypted Noise IK tunnel relayed through a
  blind Cloudflare Worker, NOT a direct desktop WebSocket server).
- Add a 'Tunnel & multi-session' section (transport, pairing, sessions,
  Plan, disconnect/unpair) and a tunnel pairing/mirroring data-flow.
- Update the ethos + folder tree for the 6-tab nav, the Plan tab, the
  tunnel libs (TunnelContext/tunnel/noiseSession/noise), the new ui
  primitives (PageHeader/Card/Tag/SectionLabel/Btn/SessionStrip/
  SessionSwitcher), and src/screens/plan.
- Add the Noise/tunnel test scripts to Commands.
- Remove the stale 'Run tab is a placeholder' note; clarify Plan renders a
  ready-to-wire fixture layer until the relay carries planning state.

Sync docs/redesign-status.md (Run/SessionStrip/font/CLAUDE.md done) and
flag PROJECT_PLAN.md's obsolete WS-server line for the planner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(redesign): mobile redesign & Plan tab — v0.2.0 tail
@kevinthelago
Copy link
Copy Markdown
Owner Author

Director triage: this branch (feature/msc-redesign) is DIRTY/diverged vs develop (ahead 20, behind 10, 64 files) and conflicts, so it cannot be merged as-is. The redesign work is now carried by the fleet mobile-redesign stream (branch mobile-redesign, issues #46-54). Please either (a) rebase feature/msc-redesign onto current develop and resolve conflicts if it still carries unique work, or (b) close this PR if the fleet mobile-redesign stream supersedes it. Holding off on merge until clarified.

@kevinthelago
Copy link
Copy Markdown
Owner Author

This branch has merge conflicts with develop and needs a rebase before it can land. The work looks good — rebase on develop, resolve any conflicts, and re-push.

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.

1 participant