Skip to content

feat(account): collapse the character manifest to one status chip per row - #167

Merged
guarzo merged 9 commits into
mainfrom
worktree-manifest-density
Aug 6, 2026
Merged

feat(account): collapse the character manifest to one status chip per row#167
guarzo merged 9 commits into
mainfrom
worktree-manifest-density

Conversation

@guarzo

@guarzo guarzo commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Pull request

What changed and why

PR #162 merged six manifest columns into four to fix horizontal crowding at 10+
characters. It fixed that and made the original complaint worse: the STATUS cell
stacked three .status-line rows and the name cell gained a location line, so a
row that was one line tall became three. At ten characters the manifest went from
~10 lines of vertical space to ~30 — on a page whose stated problem was crowding.

The three lines almost always read ok / ok / on. The page was spending its
scarcest resource on saying nothing was wrong.

The rule: a row shows detail only when the member can act on it.

  • src/core/account-health.ts exports classifyCharacter, a three-way
    attention / stalled / ok taxonomy built from the needsAttention and
    isStalled predicates that were already there and stay private.
    computeAccountHealth now calls it too, so the row rendering and the
    account-level verdict cannot drift into two taxonomies. One exported classifier
    rather than two exported booleans, so callers cannot invent a fourth
    combination. No new imports — src/core/ purity is preserved.

  • The STATUS cell collapses to one chip for ok and stalled rows, and
    expands to detail lines only for attention. This is static, not a
    disclosure: no click, no client component, the manifest stays a server
    component. A stalled character keeps its own chip — sync disabled,
    sync failed — and is never shown a green ok it hasn't earned.

  • formatLocation stops printing the system name twice. EVE dock names
    already contain their system (an NPC station is <celestial> - <name> where
    the celestial begins with the system name, and players name structures after
    the hole they live in), so ${system} — ${dock} rendered
    Jita — Jita IV - Moon 4 - Caldari Navy Assembly Plant. Taking the dock's last
    - segment gives Jita — Caldari Navy Assembly Plant. The system stays
    because a J-code is the operationally useful half.

  • last seen leaves the visible string and moves to a .visually-hidden
    span, since the existing .dim treatment already carries "true, but not now"
    visually. This is the one place here where sighted users get less text than
    screen-reader users, and it is deliberate.

Net effect: roughly three lines per character down to two.

The one-line layout was measured and rejected

The design proposed putting name and location on one line, and gated it on
measurement because the name column sets the manifest's width — spending
horizontal budget in exactly the column #162 was compressing. The gate resolved
against it. At the 320px narrowest supported viewport, a flex row forced 413px of
horizontal scroll against a 286px-wide scroll region, versus ~146px stacked:
close to a full extra screen of blind scrolling to reach the STATUS and ACTIONS
columns a member came for. .char-line is therefore a stacked grid, and
e2e/account.spec.ts pins the measurement that forced the call so a future
change cannot reintroduce the flex row without re-checking it.

Accessibility

The collapsed row must not fix a sighted-density problem by taking information
away from screen-reader users, which would invert the work in #137, #150, #159
and #162.

  • A collapsed chip never overstates health — ok is shown only for the ok
    state.
  • The chip's accessible name carries all three facts (token, standings, map), so
    a screen-reader user hears what a sighted user read across three lines. This is
    how map: off stays reachable while the chip reads ok.
  • aria-describedby stays gated on hasContactRemedy, now deliberately a
    different predicate from the expand rule rather than accidentally the same one.
  • The <caption> is updated; it described a STATUS column that always showed
    three states, which is now false for most rows.

What CI cannot check

Nothing beyond CI for the logic. The layout decision rests on a measurement CI
now runs as a test rather than on a screenshot:

e2e/account.spec.ts — maxScrollLeft at 320px viewport
  flex row (rejected):  413px forced scroll
  stacked (shipped):   ~146px
  scroll region width:  286px
  gate threshold:       250px

That test asserts the location actually rendered on the row it measures and
floors maxScrollLeft above zero, so a broken seed fails loudly instead of
passing under the ceiling.

Deploy notes

None. No schema change, no migration, no new secret, no fly.toml change.

Flags

  • map: off renders a plain ok chip. This is the one place the design
    shows a green chip over a false-ish fact, and it is deliberate: there is
    nothing a member can do about map membership from this page, and
    src/core/account-health.ts:27-35 already excludes it from the account verdict
    because wanderer_acl_observation is a delete-and-replace snapshot — "off the
    ACL legitimately" and "job never ran" are the same absent row. A chip reporting
    it as a fault would raise an alarm it cannot substantiate for every member with
    an off-map alt. The fact stays readable in the chip's accessible name.

  • A player structure with an internal - (e.g. "Home - Refinery") loses
    its first segment. Accepted cost of a rule with no special cases.

  • Two deferred Minors, both judged non-blocking. (1) The — not managed /
    not yet run copy is duplicated between standingsSummary and
    contact-state.tsx; it can only drift for two cases that never render
    ContactState on a collapsed row. (2) .char-line duplicates .stack
    exactly and no selector references it — kept because the comment above it is
    the only committed home for the measurement that forced the fallback, written
    for whoever retries the flex row.

  • Out of scope, unchanged: the admin members drawer's layout. It renders
    CharacterLocation too, so the location text change reaches it — intended,
    since the redundancy was the same there.

guarzo added 9 commits August 6, 2026 16:15
Gate passed: measured against the running app (56.5px one-line baseline vs
74.3-74.8px two-line pre-change), the one-line layout holds for ten ordinary
characters and for a realistic long, unshortened structure name at the
narrowest supported viewport. No fallback needed.
…ayout's horizontal cost

Fix round 1 on c1286e3. The one-line flex row for name+location was
structurally guaranteed to pass its own vertical test (nowrap + ellipsis
already capped every string to one row), so it never measured what a long
location name actually costs: at the 320px narrowest viewport it forced
~413px of horizontal scroll before reaching STATUS/ACTIONS, against ~146px
for the stacked layout it replaced. Per the plan's documented fallback,
.char-line reverts to the same two-line stacked layout .stack already used;
two lines per character is still a win over the three this task started
from. Also fixes a vacuous location assertion, an unreproducible hardcoded
height threshold (now derived from an in-page reference row), and unscoped
table locators.
Fix round 2 on the manifest-density Task 4 fallback. The horizontal test
was the load-bearing gate but never confirmed the seed actually rendered a
location, and a broken seed would misdiagnose as "the flex row came back"
in the other tests rather than "the seed broke." Also hoists the repeated
manifest selector, adds maxScrollLeft > 0 so "nothing to scroll" can't read
as success, and clarifies two comments that overclaimed (an inert .stack
comparison, a non-independent cellWidth/regionWidth ratio check).
…n up two comments

Whole-branch review found the offline "last seen" hidden-text substitute
(Task 3) had never been exercised by any seed, so deleting it would fail
zero tests and leave a docked-offline character announced to a screen
reader as present. Adds an e2e case that seeds locationOnline: false plus
a merely-stale-but-online sibling, asserting the hidden span's own text
so a deletion of the span (not just its content) trips the test, and that
the stale sibling does NOT get the hidden text since it is genuinely
where the line says it is. Also rewrites five e2e comments pointing at
the gitignored task-4-report.md to point at the committed CSS comment
instead, and names the invariant behind the collapsed row's hardcoded
"token ok" aria-label.
@coderabbitai

coderabbitai Bot commented Aug 6, 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: 20 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d20c452c-6459-4b0f-ab43-a53601b071a7

📥 Commits

Reviewing files that changed from the base of the PR and between e9ff584 and 18ba3e5.

📒 Files selected for processing (11)
  • e2e/account.spec.ts
  • e2e/geometry.ts
  • e2e/location.spec.ts
  • src/app/_components/character-location.tsx
  • src/app/account/contact-state.tsx
  • src/app/account/page.tsx
  • src/app/globals.css
  • src/core/account-health.ts
  • src/core/location.ts
  • tests/account-health.test.ts
  • tests/location-format.test.ts

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

@guarzo
guarzo merged commit dedc867 into main Aug 6, 2026
1 check passed
guarzo added a commit that referenced this pull request Aug 7, 2026
…169)

* perf(account): tighten the manifest row from 75px to 63px

The row's height floor was never the STATUS cell — it is the NAME cell's
two lines plus cell padding. Round 1 collapsed a cell that had 30px of
slack in a 50px block, so the pitch did not move.

Replaces #167's height-band assertion with a pitch ceiling. The band
required a located row to be 10-30px taller than a no-location row, which
is two lines by definition: it passed at 75px and would pass at 95px.

* feat(account): shorten the manifest's make-main control to 'main'

89px to 50px per character column, with the verb moved into a per-character
accessible name — the same trade the unlink beside it already makes.

* feat(account): render the manifest STATUS column only on exception

On the common account every row reads ok, costing a header, a column and
82px of a 320px viewport to report the absence of news. The column now
renders when at least one character is not ok, so its presence is the
signal.

`map on|off` varies per character while the chip reads ok either way, so
the cell's accessible name was its only home; it moves to a visually-hidden
span in the NAME cell at zero vertical cost. `aria-describedby` cannot
dangle: a contact remedy implies attention or stalled, never ok, so a
remedy existing implies the column renders.

* feat(account): fold STANDING into the page head

539px of chrome sat above the first manifest row at 1440x900 — 60% of the
fold — and 171px of it was a rule-head, a two-row definition grid and a
collapsed margin holding two facts. The verdict joins the h1's line at zero
vertical cost; tier and Discord become one meta line under the lede.

The <dt>s were the only thing naming those values, so each fact keeps a
visually-hidden label. The confirm cost keeps its own line via
.page__meta-item > .confirm-cost: without it the revealed sentence
re-centres the button out from under a stationary pointer and the
pointerLeave disarms the control (#112), pinned by a boundingBox test
at 700px.

* test(account): gate the fold count at three viewports

The success criterion is characters above the fold, so assert that
directly — at 1440x900, 1280x800 and 390x844, not only the desktop case.

Re-baselines the 320px horizontal gate onto a ten-character seed. Its
previous 250px threshold came from a one-character account; a realistic
account measured 258px, so the number the gate protected had never been
taken at the size that matters.

* fix(account): post-review cleanup — dead CSS, a false caption, three stale comments

Whole-branch review of the manifest-density work (a4a292b..7c5654f) came back
SHIP with only Minors: Task 4 duplicated .facts__lead/.facts__lead >
.confirm-cost as .page__meta-item variants instead of renaming them, leaving
the originals with no consumer; the all-ok manifest caption asserted every
character is healthy even at zero characters, where showStatusColumn is
vacuously false; and three comments described a `.facts` grid or a "block
below" layout that Task 4's fold into the page head replaced.

- Remove the dead .facts__lead / .facts__lead > .confirm-cost rules and point
  every comment that cited them (confirm-submit.tsx, globals.css's discord-id
  comment) at the live .page__meta-item selectors instead.
- Give the zero-character case its own caption sentence instead of routing it
  through the all-healthy branch.
- Correct standing.tsx's `.facts` grid reference, globals.css's "block below"
  verdict comment, and the .verdict margin-top comment (dead in practice —
  every call site is inside .page__head-row, which zeroes it) to match the
  post-fold layout.
- Document that .page__meta-item > .confirm-cost's `>` combinator depends on
  ConfirmArmScope staying a pass-through with no wrapper element (the #112
  fix goes dark silently otherwise).

* test(account): pin the manifest caption's zero-character branch

The no-STATUS-column sentence is selected by `showStatusColumn`, a
`.some()` over the crew — false for an empty account as readily as for a
healthy one. The zero-character branch added in post-review cleanup was
the only user-visible copy on this branch with no test behind it;
removing it turns this test red.
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