Skip to content

fix(admin): size the row drawer to the scroll region, not to the table - #72

Merged
guarzo merged 2 commits into
mainfrom
fix/drawer-wraps-to-region
Aug 4, 2026
Merged

fix(admin): size the row drawer to the scroll region, not to the table#72
guarzo merged 2 commits into
mainfrom
fix/drawer-wraps-to-region

Conversation

@guarzo

@guarzo guarzo commented Aug 4, 2026

Copy link
Copy Markdown
Owner

The bug

The accounts drawer renders as its own row — <tr class="drawer-row"><td colSpan={8}> (row-disclosure.tsx:63) — so .drawer__controls had a flex line box as wide as the accounts table. It does set flex-wrap: wrap, but a flex line box is as wide as its container, and the container was eight columns of tabular data, so wrapping never fired.

Measured at a 320px viewport:

before
scroll region 286px
accounts table 967.8px
.drawer__controls 943.8px
scroll needed to reach save note 332px

An admin who opened a row to write a note had to scroll a table 332px sideways to reach a note field with no tabular reason to be there. Pre-existing, introduced by #59 when the drawer moved out of the name cell into its own row.

The fix

An inline-size container on the scroller, with the drawer sized to that and pinned to the region's left edge:

.scroller:has(.drawer) { container-type: inline-size; }
.drawer {
  width: calc(100cqi - 2 * var(--s-3));
  position: sticky;
  left: var(--s-3);
}
.drawer__crew { min-width: 0; }

Three things worth reviewing, each measured rather than reasoned about:

  • - 2 * var(--s-3) is not cosmetic. It subtracts the drawer cell's own inline padding. A bare 100cqi overflows the cell by 24px and invents horizontal scroll on viewports that had none — at 1280px the table went 1198 → 1222 and maxScrollLeft 0 → 24. With the padding subtracted, desktop is byte-identical to before.
  • The container goes on .scroller, not .scroller-frame. .scroller carries the 1px border, so the frame is 2px wider than the scrollport; a frame-hosted container measured a 288px drawer inside a 286px region.
  • .drawer__crew { min-width: 0 } is load-bearing. Grid items floor at min-width: auto, and that item holds the crew table's own Scroller, whose min-content is the crew table (372.5px). That floor sized the drawer's column and pushed freeze 4.3px outside the region. Caught during implementation — the prototype had missed it because its variant declared container-type on every .scroller, including the nested crew one, which zeroed the contribution. Written up in the spec.

What it costs

At 320px only, the groups stack three deep: controls height 76.7 → 217.6px, and the crew list sits 141px further from the row that opened it. That is the accepted trade — vertical distance on a page that scrolls vertically anyway, against 332px of horizontal scroll inside a nested scroll region. 768px and up are unchanged.

Testing

One new test at e2e/admin.spec.ts:439, asserting properties rather than the numbers they currently produce.

It measures the pin with a 2-D area intersection, not an x-extent comparison. This matters: freeze sits with 83% of its width inside the pinned column's x-band and 0% of its area covered, so an x-only measure would report it 83% occluded. A control scrolled off the region's left edge has the same x-relationship to the pin as one buried under it.

Both vacuity modes are closed explicitly — inRegion proves the control was on screen, and a separate assertion proves a pinned cell exists at all (with none found, coveredByPin sums to 0 and the whole loop would pass against a table that had lost its pin; inRegion does not catch that).

Verification, all run on this branch:

  • npm run typecheck — clean
  • npm testTest Files 52 passed (52), Tests 430 passed (430)
  • npm run test:e2e57 passed
  • npm run lint — 0 errors (5 pre-existing <img> warnings in untouched files)
  • npm run format:checkAll matched files use Prettier code style!

Coordination with #61 — resolved

#61 (fix/pin-stays-with-drawer-open) merged while this was in flight, so this branch has been rebased onto origin/main (bb08765). What the verbatim-copy strategy actually bought, now that it has been tested rather than predicted:

  • e2e/geometry.tsno conflict, and no diff. This branch carried a byte-identical copy of coveredByPin, comment included, at the same position between pinGeometry and clearOfPin. Git collapsed it silently: the file no longer appears in this PR's diff at all, there is exactly one definition (geometry.ts:70), and admin.spec.ts imports it once. Had the copy drifted by a comment word, this would have been a hand-resolved conflict inside a helper both PRs' tests depend on.
  • e2e/admin.spec.ts — conflicted on the import block only (test(admin): measure what the pin costs an open drawer, and record it #61 added BASE_URL). Kept it. Both sets of tests survive: test(admin): measure what the pin costs an open drawer, and record it #61's "an open drawer does not widen the shared first column" (:555) and this branch's 320px test (:619).
  • src/app/globals.css — conflicted inside .drawer's leading comment. main had renamed the component reference while this branch rewrote the comment around it. Kept the expanded comment, took main's reference.

The component was renamed upstream

row-disclosure.tsx is gone; disclosure.tsx replaces it with as="details" (default, used by the sync page) and as="row" (accounts). That matters to .scroller:has(.drawer), which was written when only one component could produce a .drawer — so it was checked, not assumed: the details branch renders a bare <div id={id}> with no .drawer class (disclosure.tsx:97-109), so the container rule still applies to exactly one scroller. If a future change gives that branch the class, the scoping needs revisiting.

Re-verified after the rebase

Upstream also landed #63 (primitives), #65 (payouts) and #67 (admin denials), two of which touch admin/accounts/page.tsx — so the spec's measured figures were re-probed rather than assumed to carry over. They reproduce exactly: region 286, table 967.8125, maxScrollLeft 682, drawer 262, controls height 217.6.

npm run typecheck    clean
npm run format:check All matched files use Prettier code style!
npm run lint         5 problems (0 errors, 5 warnings)   pre-existing <img>, untouched files
npm test             64 files, 633 tests passed          scoped DB
npm run test:e2e     83 passed (1.2m)

Where to look

src/app/globals.css is the whole change; the two e2e files are the proof. The comment-to-declaration ratio in the CSS is high (58 comment lines to 6 declarations, against a 0.7:1 baseline for the file) — deliberate, since each declaration encodes a measured tradeoff that reads as arbitrary otherwise, but say the word if you'd rather they pointed at the spec instead of restating it.

Noted, not fixed

.log--sticky-col is a descendant selector, so the crew table nested inside a drawer row picks up the sticky-first-column rule too — its Name column is sticky within its own scroller, with the hairline and opaque ground that come with it. Nothing chose this, and it already cost a round of debugging in coveredByPin. This work doesn't touch those selectors, so it stays unfixed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Accounts-table drawers now wrap to the scroll region on narrow screens.
    • Drawer controls remain accessible without expanding the table or adding horizontal scrolling.
    • Drawer content stays visible and unobstructed while scrolling.
  • Bug Fixes

    • Prevented drawer content from widening tables unexpectedly.
    • Improved drawer alignment and behavior alongside pinned cells.
  • Tests

    • Added regression coverage for narrow 320px account-table drawers and multiple scroll positions.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR constrains account drawers to the scroll region, prevents nested crew content from widening the drawer, and adds 320px end-to-end checks for control reachability, horizontal scrolling, and pinned-cell overlap.

Changes

Accounts drawer layout

Layer / File(s) Summary
Drawer layout contract
docs/superpowers/specs/2026-08-04-drawer-wraps-to-region-design.md
The specification defines container-query sizing, sticky positioning, nested crew overflow handling, responsive height behavior, and unchanged sticky-column behavior.
Scroll-region drawer CSS
src/app/globals.css
Drawer scrollers use inline-size containment. Drawers size from the scrollport, align to padded cell edges, and allow nested crew content to overflow internally.
Drawer regression coverage
e2e/admin.spec.ts, docs/superpowers/specs/2026-08-04-drawer-wraps-to-region-design.md
End-to-end tests check 320px drawer sizing, control reachability, unchanged table scrolling, pinned-cell presence, and zero overlap at multiple offsets.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • guarzo/authGD#38: Both update drawer CSS and admin end-to-end coverage for 320px layouts and pinned cells.
  • guarzo/authGD#59: Both refine drawer wrapping behavior and related drawer tests.
  • guarzo/authGD#61: Both extend drawer visibility and sticky-pin regression coverage.

Poem

A rabbit checks the drawer’s width,
No scroll escapes its measured frame.
The pinned cells stay clear of it,
Each button remains in reach.
At three-twenty, the layout holds—
Hop, test, and close the case!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary drawer sizing fix.
Description check ✅ Passed The description explains the problem, solution, trade-offs, testing, coordination, and out-of-scope issue in sufficient detail.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/drawer-wraps-to-region
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/drawer-wraps-to-region

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

guarzo added 2 commits August 4, 2026 09:05
The accounts drawer renders as its own row — `<tr class="drawer-row"><td
colSpan={8}>` — so `.drawer__controls` had a flex line box as wide as the
accounts table. It wraps, but a flex line box is as wide as its container, and
the container was eight columns of tabular data. At 320px that made the control
row 943.8px inside a 286px region: `save note` sat 332px of horizontal scroll
off to the right, in a note field with no tabular reason to be anywhere but on
screen.

Give the drawer an inline-size container on the scroller and size it to that
instead, pinned to the region's left edge so it holds at every offset.

- `- 2 * var(--s-3)` subtracts the drawer cell's own inline padding. This is not
  cosmetic: a bare `100cqi` overflows the cell by 24px and invents horizontal
  scroll on viewports that had none (1280px: table 1198 -> 1222, maxScrollLeft
  0 -> 24). With it subtracted, desktop is byte-identical to before.
- The container goes on `.scroller`, not `.scroller-frame`, which is 2px wider
  than the scrollport because `.scroller` carries the border.
- `.drawer__crew { min-width: 0 }` releases the grid `min-width: auto` floor.
  The nested crew Scroller's 372.5px min-content was sizing the drawer's column,
  which pushed `freeze` 4.3px outside the region.

Costs 141px of height at 320px only, where the groups now stack three deep, on
a page that scrolls vertically anyway. 768px and up are unchanged.

The new test measures what the pin does to each drawer control with a 2-D area
intersection, not an x-extent comparison: `freeze` sits 83% inside the pinned
column's x-band with 0% of its area covered, so an x-only measure would call it
occluded. `inRegion` is asserted beside every `covered` so a zero cannot pass
for want of anything to measure.
@guarzo
guarzo force-pushed the fix/drawer-wraps-to-region branch from 3d59a54 to e2bbe4f Compare August 4, 2026 13:19
@guarzo
guarzo merged commit 5d04aec into main Aug 4, 2026
6 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