Skip to content

Web UI: chrome polish + portrait/mobile touch shell#2403

Merged
sinelaw merged 5 commits into
masterfrom
claude/web-frontend-rendering-style-7bjt8m
Jun 21, 2026
Merged

Web UI: chrome polish + portrait/mobile touch shell#2403
sinelaw merged 5 commits into
masterfrom
claude/web-frontend-rendering-style-7bjt8m

Conversation

@sinelaw

@sinelaw sinelaw commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Resumes the experimental web frontend. It rendered correctly but looked rough, and it had no portrait/mobile story. This branch is a styling + responsive pass on web-ui/index.html only — no Rust/core changes — verified headless against the real editor bridge (Playwright, Chromium). The existing 50-assertion suite (web-ui/test/drive.mjs) stays green at desktop size throughout.

Desktop chrome polish

The native chrome piped the terminal theme's colours straight into CSS, so the default high-contrast theme produced harsh cyan box borders, flat black-on-black tabs, and an invisible white-on-white "Save" button. Reworked into a deliberate visual language:

  • Theme-derived tokens (color-mix of the live --bg/--fg): elevated --surface layers + soft --hairline dividers, so chrome reads as depth on any theme instead of hard outlines. One radius scale and one shadow/elevation language shared by menus, popups, palette and modals.
  • Hairline dividers replace the assorted hard greys and the theme border (the cyan) everywhere a structural line is drawn.
  • Inset rounded selection "pills" with a faint top-down sheen, replacing edge-to-edge highlight blocks (explorer, palette, popups, context menu, settings, keybinding table, …). Rows keep the same box selected or not, so text never shifts.
  • Bug fix: luminance-derived --on-accent so accent-filled controls (the Save button, toggle dots, selected dropdown rows) are always legible.
  • Centered floating command palette, clear active-tab indicator, quiet hover, soft focus rings, slim scrollbars, and overflow-safe list rows (the Languages JSON no longer spills).

Portrait / mobile touch shell

Gated entirely by body.mobile (max-width:640px media query) — the desktop layout is untouched. The editor keeps its cell grid; JS just reports fewer rows so the pane ends above the bottom stack, and a 36px header overlays the two hidden top chrome rows (menu+tab) so the pane lines up with no offset math. Every control routes through the real editor (sendKey / sendAction) — no mock state.

  • Header: ⚡ logo · live filename · search / run / ⌨ keyboard / ⋮ overflow.
  • Overflow sheet: Go to File, Find & Replace, Terminal, LSP Status, Settings.
  • Coding-symbol accessory row ({ } [ ] ( ) < > ; : = ! & | / " ' + Tab) and a Save key — real key events.
  • Termux-style modifier row (optional; toggle from the overflow sheet): Esc · Ctrl · Alt · Shift · arrows · Home/End. Ctrl/Alt/Shift are sticky one-shot toggles that fold into the next key from the symbol row, an arrow, or the device's native keyboard. Showing/hiding it re-fits the editor grid.
  • Bottom nav: Files / Issues / Console / LSP / Palette → toggle_file_explorer, jump_to_next_error, terminal, show_lsp_status, command_palette. Status strip from the live status segments.
  • Native-keyboard toggle (): focuses a hidden, text-free capture input to summon the OS keyboard and blurs to dismiss it, driven by an explicit intent flag. Keystrokes still flow through the global keydown handler (which preventDefaults). Android fallback: the capture input also handles beforeinput (insertText / deleteContentBackward / line-breaks) for soft keyboards that report key="Unidentified", with a timestamp guard so iOS/desktop don't double-insert.
  • File explorer, palette, and the Settings/keybinding modals become full-width sheets between the header and bottom stack.

Verification

Headless Playwright against the live bridge:

  • Desktop drive.mjs: 50/50 after every commit.
  • Mobile (390×844 @3×, touch): header/nav/symbols render; overflow sheet, Settings, Palette, full-width Files all reachable; symbol keys and Save edit through the real pipeline; modifier arming/clearing and the show/hide re-fit; summon/dismiss; Android beforeinput reaches the editor with no double-insert. Zero JS page errors throughout.

Commits are scoped one concern each (chrome polish → pills → mobile shell → modifier keys + keyboard toggle → Android fallback). Screenshots for every surface (desktop high-contrast + dark, and the full mobile shell) were shared during development.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK


Generated by Claude Code

claude added 5 commits June 19, 2026 14:49
The native chrome piped the terminal theme's colors straight into CSS, so
the default high-contrast theme produced harsh cyan box borders, flat
black-on-black tabs, and a white-on-white (invisible) primary button.
Spacing and corners were also inconsistent across surfaces.

This is a subtle craft pass, not a recolor:
- Theme-derived design tokens (color-mix of --bg/--fg): elevated --surface
  layers and soft --hairline dividers so chrome reads as depth on ANY theme
  instead of hard outlines. Re-evaluates live when applyTheme() runs.
- One radius scale (--r-sm/md/lg) and one elevation language (hairline edge
  + shadow) shared by menus, popups, palette and all modals.
- Replace the assorted hard greys and the theme border (the cyan) with a
  single low-contrast hairline for every structural divider.
- Even padding/gaps in the natively-flowed lists (kept ~1 cell tall so the
  editor's row window still aligns), quiet uniform hover, soft focus rings,
  slim rounded scrollbars, a clear active-tab indicator.
- Long composite-list values (Languages JSON) truncate to one line.
- Fix the white-on-white primary button: derive --on-accent by luminance.
- Plain command palette is now a centered floating card (position is
  cosmetic; clicks/keys still route through the editor).

Verified headless (Playwright) on high-contrast and dark themes; all 50
assertions in web-ui/test/drive.mjs still pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK
Follow-up to the polish pass, matching the reference mockups: the
edge-to-edge highlight blocks become inset, rounded "pills" with a faint
top-down sheen.

- Each scrolling list (explorer, command palette, popups, context menu,
  settings categories/items, keybinding table, dual-list, widget list)
  gets a small side gutter; every row a matching radius. Rows keep the
  same box selected or not, so text never shifts as the selection moves.
- New --sel token: a subtle linear-gradient over --menuhi for the sheen.
- Settings rows drop the per-row hairline divider in favour of spacing and
  a rounded selection, the way modern settings panes read.
- A little more vertical room between explorer rows.
- Menu dropdown highlight gently rounded to match (items stay cell-pinned).

Verified headless: 50/50 in web-ui/test/drive.mjs against a fresh bridge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK
Adds a native touch shell for narrow/portrait viewports (gated by a
max-width:640px media query via body.mobile; the desktop layout is
untouched and the 50-assertion suite still passes at 1280x800).

The editor keeps its cell grid — no reflow of the core. resize() simply
reports fewer rows so the pane ends above the bottom stack, and a 36px
header overlays the two hidden top chrome rows (menu+tab), so the pane
lines up with no offset math. Desktop cell chrome (menu/tab/status/
scrollbars/separators) is hidden and replaced with touch bars:

- Header: ⚡ logo · live filename (active tab) · search / run / overflow.
- Overflow sheet: Go to File, Find & Replace, Terminal, LSP Status,
  Settings.
- Coding-symbol accessory row ({ } [ ] ( ) < > ; : = ! & | / " ' + Tab)
  and a blue Save — sent as real key events through handle_key.
- Bottom nav: Files / Issues / Console / LSP / Palette, each routed to a
  real action (toggle_file_explorer, jump_to_next_error, terminal,
  show_lsp_status, command_palette).
- Status strip from the live status segments.
- File explorer, palette and the Settings/keybinding modals become
  full-width sheets between the header and the bottom stack.

Every control routes through the existing sendKey / new sendAction bridge,
so the editor stays the single source of truth — no mock state.

Verified headless (Playwright, 390x844 @3x, touch): header/nav/symbols
present, overflow sheet opens, Settings/Palette/Files reachable, symbol
keys and Save edit through the real pipeline, zero JS errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK
…bile)

Builds on the portrait touch shell.

Modifier / navigation row (optional; toggle from the overflow sheet,
shown by default):
- Esc · Ctrl · Alt · Shift · ← ↑ ↓ → · Home · End.
- Ctrl/Alt/Shift are STICKY one-shot toggles (Termux-style): tapping arms
  the modifier (highlighted); it folds into the NEXT key — from the symbol
  row, an arrow, OR the device's native keyboard — then clears. So tap-Ctrl
  then a native 's' sends Ctrl+S through the real handle_key.
- Showing/hiding the row re-fits the editor grid (rows recomputed) so no
  code ever hides behind the taller/shorter bottom stack.

Native soft-keyboard toggle (⌨ in the header):
- The page can't toggle the OS keyboard directly, so a hidden, text-free
  capture input is focused to summon it and blurred to dismiss it; an
  explicit intent flag drives the toggle (avoids the tap-blur race) and the
  button reflects on/off state. Keystrokes still flow through the global
  keydown handler, which preventDefaults so the input never holds text.

Verified headless (390x844 @3x, touch): modifier row renders (10 keys),
Ctrl arms and clears after one key, Shift+arrow disarms, hide toggle adds
editor rows back; ⌨ summons (focus) and dismisses (blur) with correct
active state. Desktop suite still 50/50; zero JS errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK
Some Android soft keyboards report key="Unidentified" on keydown and only
deliver the real text via beforeinput, so the mobile keyboard toggle's
capture input now also handles beforeinput:
- insertText -> each char sent as a key (folds in armed sticky modifiers)
- insertLineBreak/insertParagraph -> Enter
- deleteContentBackward/Forward -> Backspace/Delete

iOS/desktop fire a usable keydown (which cancels its default and normally
suppresses beforeinput); a short timestamp guard skips beforeinput when a
keydown was just handled, so those platforms never double-insert.

Verified headless: a synthetic beforeinput insertText reaches the editor,
deleteContentBackward maps to Backspace, and a real keydown still inserts
exactly once. Desktop suite 50/50; zero JS errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XkVw18h2cpkouakSub7HuK
@sinelaw sinelaw merged commit d2d6d1a into master Jun 21, 2026
8 checks passed
@sinelaw sinelaw deleted the claude/web-frontend-rendering-style-7bjt8m branch June 21, 2026 18:19
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.

2 participants