Skip to content

fix(ui): account page alignment, hierarchy, and demoting the destructive action - #41

Merged
guarzo merged 3 commits into
mainfrom
fix/account-page-mechanics
Aug 3, 2026
Merged

fix(ui): account page alignment, hierarchy, and demoting the destructive action#41
guarzo merged 3 commits into
mainfrom
fix/account-page-mechanics

Conversation

@guarzo

@guarzo guarzo commented Aug 3, 2026

Copy link
Copy Markdown
Owner

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

.facts becomes a definition grid with grid-template-columns: 6rem 1fr, so TIER and DISCORD values share a vertical. Fixed rather than max-content deliberately — 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 optional size="lead" prop; .tier--lead scales it --t-label--t-data with 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.tsx renders the same component in a list, where the old size is correct.

Discord drops to secondary: its linked token goes from tone="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 actionable Link Discord anchor.

3. Action column

.btn-row--end right-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-quiet modifier: neutral at rest, taking --signal-bad on row hover and on :focus-visible. Full --signal-bad on 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-faint without the colour, so keyboard and touch users get the same reachable, visible control.

.btn--danger itself is untouched — admin's revoke still uses it. The rule ordering is deliberate: .log tbody tr:hover .btn--danger-quiet is (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-describedby target — reachable by keyboard, not a hover-only title attribute (the page has zero title attributes, 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-hidden otherwise. That way the header's description never dangles — only its presentation changes. ok and missing_label don'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

$ npm run typecheck
> tsc --noEmit          (clean)

$ npm run lint
✖ 4 problems (0 errors, 4 warnings)     # identical to baseline; all pre-existing <img> warnings

$ npx playwright test
  ✓ account page shows characters, main marker, and tier
  ✓ the contacts note describes the column and only shows where it explains something
  ✓ unlink is quiet at rest and lands on one vertical with make main
  … 10 passed (17.5s)

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-note and visually-hidden while the header keeps its aria-describedby.

On how that suite was run: playwright.config.ts sets reuseExistingServer: !CI, so with concurrent worktrees a plain npm run test:e2e silently 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.
  • .footnote in globals.css is now unused but retained; PR 6 adds the closing-beat content that may want it.
  • The empty lower page is deliberately left alone — that is PR 6.

🤖 Generated with Claude Code

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.
@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: 4 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: 10dfe1af-8bd2-409b-af8d-fba886219bd1

📥 Commits

Reviewing files that changed from the base of the PR and between aeff20b and e1ddd52.

📒 Files selected for processing (4)
  • e2e/account.spec.ts
  • 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 added 2 commits August 3, 2026 17:56
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.
@guarzo
guarzo merged commit 86a162e into main Aug 3, 2026
5 checks passed
guarzo added a commit that referenced this pull request Aug 3, 2026
`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.
guarzo added a commit that referenced this pull request Aug 3, 2026
`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.
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