Skip to content

fix(workspace): keep utility panel focus when the drawer opens in one commit - #972

Merged
blove merged 1 commit into
mainfrom
blove/gallant-clarke-963ed0
Sep 3, 2026
Merged

fix(workspace): keep utility panel focus when the drawer opens in one commit#972
blove merged 1 commit into
mainfrom
blove/gallant-clarke-963ed0

Conversation

@blove

@blove blove commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Why

main is currently blocked: the Deploy → Vercel job fails at "Verify deployed website" on the website e2e workspace-shell … and restores utility focus, which stops refs/deploy/last-promoted from advancing and skips Production smoke. Every subsequent main deploy re-verifies and re-fails until this lands. First seen on c2bc3f9 (run 33684113829), still failing on df91042.

This is a real production a11y regression, not a flaky assertion — the expected focus target is correct and was not loosened.

Root cause

Opening the tablet context surface (800px) from the rail's Activity button mounts ControlPlaneUtilityPanel and flips MobileNavOverlay to open in the same commit. The panel focuses its own <h2 tabindex="-1">; the overlay's open effect then ran focusable()[0]?.focus() — a selector that explicitly excludes tabindex="-1" — sending focus to the Close navigation button. React runs child passive effects before parent ones, so the drawer always won.

Confirmed against the deployed site: after clicking Activity, document.activeElement was button[Close navigation] with the heading present, visible, and unfocused.

Why CI's own e2e job passed on the same commit

Website — e2e runs against next dev with React StrictMode, which double-invokes effects of newly mounted components only. MobileNavOverlay stays mounted and returns null while closed, so its steal fires once while the fresh panel fires twice — the panel's second invocation lands last and masks the bug. A focus-event log on the dev server showed exactly h2[Activity]button[Close navigation]h2[Activity]. Production has no second invocation.

The fix

Claim initial drawer focus only when nothing inside the dialog already holds it. The Tab-trap registration and all other paths are untouched; when the drawer opens with no panel, focus is still on the trigger (outside the dialog), so default behavior is unchanged.

New regression tests cover the real sequence — panel mounting in the same commit that opens the drawer. The existing utility test missed this because it clicks the invoker while the drawer is already open, a commit where the overlay's state doesn't change. Both new cases fail without the fix.

Verification

  • Reproduced the exact CI failure locally (Received: inactive, same locator) under production semantics, confirmed it passes with the fix and fails again with the fix reverted — the fix is causal.
  • Full e2e against a real production build: all 13 workspace-shell tests pass, including the 5 that never ran in CI because serial mode skipped them after the failure, and lines 259–282 of the failing test which had never been exercised against production.
  • nx e2e website 85/85 · nx test workspace-react · nx test ui-react · nx test cockpit · nx lint workspace-react — all green.

🤖 Generated with Claude Code

@blove
blove enabled auto-merge (squash) September 3, 2026 00:02
@vercel

vercel Bot commented Sep 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
threadplane Ready Ready Preview Sep 3, 2026 12:15am UTC

Request Review

… commit

Opening the tablet context surface from the rail mounts the utility panel
and flips the drawer to open in the same commit. The panel focuses its own
heading, then MobileNavOverlay's open effect ran focusable()[0].focus() --
a selector that excludes tabindex="-1" -- and took focus to the Close
navigation button. Child passive effects run before parent ones, so the
drawer always won.

Dev hid this: StrictMode double-invokes effects of newly mounted components
only, and the overlay stays mounted while closed, so the panel's second
invocation landed last. The dev-server e2e job passed 85/85 while the
deploy job's verify step against the production build failed 3/3 on the
same commit, blocking refs/deploy/last-promoted from advancing.

Claim initial drawer focus only when nothing inside it already holds focus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval: this PR received an intelligent (AI) code review. See the review comments on this PR.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove force-pushed the blove/gallant-clarke-963ed0 branch from f8d39c8 to 7ef0dec Compare September 3, 2026 00:05
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @blove's task in 0s —— View job


I'll analyze this and get back to you.

@blove
blove merged commit 236aa4a into main Sep 3, 2026
31 checks passed
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