hub-client: UI/UX modernization Phase 1 — component consistency - #607
Merged
Conversation
Contributor
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
shikokuchuo
force-pushed
the
hub-client-uiux-phase1
branch
2 times, most recently
from
August 26, 2026 12:23
2e9f4e4 to
61ab2e8
Compare
Single source for UI icons with a documented contract: decorative (aria-hidden), 24x24 stroke style (currentColor, stroke-width 2), size prop. Consolidates 14 icons from FileSidebar, MinimalHeader, ViewToggleControl, ProjectsHome, and ReplayDrawer; adds MoreIcon for the upcoming kebab affordance. Brand logo and the replay waveform (data viz, not an icon) stay local. Visual baselines diff clean. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
Migrates the three ad-hoc stacks ('SF Mono'…, 'JetBrains Mono'…,
'Source Code Pro'…) across 10 CSS files to var(--font-mono), adopting
the dominant JetBrains Mono stack per the phase's alignment rule.
src/debug/debug.css keeps its local token (standalone page). Visual
baselines diff clean.
Phase 1 of the UI/UX modernization (bd-iguk0hpd).
One canonical system in ui.css, two sizes: .qh-btn (variants primary/ outline/danger/ghost-accent, .small) and .qh-icon-btn (with .boxed 28x28 fixed target). Disabled and focus-visible states are now defined once on the base classes (token focus ring) instead of per-variant. MinimalHeader's ad-hoc .icon-btn becomes .qh-icon-btn.boxed with only header-context colors overridden locally. Documented boundaries: view-toggle is a segmented control, .qh-pager a nav strip, and the header's .preview-btn a filled primary pill on header tokens — none are restyled. Font sizes/weights migrate to type-scale tokens; radii and paddings await the Phase 5 grid pass. Visual baselines diff clean. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
…llery New components/Menu.tsx implements the APG menu-button pattern: arrow/Home/End navigation (wrapping, skipping disabled), type-ahead, submenus (ArrowRight/ArrowLeft, hover parity), Escape with focus return, pointer-outside close, scroll-into-view, and viewport-edge flip for fixed placement. Destructive items use danger styling and the confirm-guard rule is documented in the module header. Adopted by: FileSidebar context menu (was right-click-only divs; gains a visible hover/focus kebab button per file row — new chrome, token- styled, to be ratified at the Phase 5 gate) and all four ProjectsHome action menus (project card/row, collection, both sort menus, New). The avatar popover stays on .qh-menu styling: it contains a form and is a popover, not an action menu. Copy-feedback items (Share link, Copy project ID) keep the menu open via a keepOpen option, preserving the in-place 'copied!' feedback. Also: DevHarness grows a #/dev/gallery component gallery (buttons, icon buttons, menu, form controls, icons) covered by new visual + axe baselines, and e2e/menu-keyboard.visual.spec.ts covers the keyboard contract (9 specs). Two lint:css exceptions burned down with the deleted .context-menu block. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
New components/Tooltip.tsx implements the APG tooltip pattern: 400ms hover delay, immediate on focus, aria-describedby wiring, Escape dismiss, viewport-edge flip with horizontal clamp. Token-styled; appearance to be ratified at the Phase 5 gate. Hover handlers live on the wrapper span so disabled controls keep their tooltips. Converts every title= in the app chrome (MinimalHeader, FileSidebar, ViewToggleControl, ReplayDrawer, ProjectsHome, OutlinePanel, EphemeralSessionBanner, AboutTab, ProjectTab, JoinCollectionLanding). Not converted, deliberately: iframe title= (accessible name, not a tooltip), AST-rendered content links (document content), the classic ProjectSelector (out of scope), and src/debug tooling. Redundant titles duplicating visible text (project card/row names) were dropped; icon-only buttons gained aria-labels where the title was the only name (peek, fork, menu, pager, swatches). Also fixes a real dark-theme bug the gallery axe scan exposed: bare .qh-btn declared no color, so default buttons rendered black-on-dark (1.35:1). Remaining gallery contrast entries are token-level brand fills/accents, deferred to the Phase 5 visual gate per the plan. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
The eight ad-hoc ProjectsHome dialogs (duplicate, new/rename collection, confirm, move warning, rename project, new project, connect/import) hand-rolled role=dialog with no focus trap and no focus restoration. They now use the shared ModalDialog: header/content/actions via the shared classes (.qh-form-dialog zeroes the padding so the hairlines span), Escape closes, Tab cycles, focus restores on close. Two focus bugs fixed along the way: - ModalDialog captured its focus-restore target in a mount effect — after autoFocus children had already taken focus — so restoration targeted a detached input and focus was lost to <body>. Capture now happens during render. - Menu returned focus to its trigger synchronously on activation, stealing focus back from a dialog the item had just opened. The return is now deferred past the commit and skipped when focus is inside a dialog. Coverage: e2e/projects-home-dialogs.visual.spec.ts drives the New and Connect/Import dialogs through the real UI (menu keyboard activation, focus trap wrap, Escape/close-button with focus return). Phase 1 of the UI/UX modernization (bd-iguk0hpd).
One system, three documented tiers in components/notifications.css: transient status (.toast, auto-dismiss, bottom-center), dismissible persistent (.update-available-toast, bottom-right — persistent wins the corner so transient never covers its actions), and session banner (.ephemeral-session-banner, in-flow mode indicator, never dismissible). Toast.css and UpdateAvailableToast.css are gone; the banner rule moves out of Editor.css. Class names are unchanged (baselines/tests reference them); values are preserved, with font sizes/radii migrated to scale tokens and the redundant font-family overrides dropped (identical to the root stack). The persistent toast's dismiss button gains the token focus ring, and the fixed placements are now logical-property based (two lint:css exceptions burned down properly rather than carried). Phase 1 of the UI/UX modernization (bd-iguk0hpd).
Form controls: .qh-input gains disabled and aria-invalid states; the validation pattern is documented (field-level aria-invalid + error text via aria-describedby; form-level .qh-error.inline). The SettingsTab custom checkbox was invisible to keyboard users — the token focus ring now lands on the custom box (WCAG 2.4.7). Gallery gains disabled-input and tooltip sections. Status-indicator audit (plan item): every status now pairs color with text or label — connection (dot + Online/Offline text), renderer status (dot + label), collaborators (color dot + name), diagnostics (Monaco markers, not color-only). No changes needed beyond the Phase 1 header work; recorded here for the plan checkbox. Adds hub-client/design-system.md: the token-layer contract, the primitives table, and the how-to-add-a-component rules — the drift-prevention counterpart to lint:css. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
…clip them The sidebar is overflow-hidden, so the in-place absolute-positioned bubble was cut off at the sidebar edge — native title tooltips never clipped because the OS drew them. The bubble now renders on document.body with fixed positioning measured from the anchor (centered above, flip below, horizontal clamp), tracks the anchor on scroll and resize, and stays hidden until measured so it never flashes unpositioned. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
The tooltip wrapper spans became the header's flex items, so the buttons' flex:1 growth went inert and they clustered right (40px each starting at x=76), leaving a gap that read as a missing button. The header is now justify-content: flex-start with compact natural-width buttons. The 1% baseline tolerance masked the shift; the sidebar baseline is regenerated for the intended layout. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
Above-the-anchor placement read as awkward bars floating over the chrome; below is where native title tooltips always appeared. The bubble now centers below the anchor and flips above only at the viewport bottom; horizontal clamping is unchanged. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
Review follow-ups on the new primitives: - Clicking a disabled menu item no longer closes the menu (the click bubbled to the root closer even though the item didn't activate). Regression spec added. - The type-ahead timer is cleared on unmount, and Space no longer pollutes the type-ahead buffer (it activates the focused item). - Submenus get an accessible name via aria-labelledby on the parent item. - Elements made focusable for their tooltips (connection indicator, ephemeral banner, footer and About commit hashes) gain the token focus ring — focusable without a visible indicator fails WCAG 2.4.7. This cleared the last outline:none exception in ProjectsHome.css, burning down that lint exception. - The file context menu is keyed by file path, so right-clicking a different row remounts it — first-item focus and the viewport flip are mount-time effects and must re-run. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
The New-project menu was its only user; MenuItem's strong + subtext props cover it. Phase 1 of the UI/UX modernization (bd-iguk0hpd).
CI's E2E — interactive leg (the only place this suite runs) failed on four tests after Phase 1; all were stale expectations, plus one real small bug the staleness hid: - files-header.spec.ts asserted the sidebar header buttons fill the row at equal width — the layout the Phase 1 review fixes replaced with compact left-aligned buttons. The spec now asserts the current intent: one compact row, no wrapping, equal sizes, left-aligned at the padding edge (the original guard was against buttons wrapping to a second row). - projects-home.spec.ts located the project row's menu button by its old accessible name '⋯'; Phase 1 gave it aria-label='Actions for …'. Menu items were queried by role 'button'; the Phase 1 Menu uses the APG role 'menuitem'. - MenuSubmenu click was a toggle, but hover opens the submenu first — so a pointer click (which hovers before it clicks) closed the submenu it meant to open. Click/Enter is now open-only and focuses the first submenu item, matching the ArrowRight path and the APG pattern. Verified locally against the interactive config (VITE_E2E=1 build): files-header 2/2, projects-home 2/2, menu-keyboard + projects-home -dialogs visual specs green, component vitests green.
Two CI failures on this branch, both in the visual e2e suite: - projects-home-dialogs 'New project dialog … focus return' failed deterministically in CI (reproduced locally with the WASM build present): the menu's deferred (microtask) focus return raced the opening dialog's autoFocus and stole focus back to the trigger. Backports the Phase 2 focus-chain fix (9b91c96): Menu returns focus synchronously before the commit and activates Enter/Space explicitly (the browser's default activation click otherwise lands on the re-focused trigger); ModalDialog defers its focus restore past the commit and skips while the dialog is still connected (StrictMode's mount-time cleanup was stealing the autoFocus). - baseline-screens 'dialog-share — dark' flaked with 'Execution context was destroyed' at bootHarness's identity pin: on a cold cache the vite dev server re-optimizes dependencies and reloads the page mid-probe. The waitForFunction + evaluate pair is idempotent and now retries past the reload. No changelog entry: the focus bug is in the unreleased Phase 1 menu/dialog work this branch introduces, and the bootHarness change is test-only.
shikokuchuo
force-pushed
the
hub-client-uiux-phase1
branch
from
August 26, 2026 17:13
427eb35 to
f310c87
Compare
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.
Phase 1 of the hub-client UI/UX modernization: one canonical implementation per UI primitive. Stacked on #605 (Phase 0). New visible chrome (the file-row ⋯ button, styled tooltips, notification placement) is token-styled and gets a final review at the Phase 5 design gate.
What changes
icons.tsx, with a documented contract (decorative only, one stroke style, one size prop).--font-monotoken..qh-btn(variants primary/outline/danger/ghost-accent, plus.small) and.qh-icon-btn(plus.boxed). Disabled and focus-ring states are defined once on the base classes.Menucomponent with full keyboard support (arrow keys, Home/End, type-ahead, submenus, Escape, focus return). Used by the file-sidebar context menu and all four projects-home menus. File rows gain a visible ⋯ button, so menus are no longer right-click-only.Tooltipcomponent replaces every nativetitleattribute in the app chrome. 400ms on hover, immediate on keyboard focus, Escape dismisses.ModalDialog(focus trap, Escape, focus restoration)..qh-inputgains disabled and invalid states; one documented validation pattern; the settings checkboxes get a visible focus ring.hub-client/design-system.mddescribes the token layers, the primitives, and how to add a component.Bugs found and fixed along the way
.qh-btndeclared no text color, so default buttons were black-on-dark in the dark theme (1.35:1 contrast).ModalDialogcaptured its focus-restore target after autoFocus had already run, so closing a dialog lost focus to the page body.Verification
npm run build:allandnpm run test:cipass (1005 + 112 + 133 tests).lint:cssis clean; two more exceptions burned down.Notes
Tracks bd-2q55e6rc (Phase 1: bd-iguk0hpd, closed). Plan:
claude-notes/plans/2026-08-26-hub-client-uiux-modernization.md.