Skip to content

fix(a11y): finish the Notice conversion in the appraise form - #137

Merged
guarzo merged 5 commits into
design-sweep/2026-08-06-4from
design-sweep/2026-08-06-5
Aug 6, 2026
Merged

fix(a11y): finish the Notice conversion in the appraise form#137
guarzo merged 5 commits into
design-sweep/2026-08-06-4from
design-sweep/2026-08-06-5

Conversation

@guarzo

@guarzo guarzo commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Round 5 of the autonomous design-sweep-loop run. Completes the cause PR #131 opened.

What this round resolved

The last &&-gated Notice in the app, and the one #131 missed. That round grepped the pattern across server pages and found six; this one is in a client component and reads {state && !state.ok && <Notice/>} rather than {message && <Notice/>}.

It is not an afterthought — it is the worst instance of the pattern. useActionState keeps AppraiseForm mounted across a rejection; that is the entire reason the component exists (appraise-form.tsx:15-25). So the tree around the notice never moves, and the && inserted a fresh role="alert" node carrying its own text into an otherwise stable document. In the server-page cases a re-render at least changed several things at once. Here the region's birth is the only change on the page, which is exactly the announcement AT handles least reliably — the case Notice's docblock (ui.tsx:255-270) was written about.

Cost to a user: an operator on a screen reader who pastes a malformed loot list into the appraise form is not reliably told the submit was rejected. The form stays put and the paste survives (by design), so nothing else on screen reports it either — the notice is the only signal, and it is the one that does not land.

Visible behaviour is unchanged. The existing comment's argument is preserved verbatim by moving the condition into the children: state === null still covers both "hasn't submitted yet" and "still pending", and neither still announces anything. Empty children render .notice-slot, which globals.css:2231-2243 positions out of flow, so the .form-stack row gap does not open up around an empty region.

After this, grep '<Notice' across src/app returns no &&-gated call site.

What was skipped

Nothing this round.

What needs a human

Unchanged from #136 and the rest of the stack. The one adjacent to this file: the payout forms still have no per-field error association (aria-invalid / aria-describedby). This PR makes the form-level rejection announceable; it does not connect a message to the field that caused it, and a screen-reader user who tabs into the ten-row textarea after the announcement has nothing tying the two together.

Gate results

Gate Result
scripts/check-node-version.sh pass
npm run typecheck pass
npm run lint pass
npm run format:check All matched files use Prettier code style!
npm run build exit 0
npm test 76 files, 1085 tests passed — identical to baseline
docker build . not run (slow, environment-bound)
npm run test:e2e not run (needs a migrated DB; known tree-dirtier)

Stack position

PR 5 of the stack, based on design-sweep/2026-08-06-4 (#136).

Merge #131#132#134#136 → this, and prefer a merge commit or rebase over squash.

The last `&&`-gated Notice in the app, and the one PR #131 missed: a grep for
the pattern across server pages found six, but this one is in a client
component and reads `{state && !state.ok && <Notice/>}` rather than
`{message && <Notice/>}`.

It is the worst instance rather than an afterthought. `useActionState` keeps
this component mounted across a rejection — that is the whole reason it exists
(see the docblock) — so the surrounding tree never moves and the `&&` inserted
a fresh role="alert" node carrying its own text into an otherwise stable
document. In the server-page cases a document-less re-render at least changed
several things at once; here the region's birth IS the only change, which is
precisely the announcement AT handles least reliably.

The visible behaviour is unchanged. The comment's argument — that `state ===
null` covers both "hasn't submitted yet" and "still pending", neither of which
is a rejection worth announcing — is preserved exactly by moving the condition
into the children. Empty children render the reserved `.notice-slot`, which is
positioned out of flow, so the form-stack row gap does not open up.

Cost to a user: an operator on a screen reader who pastes a malformed loot list
into the appraise form is not reliably told the submit was rejected. The form
stays where it is and the paste survives, so nothing else on screen reports it
either.

Gates: node-version, typecheck, lint, format:check, build, npm test
(76 files / 1085 tests) all pass. docker build and test:e2e not run.
@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: 10 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: c3b2c544-e7a5-4ccc-b4a3-4ae9b9d096de

📥 Commits

Reviewing files that changed from the base of the PR and between 1e63627 and e3be050.

📒 Files selected for processing (2)
  • src/app/login/page.tsx
  • src/app/payouts/[id]/appraise-form.tsx

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

@guarzo

guarzo commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@guarzo

guarzo commented Aug 6, 2026

Copy link
Copy Markdown
Owner Author

Carries the round-4 e2e fix by merge (c44517f93d69c7). The loot-paste label added in #136 read …quantity before or after the name, which getByLabel substring-matched against getByLabel("Name") on the same form and took out 22 payouts specs at their setup line. Reworded to "one line per item, quantity before or after"; npm run test:e2e -- payouts.spec.ts passes 57/57 locally. Full write-up in #136.

guarzo added 3 commits August 6, 2026 01:58
… it (#138)

The login seal carried alt="<brand> emblem" two lines above
<h1>{brand.name}</h1>. Every other decorative image in the app already uses
alt="" — account/page.tsx:473, account/page.tsx:681, and, most directly,
ui.tsx:117, which is the same brand mark rendered immediately before the
wordmark that spells the brand out in text. /login was the one exception.

Cost to a user: a screen-reader visitor to the only page an unauthenticated
person can reach hears the brand name, then the brand name again, before
reaching the motto, the scope disclosure, or the sign-in control. The emblem
communicates nothing the h1 does not, which is the case alt="" exists for.

The EVE SSO mark below keeps its alt: that image is the control's label, not
decoration, and nothing else names it.

The eslint-disable-next-line for @next/next/no-img-element now sits directly
above the <img> again — the new comment goes above it, not between. Inserting
it between broke the directive's adjacency and reintroduced the warning #123
cleared; lint is quoted below to show it is clean.

Gates: node-version, typecheck, lint (0 warnings), format:check, build,
npm test (76 files / 1085 tests) all pass. docker build and test:e2e not run.
@guarzo
guarzo merged commit d297388 into main Aug 6, 2026
7 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