Web UI: chrome polish + portrait/mobile touch shell#2403
Merged
Conversation
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.htmlonly — 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-contrasttheme produced harsh cyan box borders, flat black-on-black tabs, and an invisible white-on-white "Save" button. Reworked into a deliberate visual language:color-mixof the live--bg/--fg): elevated--surfacelayers + soft--hairlinedividers, 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.--on-accentso accent-filled controls (the Save button, toggle dots, selected dropdown rows) are always legible.Portrait / mobile touch shell
Gated entirely by
body.mobile(max-width:640pxmedia 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.{ } [ ] ( ) < > ; : = ! & | / " '+ Tab) and a Save key — real key events.toggle_file_explorer,jump_to_next_error,terminal,show_lsp_status,command_palette. Status strip from the live status segments.⌨): 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 (whichpreventDefaults). Android fallback: the capture input also handlesbeforeinput(insertText / deleteContentBackward / line-breaks) for soft keyboards that reportkey="Unidentified", with a timestamp guard so iOS/desktop don't double-insert.Verification
Headless Playwright against the live bridge:
drive.mjs: 50/50 after every commit.⌨summon/dismiss; Androidbeforeinputreaches 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