hub-client: UI/UX modernization Phase 3 — functional states and motion safety - #611
Merged
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. |
…stic visual specs A global rule in ui.css now collapses every transition/animation to an effectively-instant 0.01ms under prefers-reduced-motion: reduce (0.01ms rather than none so transitionend/animationend listeners still fire). Global by design: any motion Phase 5 adds is covered automatically, so per-component reduced-motion blocks are banned — ReplayDrawer's local block is removed as subsumed, and theme.css's motion comment points at the rule. The visual specs now get deterministic screenshots through the app's own rule: bootHarness emulates reducedMotion: 'reduce' (also in setup-screens), subsuming Phase 0's addStyleTag transition-killer. The config-level reducedMotion option is silently ignored by Playwright 1.60's default context (verified: browser.newContext honors it, project use does not), so emulation lives in the helper; a comment in playwright.visual.config.ts records the trap. e2e/reduced-motion.visual.spec.ts pins the contract with computed-style checks (transitions on sidebar/header chrome, the toast entrance animation, iteration counts) plus no-preference counter-checks proving the assertions aren't vacuous. Phase 3 of the UI/UX modernization plan (bd-6oxpa77k).
Every async surface now has functional loading, error-with-retry, and
empty states:
- components/Loading.tsx is the one loading indicator: token-styled
spinner + label with role='status' (the spin is covered by the global
reduced-motion rule). Replaces the bare 'Loading…' text in App's auth
and identity gates, ProjectsHome's loading render, and the classic
ProjectSelector (whose local .loading class is removed; its
three-dots '...' becomes … along the way). .qh-loading/.qh-connecting
move from ProjectsHome.css to ui.css as shared classes.
- ProjectsHome's legacy project-list load no longer swallows failures
into the wrong empty copy ('No projects yet' on a failed load): a
load failure now renders an error surface with a working Try again.
- A failed project open surfaces through the connection error banner
with a Try again action (new onRetry prop); App keeps the last
open attempt and re-invokes it. The .qh-error-action pattern now
inherits banner colors (currentColor) so it works on editor-theme
error surfaces too.
- StatusTab's WASM boot error gains a Reload recovery action (onRetry
prop, defaulting to a page reload — the renderer initializes at
Editor mount, so a reload is the honest retry).
New dev-harness routes pin each state (projects-home-loading/-error/
-empty, sidebar-empty, status-tab-loading/-error) with offscreen action
recorders so specs can assert retries fire; e2e/async-states covers
copy, retry click-through, and both themes. The routes join the axe
characterization baseline (they render pre-existing chrome with
baselined contrast debt) — regenerating the baseline surfaced a real
violation: the empty file tree rendered role='tree' with zero items
(aria-required-children). The tree/listbox now drops its widget role
when empty.
Scope notes: the classic ProjectSelector's error banner keeps its
current form (dual-selector consolidation is out of scope; the
collections UI is the default), and in-editor sync loss was already
covered (header Online/Offline + auto-reconnect). Skeleton screens and
designed empty states remain Phase 5.
Phase 3 of the UI/UX modernization plan (bd-6oxpa77k).
The authoritative plan lived in .posit/assistant/plans/ (tool-local, untracked); claude-notes/plans/2026-08-26-hub-client-uiux-modernization.md was a pointer to it. The plan now lives at claude-notes/plans/2026-08-25-hub-client-uiux-modernization.md per the repo convention, the pointer is removed, and the plan header records the braid epic (bd-2q55e6rc) and current phase status. Braid references to the old path updated (bd-2q55e6rc, bd-7byucvr6).
shikokuchuo
force-pushed
the
hub-client-uiux-phase3
branch
from
August 26, 2026 17:13
54ce1ac to
4da551b
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.
Stacked on #609 (phase 2). Phase 3 of the UI/UX modernization plan.
What this change does
Phase 3 gives every async surface a working loading, error, and empty state. It also makes motion safe for users who request reduced motion.
Motion safety. A global rule in
ui.csscollapses all transitions and animations to 0.01 ms underprefers-reduced-motion: reduce. Motion added later is covered automatically. The visual specs now emulate reduced motion, so screenshots stay deterministic through this rule.Loading. A new
LoadingIndicatorcomponent (spinner plus label, announced to screen readers) replaces every bare "Loading…" text.Errors with retry. A failed project-list load now shows an error with "Try again". Before, it showed the wrong empty state. A failed project open offers "Try again" and runs the attempt again. A failed renderer boot offers "Reload".
Found by the new tests. The empty file tree declared
role="tree"with zero items. The tree now drops the widget role when it is empty.Tests
e2e/reduced-motion.visual.spec.ts(5) ande2e/async-states.visual.spec.ts(14), over six new dev-harness routes.test:ci(1251 tests),test:visual(115 specs),build:all.