fix(ui): account page alignment, hierarchy, and demoting the destructive action - #41
Conversation
The account page is the highest-traffic member surface and PRODUCT.md describes it as a short "confirm state and leave" visit. Five mechanical fixes so it reads that way. STANDING becomes a definition grid with a fixed 6rem label column, so TIER and DISCORD share a vertical. Fixed rather than max-content: a content-sized label column re-flows every value the moment one label changes length. Tier gains hierarchy through size alone (new optional `size="lead"` on <Tier>, --t-label -> --t-data). The badge already carries its tier's hue, so this spends no additional gold against DESIGN.md's ration. Discord drops to secondary and its "linked" token goes neutral, per DESIGN.md's rule that colour is for actionable states -- a settled link is not one. The unlinked branch keeps its actionable link. Row actions right-align (.btn-row--end), so UNLINK lands on one vertical whether or not the row also carries MAKE MAIN. Left-aligned, the main character's lone UNLINK sat ~90px inboard of every other row's. UNLINK demotes to a new .btn--danger-quiet grade: neutral at rest, taking --signal-bad on row hover and on :focus-visible. Full --signal-bad on every row made the most saturated thing on the page an action members should almost never take, which reads as punishment (PRODUCT.md principle 4). Nothing is gated on hover -- the label is legible without the colour, so keyboard and touch users get the same reachable control. .btn--danger is untouched; admin's `revoke` still uses it. The standing footnote moves off the page foot and onto the CONTACTS column as an aria-describedby target, so it is reachable by keyboard rather than being a hover-only title attribute. One element serves both roles: it is visible copy above the manifest only when some row's contacts state is one the note explains, and visually-hidden otherwise, so the header's description never dangles.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 4 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
Comment |
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.
`next dev` rewrites tsconfig.json on start — it reserializes every array one-element-per-line (which prettier --check rejects, and which is why CI's format step failed), appends .next/dev/types to include, and flips jsx from "preserve" to "react-jsx". It also generates AGENTS.md. Both were picked up by a `git add -A` while resolving the merge; neither belongs in a UI change. tsconfig.json is restored to main's version. AGENTS.md is untracked again — whether to commit it or gitignore it is a repo-wide call, not this PR's.
`next dev` writes this file from node_modules/next/dist/server/lib/generate-agent-files.js on every start, including the dev server Playwright boots for the e2e suite. Untracked, it shows up as a stray `??` in every worktree and gets silently swept into unrelated PRs by `git add -A` — which is exactly what happened on #41. Committed verbatim so Next's regeneration is a no-op: it rewrites only the content between the BEGIN/END markers, and prettier --check already agrees with the formatting, so there is no reformat-then-regenerate churn. Tracking it rather than ignoring it also keeps the guidance itself under review: the block tells agents that this Next version has breaking changes and to read node_modules/next/dist/docs/ before writing code. If a future Next bump changes that text, the diff will show up in review instead of silently altering what every agent working in this repo is told.
`next dev` rewrites tsconfig.json on every start, including the dev server Playwright boots for `npm run test:e2e`. It reserializes every array with one element per line, which `prettier --check` rejects — so the file arrives unformatted through no action of the person running the tests, and the repo's own `format:check` fails on a file their change never touched. This happened on #41. Reformatting it is not a fix: the next dev run undoes it. Committing Next's version is not one either, because Next also flips "jsx" from "preserve" to "react-jsx", which changes what the compiler does — and prettier would reformat it straight back, so it churns either way. Excluding it is the only option that settles. Nothing is lost: no one hand-edits this file, and its formatting is decided by Next rather than by us. Verified by restoring the exact mangled tsconfig.json that failed CI on #41 and confirming `prettier --check .` passes with this entry in place.
The member account page is the highest-traffic surface and the one PRODUCT.md describes as a short, interruptive "confirm state and leave" session. Five mechanical fixes so it reads that way.
1. STANDING alignment
.factsbecomes a definition grid withgrid-template-columns: 6rem 1fr, so TIER and DISCORD values share a vertical. Fixed rather thanmax-contentdeliberately — a content-sized label column re-flows every value the moment one label changes length, which is the same bug arriving by another route.2. Tier hierarchy
<Tier>gains an optionalsize="lead"prop;.tier--leadscales it--t-label→--t-datawith looser padding and tracking. Hierarchy is bought with size, not colour — the badge already carries its tier's hue, so this spends nothing extra against DESIGN.md's gold ration.The prop is optional because
admin/accounts/page.tsxrenders the same component in a list, where the old size is correct.Discord drops to secondary: its
linkedtoken goes fromtone="ok"(green) to neutral, per DESIGN.md's own rule that colour is only for actionable states — a settled link is not one. The unlinked branch keeps its actionableLink Discordanchor.3. Action column
.btn-row--endright-aligns the action cell's row, so UNLINK lands on one vertical whether or not the row also carries MAKE MAIN. Left-aligned, the main character's lone UNLINK sat ~90px inboard of every other row's and the column stopped reading as a column.4. UNLINK demoted
New
.btn--danger-quietmodifier: neutral at rest, taking--signal-badon row hover and on:focus-visible. Full--signal-badon every row, always, made the most saturated thing on the page an action a member should almost never take — which reads as punishment, contradicting PRODUCT.md principle 4.Nothing is gated on hover: the label is fully legible in
--ink-faintwithout the colour, so keyboard and touch users get the same reachable, visible control..btn--dangeritself is untouched — admin'srevokestill uses it. The rule ordering is deliberate:.log tbody tr:hover .btn--danger-quietis (0,3,1) and would otherwise beat the button's own hover rule, so that rule carries the row prefix too.5. Standing footnote moved
Removed from the page foot. It now hangs off the CONTACTS column header as an
aria-describedbytarget — reachable by keyboard, not a hover-onlytitleattribute (the page has zerotitleattributes, asserted in the test).One element serves both roles: it is visible copy above the manifest only when some row's contacts state is one the note actually explains, and
visually-hiddenotherwise. That way the header's description never dangles — only its presentation changes.okandmissing_labeldon't surface it; the latter already carries more specific instructions.Shown once above the table rather than repeated per affected row: two identical four-line paragraphs inside a table column is noise, and the note is about the column as a whole.
Verification
Three new/updated e2e assertions carry the load: the two UNLINK right edges must be the same integer; UNLINK's resting colour must equal MAKE MAIN's and change on focus; and the note must flip between
table-noteandvisually-hiddenwhile the header keeps itsaria-describedby.On how that suite was run:
playwright.config.tssetsreuseExistingServer: !CI, so with concurrent worktrees a plainnpm run test:e2esilently attaches to a sibling's dev server on port 3111 and reports green without touching this branch. The run above is isolated on a private port and a dedicated Postgres container. A follow-up to remove that footgun is tracked separately.Scope notes
scope="col"was added to all six<th>s, not just Contacts — a consistency call slightly past the brief..footnoteinglobals.cssis now unused but retained; PR 6 adds the closing-beat content that may want it.🤖 Generated with Claude Code