Skip to content

fix(ui): align the shell to the content grid - #39

Merged
guarzo merged 2 commits into
mainfrom
fix/shell-alignment
Aug 3, 2026
Merged

fix(ui): align the shell to the content grid#39
guarzo merged 2 commits into
mainfrom
fix/shell-alignment

Conversation

@guarzo

@guarzo guarzo commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Aligns the header to the content grid, so every later screen's alignment is judged against a fixed edge rather than a moving one.

What changed

1. The header's contents now sit on the page measure. A .shell__bar wrapper inside <header class="shell"> carries the layout. The bar's ground and bottom hairline stay full-bleed — the shell is the frame the page sits in, not a thing inside it — while only the contents are measured, with padding matching .page. The seal's left edge lands on the H1's left edge and the nav's right edge lands on the content column's right edge.

2. The active nav tab is a gold underline, not an outlined box. The box was the only bordered element in the header, so it read as a button and competed with the page's one primary action — and DESIGN.md rations gold to one primary action per view plus the mark. A hairline under the label is the system's own structural device (it's what .rule-head is built from) and spends 1px of gold instead of a whole outline. It sits on a pseudo-element rather than a border, so it can't shift the item's box. aria-current="page" and the focus ring are untouched.

3. The gold wordmark on /admin/sync was a hover state, not an inconsistency. Measured computed colour is --ink on all four pages. The cause: the global a:hover (specificity 0-1-1) beats .shell__mark (0-1-0), but .shell__wordmark span sets --ink-faint on itself and blocks the second line — so half the mark flipped gold and half sat still. Both lines are now declared on .shell__mark:hover so the mark responds as one object.

4. Rule lengths audited. On /account the footnote rule stopped short of the crew-manifest table's right edge, because .footnote capped the whole block at --measure (68ch). The rule now runs the full column width; the prose keeps its 68ch cap on an inner <span>, since 68ch is a reading limit and not a structural one. That was the only mismatched hairline found.

Deviation from the spec — per-route header measure

The spec asked for both "the seal's left edge lands on the H1's left edge" (verified on all four pages) and "pages using .page--narrow keep the header at the wider 78rem measure — do not make the header width vary by route." Those can't both hold on /account, whose column is 60rem: obeying the second rule leaves the seal 144px inside the H1.

The first commit follows the explicit rule and aligns three of four pages. The second, after discussion, switches to the per-route measure so all four align. SiteHeader takes a measure?: "wide" | "narrow" prop, and --measure-page-narrow is shared by .page--narrow and .shell__bar--narrow so the two can't drift.

The measure is passed as a prop rather than derived with :has(+ .page--narrow) for the same reason current is a prop: SiteHeader stays a server component, and this avoids betting on Next not inserting a wrapper between <AdminNav /> and {children}, which would fail silently on a framework upgrade. There's also no :has() precedent in globals.css. The cost is that a future narrow page must remember the prop — noted in the component's doc comment.

The accepted trade-off: the header shifts 144px when an admin crosses between /account and /admin/*. Members only ever see /account, so the frame is fixed for almost everyone, and admins get alignment on all four screens instead of three.

Verification

Measured edges, authenticated as an admin:

[1440px] /account        {"markLeft":264,"navRight":1176,"h1Left":264}
[1440px] /admin/accounts {"markLeft":120,"navRight":1320,"h1Left":120}
[1440px] /admin/audit    {"markLeft":120,"navRight":1320,"h1Left":120}
[1440px] /admin/sync     {"markLeft":120,"navRight":1320,"h1Left":120}
[390px]  all four        {"markLeft":16,"navRight":374,"h1Left":16}

markLeft === h1Left on all four pages, at both viewports.

npm run typecheck — clean, no output.

npm run lint✖ 4 problems (0 errors, 4 warnings). All four are pre-existing @next/next/no-img-element warnings on <img> tags this PR doesn't touch.

npm run format:checkAll matched files use Prettier code style!

e2e — 8 passed (19.3s), all of e2e/account.spec.ts and e2e/admin.spec.ts.

One caveat on that last run: it is not the plain npm run test:e2e invocation. Port 3111 and the authgd_test database were both in use by a concurrent worktree, and reuseExistingServer: !CI silently serves that worktree's code while its resetDb() truncates the shared tables mid-run. I ran the same specs on port 3149 against a private authgd_test_shellalign database — only the port and DB URL differ.

Where to look

src/app/globals.css for the measure tokens and the .shell__bar / active-tab rules; src/app/_components/ui.tsx for the wrapper and the new prop. Note the spec named src/app/layout.tsx and src/app/_components/admin-nav.tsx, but SiteHeader actually lives in ui.tsxadmin-nav.tsx is just the "use client" shim that reads usePathname, and is unchanged.

Colours, type scale, spacing tokens, and the login page are untouched.

🤖 Generated with Claude Code

guarzo added 2 commits August 3, 2026 17:27
The header was full-bleed while page content sat in a centred .page column,
so the seal, the nav and the page H1 landed on three different verticals —
a violation of the system's own ruled-form premise (DESIGN.md "Layout").

- Wrap the header contents in .shell__bar, constrained to --measure-page with
  .page's horizontal padding. The bar itself stays full-bleed; only the
  contents are measured. The measure is fixed at the wide value on every
  route, so .page--narrow pages do not move the frame as you navigate.
- Introduce --measure-page, shared by .page and .shell__bar, so the two
  cannot drift apart.
- Replace the active-nav gold outlined box with a gold hairline under the
  label. The box was the only bordered element in the header, so it read as a
  button and competed with the page's one primary action. aria-current and the
  global focus ring are unchanged.
- Make the wordmark hover consistent. The gold seen on /admin/sync was the
  global a:hover leaking past .shell__mark's colour, and it reached only the
  first line because the second sets --ink-faint on itself. Both lines now
  respond together.
- Let the /account footnote hairline run the full content column so it
  terminates on the crew-manifest table's right edge; the prose keeps its 68ch
  cap on an inner span.
The shell bar was pinned to --measure-page on every route, so on /account
(a .page--narrow route at 60rem) the seal sat 144px inside the H1. Give
SiteHeader a `measure` prop that selects the matching bar variant, and
introduce --measure-page-narrow so .page--narrow and .shell__bar--narrow
share one literal.

The measure is passed rather than derived from the DOM for the same reason
`current` is: SiteHeader stays a server component, and this avoids betting
on Next's layout DOM shape with :has(+ ...).

Trade-off: the header shifts when an admin crosses between /account and
/admin/*. Members only ever see /account, so the frame is fixed for almost
everyone, and admins get alignment on all four screens instead of three.

Verified: seal.left === h1.left on /account (264), /admin/accounts,
/admin/audit and /admin/sync (120) at 1440px, and on all four (16) at
390px.
@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 17 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 69781b76-a35f-4785-8820-f13a853e1b0b

📥 Commits

Reviewing files that changed from the base of the PR and between 110525d and 7f793af.

📒 Files selected for processing (3)
  • src/app/_components/ui.tsx
  • src/app/account/page.tsx
  • src/app/globals.css

Comment @coderabbitai help to get the list of available commands.

@guarzo
guarzo merged commit 19165d9 into main Aug 3, 2026
5 checks passed
@guarzo
guarzo deleted the fix/shell-alignment branch August 3, 2026 21:53
guarzo added a commit that referenced this pull request Aug 3, 2026
Two conflicts, both the same collision: #39 reworked the standing footnote
(wrapping its prose in a span so the hairline runs the full content column
while the text stays capped at 68ch) while this branch removes the footnote
entirely, having moved that text onto the CONTACTS column header.

Resolved by taking this branch's deletion of the markup and main's improved
.footnote CSS verbatim. The rule is now unused — kept, as noted in the PR,
because PR 6 adds the closing-beat content to the empty lower page and is
the likely consumer. Discarding main's refinement to a rule we are keeping
would have thrown away work for no gain.
guarzo added a commit that referenced this pull request Aug 4, 2026
The shell bar took a `measure` prop and tracked whichever column the page
under it used, so it was 960px on /account and /payouts/new and 1248px
everywhere else. Every crossing moved the seal and the nav 144px sideways.

That trade was made in #39 and priced as admin-only, on the premise that a
member only ever sees /account. Payouts falsified it: /payouts is wide,
/payouts/new is narrow, and a plain member walks that list -> form path in
sequence, watching the chrome slide under them mid-task.

Pin the bar to --measure-page on every route and drop the prop. The page
measures are untouched: .page--narrow still holds the reading column at
60rem. The cost is that on the two narrow routes the seal sits 144px
outboard of the H1, and the nav's right edge 144px outboard of the
content's — symmetric, and less than chrome that won't hold still.

e2e/shell.spec.ts asserts the bar's width and centring are identical on all
seven routes that render one, measuring the rect rather than the absence of
a class name.
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