Repository navigation
Let a signup page's first Tab start at the top of the page - #672
Conversation
TabsNav kept the current tab in view on a phone with scrollIntoView, which in Chrome also moves where the next Tab starts. So on a signup's Build, Responses and Settings tabs a keyboard user's first Tab skipped the whole header and landed on the tab after the current one. The effect now sets the tab strip's scrollLeft to centre the current tab instead, which scrolls the strip the same way and leaves focus navigation alone. Fixes #644. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VpmvHcvGXzLtAcbiLtSQ12
Keeps both CHANGELOG entries under Fixed (#644 and #641). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FmK6by7GY7GPLSrVR42xZ
Tophat: passedRan c82ad8f locally with
For the owner (UX): for keyboard users, the first Tab on a signup's Build, Responses and Settings tabs now starts at the top of the page (the OpenSignup link, Help, the account link, then the header's buttons) instead of jumping past the header to the next tab. Nothing changes for mouse or touch users: on a narrow phone the current tab is still centred in the strip as before, to within a pixel. Screenshots: Screenshots are in https://claude.ai/code/session_01GSt2N12HQ5GfdLLtfoQui2. Generated by Claude Code |
richshaw
left a comment
There was a problem hiding this comment.
Review: passed (round 1)
TabsNav now centres the current tab by setting the strip's scrollLeft instead of calling scrollIntoView on the tab, so loading a signup's page no longer moves where the browser starts the next Tab from. It follows the issue's outline exactly. The strip and its tabs share an offsetParent (the strip isn't positioned), so the two offsetLefts compare. Negative values (Build) and values on a desktop where nothing overflows are clamped to 0 by the browser. The tophat measured the same scroll positions as main to within a pixel at 280px. The three new tests cover "no scrollIntoView", centring on load and re-scrolling on a tab change. Dropping the scrollIntoView stub from BuildBridge.test.tsx is safe because nothing in that tree calls it now. Every "Done when" item is met: the first Tab lands in the header on all three tabs at 1280 and 390, the current tab stays in view on a narrow phone, and CI ran the checks.
Blocking (0)
Optional (2):
src/components/signup/TabsNav.test.tsx:35-41: the first test setsElement.prototype.scrollIntoViewand deletes it at the end of the test body, so a failing assertion would leave the stub in place for the rest of the file.vi.restoreAllMocksinafterEachdoesn't undo a direct assignment. Moving the delete intoafterEach(or usingvi.stubGlobal-style setup) would make it tidy. It's harmless today, since the other tests don't call it.- The issue's "Watch for" asks about other
scrollIntoViewcalls. The two left (BuildWysiwyg.tsx:256when a slot editor opens,commit-dialog.tsx:177when an error shows) only run after the user acts, not on page load, so they don't skip anything. Nothing to do.
Earlier threads: none. Copilot couldn't review (quota used up).
Checks: CI green on c82ad8f (lint-type-test, e2e, CodeQL). I read the diff and the surrounding component locally. The tophat passed on c82ad8f, and no runtime code has changed since (shared check: 0).
Why it waits for the owner (UX): it changes what a keyboard user gets on a signup's Build, Responses and Settings tabs. The first Tab now lands on the header (OpenSignup, Help, account, then Preview and Publish and share) instead of on the next tab. Mouse and touch users see no change. The tophat's screenshots show it: 672-before-desktop-responses against 672-after-desktop-responses and 672-after-phone-responses, plus 672-after-narrow-responses / 672-after-narrow-settings for the strip still scrolled to the current tab at 280px.
Session: https://claude.ai/code/session_01QaCVc9YbQkaTfPmA9c3K1j
Generated by Claude Code
|
Approved by the owner at https://claude.ai/code/session_014LCbuCxaCSh9MudLd8Pz1H Generated by Claude Code |
Fixes #644.
The problem
On a signup's Build, Responses and Settings tabs, a keyboard user's first Tab skipped the whole header (breadcrumb, Help, account, Preview, Publish and share, ⋯, the public link's buttons) and landed on the tab after the current one.
TabsNavkeeps the current tab in view on a phone by callingscrollIntoViewon it when the page loads and when the tab changes. In Chrome that also moves where the next Tab starts from, as a click on the tab would.The fix
TabsNav(src/components/signup/TabsNav.tsx) now scrolls the strip itself. A ref on therole="tablist"element, and the effect setsstrip.scrollLeft = tab.offsetLeft - strip.offsetLeft - (strip.clientWidth - tab.offsetWidth) / 2, which centres the current tab asinline: 'center'did. The issue's outline gave this formula. The strip isn't positioned, so the strip and its tabs share anoffsetParentand the twooffsetLefts compare. On a computer, where nothing overflows, the browser clamps the value to 0 and nothing moves.BuildBridge.test.tsxno longer stubsElement.prototype.scrollIntoView, since nothing calls it now.Tests
New:
src/components/signup/TabsNav.test.tsx, with phone-sized layout stubbed in jsdom:scrollIntoViewis never called;All three failed before the fix, each for the expected reason.
Passed on
89e7c58:pnpm lint && pnpm typecheck && pnpm knip && pnpm test(1782), andpnpm build && pnpm budgets(Build at 159.4 kB, as onmain). Client-only, so nopnpm test:db.Not run here: the first Tab in a real browser. The tophat checks that at 1280 and 390.
Generated by Claude Code