Mobile: repo tab bar truncates with no scroll affordance (Releases/Settings unreachable-looking) #274

Closed
opened 2026-09-10 11:18:30 +00:00 by crueber · 3 comments
Owner

What's wrong

At a phone viewport (390×844), the repo tab bar (Code / Commits / Issues / Pulls / Checks / Releases / …) overflows: measured scrollWidth 515 vs clientWidth 358 — the trailing tabs (Releases, Settings, Wal) are cut off mid-letter with no scroll affordance (no visible scrollbar, no fade, no wrap). On the releases page the tab row visually truncates at "R…" (screenshot evidence). The user cannot tell more tabs exist, and reaching them requires an invisible horizontal pan of the page (the whole page also scrolls: page docW 531 vs 390).

Measured evidence

repo tabs: scrollWidth 515, clientWidth 358 (390px viewport, px-4 page padding)
pages affected: /tree, /blob, /commits, /issues, /pulls, /checks, /releases, /settings — every repo tab page

Root cause (code evidence)

  • web/src/pages/Repo.jsx:541-551 — the tab <nav class="repo-tabs mb-4 flex gap-1 border-b …"> is a plain flex row with no overflow-x, no wrap, no scroll snap. Nine tabs × (label + px-3 py-1.5 + gap) exceed 390px.
  • Because the nav itself doesn't scroll, the overflow propagates to the page root (repo pages measure docW 530-531 vs 390).

Fix direction

  • Make repo-tabs horizontally scrollable on small screens: overflow-x: auto with scrollbar-width: none-style hiding plus a visible affordance — edge fade masks (gradient) or scroll-snap — so truncation is discoverable. Keep the active tab scrolled into view on navigation (scrollIntoView({inline:'center'}) when activeTab changes).
  • Optionally collapse to a select/hamburger on very narrow screens; the scroll approach is smaller and keeps one-tap access.
  • Acceptance: at 390px every tab is reachable by scrolling the tab bar only; the active tab is always fully visible after navigation; page scrollWidth === clientWidth (tab overflow no longer leaks to the page).
## What's wrong At a phone viewport (390×844), the repo tab bar (Code / Commits / Issues / Pulls / Checks / Releases / …) overflows: measured `scrollWidth 515 vs clientWidth 358` — the trailing tabs (Releases, Settings, Wal) are cut off mid-letter with **no scroll affordance** (no visible scrollbar, no fade, no wrap). On the releases page the tab row visually truncates at "R…" (screenshot evidence). The user cannot tell more tabs exist, and reaching them requires an invisible horizontal pan of the page (the whole page also scrolls: page `docW 531 vs 390`). ## Measured evidence ``` repo tabs: scrollWidth 515, clientWidth 358 (390px viewport, px-4 page padding) pages affected: /tree, /blob, /commits, /issues, /pulls, /checks, /releases, /settings — every repo tab page ``` ## Root cause (code evidence) - `web/src/pages/Repo.jsx:541-551` — the tab `<nav class="repo-tabs mb-4 flex gap-1 border-b …">` is a plain flex row with no `overflow-x`, no wrap, no scroll snap. Nine tabs × (label + px-3 py-1.5 + gap) exceed 390px. - Because the nav itself doesn't scroll, the overflow propagates to the page root (repo pages measure `docW 530-531 vs 390`). ## Fix direction - Make `repo-tabs` horizontally scrollable on small screens: `overflow-x: auto` with `scrollbar-width: none`-style hiding **plus** a visible affordance — edge fade masks (gradient) or scroll-snap — so truncation is discoverable. Keep the active tab scrolled into view on navigation (`scrollIntoView({inline:'center'})` when `activeTab` changes). - Optionally collapse to a select/hamburger on very narrow screens; the scroll approach is smaller and keeps one-tap access. - Acceptance: at 390px every tab is reachable by scrolling the tab bar only; the active tab is always fully visible after navigation; page `scrollWidth === clientWidth` (tab overflow no longer leaks to the page).
Author
Owner

Fixed by #297 (branch fix/issue-274, ready for review — not merged): repo tab bar scrolls internally below the breakpoint (#273 site-nav treatment — overflow-x-auto strip, hidden strip scrollbar, shrink-0 links, active tab scrolled into view, keyboard/Tab intact, dark + light). node --test 570/570 green, vite build green. Browser proof open (shared-daemon loopback guard).

Fixed by #297 (branch fix/issue-274, ready for review — not merged): repo tab bar scrolls internally below the breakpoint (#273 site-nav treatment — overflow-x-auto strip, hidden strip scrollbar, shrink-0 links, active tab scrolled into view, keyboard/Tab intact, dark + light). node --test 570/570 green, vite build green. Browser proof open (shared-daemon loopback guard).
Author
Owner

Review of PR #297 (fix/issue-274, repo tab bar scrolls on narrow) — verified in scratch worktree at origin/fix/issue-274 (98462d1).

What the change does (4 files, +136/-1, no Go, no package.json):

  • web/src/pages/Repo.jsx: nav gains max-w-full overflow-x-auto whitespace-nowrap; new createEffect scrolls [aria-current=page] into view on navigation.
  • web/src/ui.css: .repo-tabs scrollbar hidden both engines + .repo-tabs a shrink-0.
  • web/test/unit/repo-tabs-narrow.test.js: 6 source-text regression pins.
  • docs/go/12_web_ui.md: decision appended (law 12 satisfied).

Review against the brief:

  1. Containment (web/src/pages/Repo.jsx:602, web/src/ui.css:58-60): PASS. nav is itself the scroll container (overflow-x-auto + max-w-full + nowrap, no flex-wrap) so 515px of tab content scrolls inside the 358px strip instead of leaking to the page root. No min-w-0 needed here: unlike the #273 site-nav (a flex-1 row item), this nav's immediate parent div.repo-shell (Repo.jsx:538) is a plain block and main#app (App.jsx:80) is a column-flex item, so no row-flex min-width:auto hazard.
  2. scrollIntoView effect (Repo.jsx:511-518): PASS. {block:"nearest", inline:"center"} — centers in strip, never moves page vertically. Non-DOM guarded two ways (tabsNav?.querySelector?., typeof el.scrollIntoView check). No scroll wars with hash navigation: effect tracks only location.pathname, and activeTab (lib/tabs.js:46-51) strips ?/# before matching, so settings#wal anchor jumps don't retrigger it.
  3. Active-tab visibility incl. deep-link load: PASS. Effect runs on mount (post-commit, ref set, links rendered synchronously) and on every pathname change; activeTab always resolves (falls back to code) so aria-current=page is always present to find. On wide screens with no overflow the call is a no-op.
  4. Keyboard flow: PASS. Links stay native anchors; hidden scrollbars (scrollbar-width:none / ::-webkit-scrollbar display:none) hide chrome only — Tab focus still walks the strip and browsers follow focus natively. aria-label landmark + aria-current styling untouched.
  5. Model/deps/themes: PASS. TABS const (Repo.jsx:160-170) and lib/tabs.js untouched; createEffect/activeTab were already imported (no new imports, no new deps — law 1 holds); classes are layout-only + theme-independent CSS, active style keeps its dark: variants.

Nits (not blocking, no fix pushed): the id && guard (Repo.jsx:515) is dead but harmless — activeTab never returns falsy. inline:center recenters even when the tab is already fully visible; harmless (no-op without overflow).

Verification (scratch worktree only, no browser per instructions — browser proof remains open as the PR itself notes):

  • node --test web/test/unit/repo-tabs-narrow.test.js: 6/6 pass.
  • Full node --test web/test/unit/*.test.js: 570/570 pass, 0 fail (note: fresh worktree initially showed 8 unrelated failures from missing web/node_modules — Cannot find package marked; resolved by linking main's node_modules into scratch only, then 570/570 green, matching the doc claim).
  • vite build green (1.84s; chunk-size warning is pre-existing) + esbuild SDK bundle green; compiled CSS carries all 3 .repo-tabs rules. No Go files touched so gofmt/vet unaffected.

MERGE RECOMMENDATION: ready to merge.

Review of PR #297 (fix/issue-274, repo tab bar scrolls on narrow) — verified in scratch worktree at origin/fix/issue-274 (98462d1). What the change does (4 files, +136/-1, no Go, no package.json): - web/src/pages/Repo.jsx: nav gains max-w-full overflow-x-auto whitespace-nowrap; new createEffect scrolls [aria-current=page] into view on navigation. - web/src/ui.css: .repo-tabs scrollbar hidden both engines + .repo-tabs a shrink-0. - web/test/unit/repo-tabs-narrow.test.js: 6 source-text regression pins. - docs/go/12_web_ui.md: decision appended (law 12 satisfied). Review against the brief: 1. Containment (web/src/pages/Repo.jsx:602, web/src/ui.css:58-60): PASS. nav is itself the scroll container (overflow-x-auto + max-w-full + nowrap, no flex-wrap) so 515px of tab content scrolls inside the 358px strip instead of leaking to the page root. No min-w-0 needed here: unlike the #273 site-nav (a flex-1 row item), this nav's immediate parent div.repo-shell (Repo.jsx:538) is a plain block and main#app (App.jsx:80) is a column-flex item, so no row-flex min-width:auto hazard. 2. scrollIntoView effect (Repo.jsx:511-518): PASS. {block:"nearest", inline:"center"} — centers in strip, never moves page vertically. Non-DOM guarded two ways (tabsNav?.querySelector?., typeof el.scrollIntoView check). No scroll wars with hash navigation: effect tracks only location.pathname, and activeTab (lib/tabs.js:46-51) strips ?/# before matching, so settings#wal anchor jumps don't retrigger it. 3. Active-tab visibility incl. deep-link load: PASS. Effect runs on mount (post-commit, ref set, links rendered synchronously) and on every pathname change; activeTab always resolves (falls back to code) so aria-current=page is always present to find. On wide screens with no overflow the call is a no-op. 4. Keyboard flow: PASS. Links stay native anchors; hidden scrollbars (scrollbar-width:none / ::-webkit-scrollbar display:none) hide chrome only — Tab focus still walks the strip and browsers follow focus natively. aria-label landmark + aria-current styling untouched. 5. Model/deps/themes: PASS. TABS const (Repo.jsx:160-170) and lib/tabs.js untouched; createEffect/activeTab were already imported (no new imports, no new deps — law 1 holds); classes are layout-only + theme-independent CSS, active style keeps its dark: variants. Nits (not blocking, no fix pushed): the id && guard (Repo.jsx:515) is dead but harmless — activeTab never returns falsy. inline:center recenters even when the tab is already fully visible; harmless (no-op without overflow). Verification (scratch worktree only, no browser per instructions — browser proof remains open as the PR itself notes): - node --test web/test/unit/repo-tabs-narrow.test.js: 6/6 pass. - Full node --test web/test/unit/*.test.js: 570/570 pass, 0 fail (note: fresh worktree initially showed 8 unrelated failures from missing web/node_modules — Cannot find package marked; resolved by linking main's node_modules into scratch only, then 570/570 green, matching the doc claim). - vite build green (1.84s; chunk-size warning is pre-existing) + esbuild SDK bundle green; compiled CSS carries all 3 .repo-tabs rules. No Go files touched so gofmt/vet unaffected. MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #297 (review clean; 570/570 node tests), merged. Closing.

Fixed by PR #297 (review clean; 570/570 node tests), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#274
No description provided.