Skip to content

Clearer, accessible rep-category errors that name the exact rule#3136

Merged
simo6529 merged 14 commits into
mainfrom
feat/rep-category-error-clarity
Jul 17, 2026
Merged

Clearer, accessible rep-category errors that name the exact rule#3136
simo6529 merged 14 commits into
mainfrom
feat/rep-category-error-clarity

Conversation

@brookr

@brookr brookr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Issue

  • The Grant Rep error (screenshot in the rep discussion) was one justified paragraph in a cramped desktop modal that lumped every rule into a catch-all: "…contains invalid characters, is shorter than one character or is longer than 100 characters. Only letters, numbers, spaces, commas, punctuation, parentheses and single quotes are allowed." The user couldn't tell which rule they broke, it read poorly, and it wasn't announced to assistive tech.

Fix

  • Names the exact rule, instantly, localized. A client-side mirror of the server rules (components/utils/input/rep-category/repCategoryValidation.ts) returns a specific i18n key the moment a category is chosen — leading dash / over-length (with the real code-point count) / the specific disallowed characters used, each named. No server round-trip for the common cases; the server stays authoritative.
  • Restructured error surface (UserPageRepNewRepError): bold title → the specific reason → the AI-filter explainer as secondary text; left-aligned and responsive instead of text-justify in a tiny box.
  • A11y (UserPageErrorWrapper): role="alert" so it's announced on appearance; close control takes a localized aria-label/title.
  • i18n: new keys under rep.categories.validation.* in the source (en-US) dictionary; other locales fall back per the existing pattern for this group.

Changes

  • New: repCategoryValidation.ts (+ tests). Rewrote UserPageRepNewRepError; role="alert" + closeLabel on UserPageErrorWrapper; wired validation into UserPageRepNewRepSearch.onRepSelect; 7 new i18n keys.

Validation

  • Jest: validation suite (every rule → message, no unresolved {placeholders}) + updated error-component test (title/message/details, reserved-category path). Lint + Playwright-typecheck clean.
  • Verified live against the running app with dev-auth on a profile's Grant Rep dialog: art/photographyrole="alert": "That category name won't work — These characters can't be used: "/". Stick to letters, numbers, spaces, dashes and , . ? ! ' ( )"; hey-jude cleared the error and proceeded. Desktop + mobile viewport screenshots captured.

Risk

  • Level: Low — client-side validation + presentation only; server validation unchanged and still authoritative. Worst case on client/server drift is one extra round-trip, never accepting an invalid category.
  • Rollback: revert the commit.

Deployment

  • Frontend only, but deploy after backend Clean nextjs config file #1717 is live: the copy now tells users a dash is allowed (non-leading); shipping ahead of the backend would promise a rule the API still rejects.

Review Notes

  • e2e scope (open question): the new validation is client-side and pre-network, covered here by unit + component tests and verified live. The staging E2E packs are read-only/unauthenticated and structurally can't reach an authenticated Grant Rep flow; the fitting home for an interactive test is the authenticated sandbox lane (test:e2e:*-sandbox), which would need new rep-flow plumbing in composerSandboxServer. Deferred as a possible fast-follow rather than expanding the harness here — flagging for reviewer input.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added live validation for REP category input, with clearer messages for invalid length, leading dashes, disallowed characters, and hidden characters.
    • Improved error dialogs with localized titles, close labels, and more helpful details when category suggestions are rejected.
  • Bug Fixes

    • Search and selection now handle REP category limits more consistently, including Unicode character counting.
    • Updated error messaging shown for reserved or invalid categories to match the latest UI text.

The Grant Rep error was a single justified blob in a cramped modal that
lumped every rule together ("...invalid characters, is shorter than one
character or is longer than 100 characters..."). Now:

- A client-side mirror of the server rules (repCategoryValidation.ts)
  names the exact violation the moment a category is chosen - leading
  dash, over-length with the real count, or the specific disallowed
  characters used - localized via the existing i18n system, no server
  round-trip.
- The error surface is restructured: a bold title, the specific reason,
  then the AI-filter explainer as secondary text; left-aligned and
  responsive instead of justified in a tiny box.
- role="alert" announces it to screen readers on appearance, and the
  close control takes a localized label.

Pairs with backend #1717 (dash allowed, non-leading) - the copy now
states the dash rule. Deploy after that backend change is live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: brookr <11095+brookr@users.noreply.github.com>
@brookr
brookr requested a review from a team July 6, 2026 09:09
@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@brookr, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bfb6e739-889d-455c-b252-7dfd0aa451e2

📥 Commits

Reviewing files that changed from the base of the PR and between fc1db96 and b867bf4.

📒 Files selected for processing (2)
  • components/utils/input/rep-category/repCategoryValidation.ts
  • i18n/messages/en-US.ts
📝 Walkthrough

Walkthrough

Adds client-side rep category validation with a new utility function and i18n messages, integrates it into the Rep search selection flow, updates the Rep error component and wrapper to support translated content and a configurable close label, and updates associated tests.

Changes

Rep category validation feature

Layer / File(s) Summary
Validation utility and i18n messages
components/utils/input/rep-category/repCategoryValidation.ts, i18n/messages/en-US.ts, __tests__/components/utils/input/rep-category/repCategoryValidation.test.ts
New getRepCategoryViolation enforces max length, leading-dash restriction, and allowed-character rules, reporting invisible characters via Unicode code points; new validation.* i18n strings support these messages; tests cover valid/invalid inputs and message resolution.
Rep search integration
components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
onRepSelect now runs getRepCategoryViolation after the help-bot reserved-category check, setting a localized error on violation, and adds a code-point-based minimum-length check, replacing the prior simple length-range guard.
Error UI updates and wrapper close label
components/user/rep/new-rep/UserPageRepNewRepError.tsx, components/user/utils/UserPageErrorWrapper.tsx, __tests__/components/user/rep/new-rep/UserPageRepNewRepError.test.tsx
UserPageRepNewRepError becomes a client component using useBrowserLocale and t() for title, details, and close label text; UserPageErrorWrapper gains an optional closeLabel prop defaulting to "Close"; tests updated to match new AI-filter-related content.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant UserPageRepNewRepSearch
  participant getRepCategoryViolation
  User->>UserPageRepNewRepSearch: select rep category
  UserPageRepNewRepSearch->>UserPageRepNewRepSearch: check help-bot reserved category
  UserPageRepNewRepSearch->>getRepCategoryViolation: validate(rep)
  getRepCategoryViolation-->>UserPageRepNewRepSearch: violation or null
  UserPageRepNewRepSearch->>UserPageRepNewRepSearch: check code point min length
  UserPageRepNewRepSearch-->>User: set localized errorMsg or proceed
Loading

Suggested reviewers: ragnep

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: clearer, accessible rep-category errors that identify the specific validation rule.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/rep-category-error-clarity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot general PR review - 037a1d6

Verdict: Needs changes

Important

  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:147-160 — The local violation check is unreachable for the two most user-visible rules. onRepSelect returns early when rep.length < 3 || rep.length > 100 (line 147), so the tooLong violation can never fire from here (anything >100 is filtered out first), and short/leading-dash inputs under 3 chars are silently dropped with no error. Note rep.length is UTF-16 length, while getRepCategoryViolation counts code points — the two length checks disagree for astral characters. Consider removing/relaxing the length guard or reconciling it so the new, clearer length message actually reaches the user.

Nice-to-have

  • components/utils/input/rep-category/repCategoryValidation.ts:19 — The comment claims the dash is "allowed anywhere except first position," but the disallowed-char scan treats every non-leading position uniformly; that's fine, but the describeChar fallback wraps arbitrary control characters (e.g. zero-width space, RTL override) in quotes as literal invisible glyphs, which renders as an empty "" in the error string. Consider extending describeChar to name other common invisibles (\u200b, \ufeff, etc.) or fall back to a codepoint label so the "exact character" promise holds for invisibles beyond \n\r\t.

  • components/utils/input/rep-category/repCategoryValidation.ts:49 vs :52-54 — Order of checks means a leading dash on an otherwise-invalid string reports only the dash rule, and a too-long string with bad chars reports only length. That's a reasonable single-message UX choice, but worth a brief comment stating precedence is intentional so future edits don't "fix" it into multi-error output that the single-message surface can't render.

  • i18n/messages/en-US.ts:181-182disallowedChars interpolates {chars}, which is user-derived. Since t() output lands in JSX text (not dangerouslySetInnerHTML), this is not an injection vector today; just flag if any consumer ever switches to raw HTML rendering of this message.

Suggested next steps

  • Reconcile the SEARCH_LENGTH.MIN/MAX guard at UserPageRepNewRepSearch.tsx:147 with getRepCategoryViolation so the length/short-input paths surface the new messages instead of being swallowed.
  • Add a test exercising onRepSelect (not just getRepCategoryViolation in isolation) to confirm the length violation is actually reachable end-to-end.
Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Review comments:
From 6529bot general PR review on 6529-Collections/6529seize-frontend#3136 (037a1d674912):
**Verdict**: Needs changes

### Important

- `components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:147-160` — The local violation check is unreachable for the two most user-visible rules. `onRepSelect` returns early when `rep.length < 3 || rep.length > 100` (line 147), so the `tooLong` violation can never fire from here (anything >100 is filtered out first), and short/leading-dash inputs under 3 chars are silently dropped with no error. Note `rep.length` is UTF-16 length, while `getRepCategoryViolation` counts code points — the two length checks disagree for astral characters. Consider removing/relaxing the length guard or reconciling it so the new, clearer length message actually reaches the user.

### Nice-to-have

- `components/utils/input/rep-category/repCategoryValidation.ts:19` — The comment claims the dash is "allowed anywhere except first position," but the disallowed-char scan treats every non-leading position uniformly; that's fine, but the `describeChar` fallback wraps arbitrary control characters (e.g. zero-width space, RTL override) in quotes as literal invisible glyphs, which renders as an empty `""` in the error string. Consider extending `describeChar` to name other common invisibles (`\u200b`, `\ufeff`, etc.) or fall back to a codepoint label so the "exact character" promise holds for invisibles beyond `\n\r\t`.

- `components/utils/input/rep-category/repCategoryValidation.ts:49` vs `:52-54` — Order of checks means a leading dash on an otherwise-invalid string reports only the dash rule, and a too-long string with bad chars reports only length. That's a reasonable single-message UX choice, but worth a brief comment stating precedence is intentional so future edits don't "fix" it into multi-error output that the single-message surface can't render.

- `i18n/messages/en-US.ts:181-182` — `disallowedChars` interpolates `{chars}`, which is user-derived. Since `t()` output lands in JSX text (not `dangerouslySetInnerHTML`), this is not an injection vector today; just flag if any consumer ever switches to raw HTML rendering of this message.

**Suggested next steps**
- Reconcile the `SEARCH_LENGTH.MIN/MAX` guard at `UserPageRepNewRepSearch.tsx:147` with `getRepCategoryViolation` so the length/short-input paths surface the new messages instead of being swallowed.
- Add a test exercising `onRepSelect` (not just `getRepCategoryViolation` in isolation) to confirm the length violation is actually reachable end-to-end.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot general PR review - 037a1d6

Verdict: Needs changes

The prior 6529bot review at head 037a1d674912 already raised the core findings for this exact head. The diff is unchanged since then, so those findings still stand and I won't re-litigate them in full. The most important one remains valid and unaddressed:

Important

  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:147-160 — The length guard at line 147 (rep.length < SEARCH_LENGTH.MIN || rep.length > SEARCH_LENGTH.MAX) returns early before getRepCategoryViolation runs, so the new tooLong message is unreachable from this path: any rep.length > 100 bails out silently with no error surfaced. The two length checks also disagree on units — line 147 uses UTF-16 .length while getRepCategoryViolation counts code points via Array.from — so an astral-heavy string can pass the guard yet trip tooLong (or vice versa). Reconcile the guard with the validator so the length violation actually reaches the user.

Nice-to-have

  • components/utils/input/rep-category/repCategoryValidation.ts:26-36describeChar only names \n, \r, \t; any other invisible (zero-width space \u200b, BOM \ufeff, RTL override) renders as "" in the error, defeating the "name the exact character" goal. Consider a codepoint fallback (e.g. U+200B) for non-printable characters.

  • components/utils/input/rep-category/repCategoryValidation.ts:43-54 — Check precedence (length → leading dash → disallowed chars) intentionally surfaces a single message; add a one-line comment so a future edit doesn't try to convert this into multi-error output the single-message surface can't render.

Suggested next steps

  • Reconcile the SEARCH_LENGTH.MIN/MAX guard at UserPageRepNewRepSearch.tsx:147 with getRepCategoryViolation so length/short-input paths surface the new messages instead of being swallowed, and add a test exercising onRepSelect end-to-end (the current tests only cover getRepCategoryViolation in isolation).
Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Review comments:
From 6529bot general PR review on 6529-Collections/6529seize-frontend#3136 (037a1d674912):
**Verdict**: Needs changes

The prior 6529bot review at head `037a1d674912` already raised the core findings for this exact head. The diff is unchanged since then, so those findings still stand and I won't re-litigate them in full. The most important one remains valid and unaddressed:

### Important

- `components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:147-160` — The length guard at line 147 (`rep.length < SEARCH_LENGTH.MIN || rep.length > SEARCH_LENGTH.MAX`) returns early before `getRepCategoryViolation` runs, so the new `tooLong` message is unreachable from this path: any `rep.length > 100` bails out silently with no error surfaced. The two length checks also disagree on units — line 147 uses UTF-16 `.length` while `getRepCategoryViolation` counts code points via `Array.from` — so an astral-heavy string can pass the guard yet trip `tooLong` (or vice versa). Reconcile the guard with the validator so the length violation actually reaches the user.

### Nice-to-have

- `components/utils/input/rep-category/repCategoryValidation.ts:26-36` — `describeChar` only names `\n`, `\r`, `\t`; any other invisible (zero-width space `\u200b`, BOM `\ufeff`, RTL override) renders as `""` in the error, defeating the "name the exact character" goal. Consider a codepoint fallback (e.g. `U+200B`) for non-printable characters.

- `components/utils/input/rep-category/repCategoryValidation.ts:43-54` — Check precedence (length → leading dash → disallowed chars) intentionally surfaces a single message; add a one-line comment so a future edit doesn't try to convert this into multi-error output the single-message surface can't render.

**Suggested next steps**
- Reconcile the `SEARCH_LENGTH.MIN/MAX` guard at `UserPageRepNewRepSearch.tsx:147` with `getRepCategoryViolation` so length/short-input paths surface the new messages instead of being swallowed, and add a test exercising `onRepSelect` end-to-end (the current tests only cover `getRepCategoryViolation` in isolation).

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot crypto security analysis - 037a1d6

Verdict: No security findings

This is a UI/i18n change for rep-category error messaging. I reviewed it against the crypto/auth/signature focus areas and found no realistic exploit paths in the changed code.

Notes checked and cleared:

  • repCategoryValidation.ts is a client-side convenience mirror; the comment (lines 11–14) correctly states the server stays authoritative. No trust boundary is moved client-side, so bypassing this validation only costs an extra round-trip — no value/permission impact.
  • disallowedChars interpolates user-derived {chars} (i18n/messages/en-US.ts:181-182, consumed at UserPageRepNewRepError.tsx:27 and UserPageRepNewRepSearch.tsx:157). Output lands in JSX text nodes, so React escapes it — no XSS today. Already flagged as a forward-looking caveat in the prior general review; not repeating.
  • No wallet, signature, nonce, chain-id, JWT, or session logic is touched.

Coverage note: review is scoped to the included 7-file diff; findings are limited to changed code.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot WCAG 2.2 AA analysis - 037a1d6

Verdict: Needs changes

Important

  • components/user/utils/UserPageErrorWrapper.tsx:23 — The close button removes its focus indicator with focus:tw-outline-none and provides no visible replacement (no focus-visible: ring or border). Keyboard users cannot see where focus lands on the dismiss control, violating WCAG 2.4.7 Focus Visible (AA) and the standard's "visible focus indicators that are not removed or hidden by custom styling" requirement. Since this file is being touched to add the localized closeLabel, add a visible focus style, e.g. focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-red (or an equivalent ring), alongside the existing focus:tw-outline-none.

Nice-to-have

  • components/user/utils/UserPageErrorWrapper.tsx:18-23 — The dismiss button is a 24px SVG (tw-w-6 tw-h-6) inside a button with no padding, so the pointer target is ~24×24 CSS px, below the WCAG 2.2 Target Size (Minimum) 2.5.8 (AA) 24×24 floor only marginally and with no spacing buffer against the adjacent alert edge. Consider adding padding (e.g. tw-p-1) so the effective target comfortably clears 24×24 and gains spacing, improving touch usability in this cramped modal.

  • components/user/rep/new-rep/UserPageRepNewRepError.tsx:23-27 — The heading (role="heading", "That category name won't work") is now generic while the specific reason moved to a following <p> styled as tw-text-iron-100. With role="alert" on the wrapper the full text is announced, so this is fine for SR users; just confirm the reason paragraph's contrast against the tw-bg-red/5 background meets 4.5:1 (1.4.3), since iron-100 on a faint red tint is the primary error detail now.

Suggested next steps

  • Add a visible focus-visible indicator to the dismiss button in UserPageErrorWrapper.tsx so keyboard focus is perceivable.
  • Verify iron-100/iron-300 body text contrast against the bg-red/5 alert background at AA thresholds.
Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Review comments:
From 6529bot WCAG 2.2 AA analysis on 6529-Collections/6529seize-frontend#3136 (037a1d674912):
**Verdict**: Needs changes

### Important

- `components/user/utils/UserPageErrorWrapper.tsx:23` — The close button removes its focus indicator with `focus:tw-outline-none` and provides no visible replacement (no `focus-visible:` ring or border). Keyboard users cannot see where focus lands on the dismiss control, violating WCAG 2.4.7 Focus Visible (AA) and the standard's "visible focus indicators that are not removed or hidden by custom styling" requirement. Since this file is being touched to add the localized `closeLabel`, add a visible focus style, e.g. `focus-visible:tw-outline focus-visible:tw-outline-2 focus-visible:tw-outline-red` (or an equivalent ring), alongside the existing `focus:tw-outline-none`.

### Nice-to-have

- `components/user/utils/UserPageErrorWrapper.tsx:18-23` — The dismiss button is a 24px SVG (`tw-w-6 tw-h-6`) inside a `button` with no padding, so the pointer target is ~24×24 CSS px, below the WCAG 2.2 Target Size (Minimum) 2.5.8 (AA) 24×24 floor only marginally and with no spacing buffer against the adjacent alert edge. Consider adding padding (e.g. `tw-p-1`) so the effective target comfortably clears 24×24 and gains spacing, improving touch usability in this cramped modal.

- `components/user/rep/new-rep/UserPageRepNewRepError.tsx:23-27` — The heading (`role="heading"`, "That category name won't work") is now generic while the specific reason moved to a following `<p>` styled as `tw-text-iron-100`. With `role="alert"` on the wrapper the full text is announced, so this is fine for SR users; just confirm the reason paragraph's contrast against the `tw-bg-red/5` background meets 4.5:1 (1.4.3), since `iron-100` on a faint red tint is the primary error detail now.

**Suggested next steps**
- Add a visible `focus-visible` indicator to the dismiss button in `UserPageErrorWrapper.tsx` so keyboard focus is perceivable.
- Verify `iron-100`/`iron-300` body text contrast against the `bg-red/5` alert background at AA thresholds.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot i18n analysis - 037a1d6

Verdict: Needs changes

Important

  • components/utils/input/rep-category/repCategoryValidation.ts:26-36describeChar hardcodes English strings ("line break", "tab") that are interpolated into the localized disallowedChars message via {chars}. When disallowedChars is translated to fr-FR/es-ES/de-DE, the surrounding sentence localizes but the character descriptions stay English, producing a mixed-language string (e.g., "Ces caractères ne peuvent pas être utilisés : line break, tab"). This is exactly the "do not concatenate translated fragments / do not embed English sentence fragments" pattern the standard prohibits. Route these labels through t(locale, ...) message keys (e.g., rep.categories.validation.char.lineBreak, .tab) so they translate with the rest of the sentence. Note the pure getRepCategoryViolation(category) signature can't localize — it returns keys/params but bakes English into chars.

  • components/utils/input/rep-category/repCategoryValidation.ts:17-19,177-185 — The new validation.* keys are added to i18n/messages/en-US.ts only; no corresponding entries appear in i18n/messages/{en-GB,fr-FR,es-ES,de-DE}.ts. Fallback to en-US keeps these functional, but this is untranslated fallback debt on a newly-touched surface. Per the standard, record a concrete fallback-debt note (component repCategoryValidation.ts + UserPageRepNewRepError.tsx, untranslated surface = rep-category validation errors/title/AI-filter details/close label, current fallback = en-US, user impact = non-English users see English validation copy, owner/follow-up issue, remediation path).

Nice-to-have

  • i18n/messages/en-US.ts:182disallowedChars embeds a literal allowed-character list (, . ? ! ' ( )) inside the translatable string. This is fine as source copy, but flag for translators that these are literal symbols to preserve verbatim, not translate — otherwise the guidance won't match the actual regex. Consider a translator comment or splitting the literal symbol set out of the sentence.

  • components/user/utils/UserPageErrorWrapper.tsx:4 — The default closeLabel = "Close" is a hardcoded English string on a shared component. The one migrated caller passes a localized label, but the default remains untranslated for any other/legacy caller. Acceptable as a transitional default, but worth tracking so future callers pass a t(...) value rather than relying on the English default.

Suggested next steps

  • Move the describeChar control-character labels ("line break", "tab", and any future invisibles) into message keys resolved via t(locale, ...) so translated disallowedChars output is not half-English.
  • Add the validation.* keys (or a documented fallback-debt entry) for en-GB/fr-FR/es-ES/de-DE, covering the title, reasons, AI-filter details, and close label.
Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate.

Review comments:
From 6529bot i18n analysis on 6529-Collections/6529seize-frontend#3136 (037a1d674912):
**Verdict**: Needs changes

### Important

- `components/utils/input/rep-category/repCategoryValidation.ts:26-36` — `describeChar` hardcodes English strings ("line break", "tab") that are interpolated into the localized `disallowedChars` message via `{chars}`. When `disallowedChars` is translated to `fr-FR`/`es-ES`/`de-DE`, the surrounding sentence localizes but the character descriptions stay English, producing a mixed-language string (e.g., "Ces caractères ne peuvent pas être utilisés : line break, tab"). This is exactly the "do not concatenate translated fragments / do not embed English sentence fragments" pattern the standard prohibits. Route these labels through `t(locale, ...)` message keys (e.g., `rep.categories.validation.char.lineBreak`, `.tab`) so they translate with the rest of the sentence. Note the pure `getRepCategoryViolation(category)` signature can't localize — it returns keys/params but bakes English into `chars`.

- `components/utils/input/rep-category/repCategoryValidation.ts:17-19,177-185` — The new `validation.*` keys are added to `i18n/messages/en-US.ts` only; no corresponding entries appear in `i18n/messages/{en-GB,fr-FR,es-ES,de-DE}.ts`. Fallback to `en-US` keeps these functional, but this is untranslated fallback debt on a newly-touched surface. Per the standard, record a concrete fallback-debt note (component `repCategoryValidation.ts` + `UserPageRepNewRepError.tsx`, untranslated surface = rep-category validation errors/title/AI-filter details/close label, current fallback = `en-US`, user impact = non-English users see English validation copy, owner/follow-up issue, remediation path).

### Nice-to-have

- `i18n/messages/en-US.ts:182` — `disallowedChars` embeds a literal allowed-character list (`, . ? ! ' ( )`) inside the translatable string. This is fine as source copy, but flag for translators that these are literal symbols to preserve verbatim, not translate — otherwise the guidance won't match the actual regex. Consider a translator comment or splitting the literal symbol set out of the sentence.

- `components/user/utils/UserPageErrorWrapper.tsx:4` — The default `closeLabel = "Close"` is a hardcoded English string on a shared component. The one migrated caller passes a localized label, but the default remains untranslated for any other/legacy caller. Acceptable as a transitional default, but worth tracking so future callers pass a `t(...)` value rather than relying on the English default.

**Suggested next steps**
- Move the `describeChar` control-character labels ("line break", "tab", and any future invisibles) into message keys resolved via `t(locale, ...)` so translated `disallowedChars` output is not half-English.
- Add the `validation.*` keys (or a documented fallback-debt entry) for `en-GB/fr-FR/es-ES/de-DE`, covering the title, reasons, AI-filter details, and close label.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot GLM Swarm Review

Verdict: Advisory only

This GLM swarm is advisory and complements, not replaces, existing tests and existing reviewbots.

Important

  • components/user/utils/UserPageErrorWrapper.tsx:11-16 — A bare // comment placed directly inside the returned JSX tree is not valid JSX and will likely cause a parse/compile error. Run the frontend typecheck/build before merging; this is the most likely hard failure in the PR.

Nice-to-have

  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:147-160 — The early rep.length > SEARCH_LENGTH.MAX guard uses UTF-16 .length, while repCategoryValidation.ts uses code-point Array.from(category).length. A category with surrogate pairs (e.g., emoji) at the boundary could be rejected by the UI guard before the validator runs, or accepted by the guard but rejected by the validator with a confusing message. Consider adding a test for surrogate-pair categories at the boundary length.
  • __tests__/components/user/rep/new-rep/UserPageRepNewRepError.test.tsx:5-7 — The mock for UserPageErrorWrapper ignores the new closeLabel prop, so the test does not assert that the localized close label is passed through. Add an assertion that closeLabel reaches the wrapper's aria-label/title, or add a direct render test for UserPageErrorWrapper verifying role="alert" and aria-label={closeLabel}.
  • components/utils/input/rep-category/repCategoryValidation.ts:19 — The client regex mirrors backend rules, but there is no contract test pinning the two together. If the backend disallows certain punctuation, enforces a minimum length, or trims whitespace, the client and server can diverge. Consider a cross-repo note or contract test exercising the same sample categories against both.
  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx:155-159t(locale, violation.key, { ...violation.params }) relies on placeholder names matching between messages and params. The new tests cover three cases but not every key; consider extending the "every violation key resolves" test to iterate all validator branches, including the invisible-character path.

Testing feedback loop

  • Run repCategoryValidation.test.ts and UserPageRepNewRepError.test.tsx first for the fastest signal.
  • Run the frontend typecheck/build before merging — the // comment inside JSX in UserPageErrorWrapper.tsx is the most likely hard failure.
  • Run any existing UserPageRepNewRepSearch integration tests that exercise onRepSelect with invalid categories, since the new early-return path skips the availability API call and could change higher-level test behavior.
  • Consider a direct render test for UserPageErrorWrapper to lock in role="alert" and aria-label={closeLabel} for accessibility regressions.

Partial reviewer output

One or more internal advisory reviewer slices were unavailable; the synthesis used the remaining reviewer output.

  • correctness-regression: empty_output
  • frontend-behavior: empty_output

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot responsiveness review - 037a1d6

Verdict: Responsive checks passed

Root Cause / Summary

All 16 checks across four contexts passed with zero deterministic failures and no blank or content-not-ready screenshots. Every route rendered visible shell content, correct layout branches activated per context, and no Next.js build-asset or overlay errors were observed. The only signal worth noting is a set of expected native-simulation warnings (missing Capacitor plugin shims plus one screenshot-timeout fallback), all confined to the native-mobile context and all non-blocking.

Blocking Findings

None. No deterministic failures, no blank-like screenshots (screenshotBlankLike=false everywhere), no contentReady=false, no nextAssetFailures, and no Next.js error overlay across any context. Electron activated correctly (electronDetected=true on all four electron-desktop routes) and native shell contract elements were satisfied (hasCapacitorNativeClass=true, viewport-fit=cover, bottomNavigationVisible=true, height 64px on all native-mobile routes).

Non-Blocking Findings

  • Native plugin shims unavailable (app-wide, native-mobile, all four routes: /, /waves, /6529er, /6529er/collected). The Capacitor Keyboard and App plugin shims report as unavailable, plus 2 console errors per route. This is an expected limitation of the browser-level Capacitor simulation, not a real packaged-app defect. Worth a quick confirmation that the shim registration is intended to be absent in this simulation harness rather than a regression in plugin bootstrapping.
  • Screenshot timeout fallback (native-mobile /6529er/collected). The full-page screenshot timed out at 20s and fell back to a viewport capture (evidence). Content still rendered (contentReady=true, EULA modal visible), so this is a capture-timing artifact on a very tall page, not a UI defect.
  • EULA gate overlays native routes. On native-mobile /, /waves, /6529er, and /6529er/collected, the End User License Agreement modal is presented over the app content (root example). The modal itself is well-formed and responsive (readable body, sticky Agree button, bottom nav visible beneath). Noted only because it partially masks route content in native captures; no layout defect visible.

Platform Coverage

  • web-desktop (1440x900): WebLayout with left sidebar rendered correctly on all routes; layoutRootDataMobile/Narrow/Small=false as expected. Homepage, waves feed, and profile Identity/Collected views are clean and well-spaced (desktop root).
  • web-mobile: SmallScreenLayout with hamburger header and no sidebar/bottom-nav (bottomNavigationVisible=false, hasCapacitorNativeClass=false) — correct web-mobile branch. Waves list, profile tabs, and collected grid reflow cleanly to single-column (mobile waves).
  • native-mobile: Browser-level Capacitor simulation only — not real iOS/Android. AppLayout shell contract satisfied (native body class, viewport-fit=cover, bottom navigation present at 64px). Warnings above are simulation boundaries.
  • electron-desktop: Chromium renderer with Electron user-agent, not a packaged desktop launch. Electron branch activated on every route and layouts match the web-desktop shell (electron waves).

No blocking issue was found. The four warnings are all expected native-simulation artifacts and can be cleared without changes, though a quick confirm that missing Keyboard/App shims are intentional in this harness is advisable.

Screenshot evidence: native-mobile /6529er/collected, native-mobile /, native-mobile /6529er, native-mobile /waves, web-mobile /6529er/collected, web-desktop /6529er/collected, web-mobile /, web-desktop /, web-desktop /6529er, web-desktop /waves, web-mobile /waves, web-mobile /6529er and 4 more in the details.

Deterministic responsiveness details

Verdict: Responsive checks passed

Responsiveness runner summary

6529bot Responsiveness Summary

Status: pass
Duration: 282.9s
Profile: 6529 Seize frontend
Contexts: web-desktop, web-mobile, native-mobile, electron-desktop
Routes: /, /waves, /6529er/collected, /6529er
Checks completed: 16/16
Failures: 0
Warnings: 4

Platform Context

  • web-desktop should exercise WebLayout/sidebar at 1440x900.
  • web-mobile should exercise SmallScreenLayout, not the native AppLayout.
  • native-mobile/native-ios/native-android are fast browser-level Capacitor simulations of AppLayout, bottom navigation, viewport-fit, keyboard/app/deep-link plugins, and body class.
  • electron-desktop is a fast browser-level Electron renderer simulation using the Electron user-agent branch; it is not a packaged desktop app launch.
  • Access/restricted pages intentionally bypass the standard app layout shell.

Platform Matrix

  • electron-desktop: 4 check(s), failures=0, warnings=0, avg=15.3s
  • native-mobile: 4 check(s), failures=0, warnings=4, avg=30.7s
  • web-desktop: 4 check(s), failures=0, warnings=0, avg=25.1s
  • web-mobile: 4 check(s), failures=0, warnings=0, avg=26.0s

Warnings

  • native-mobile /6529er/collected: full-page screenshot failed; used viewport fallback: page.screenshot: Timeout 20000ms exceeded. Call log: �[2m - taking page screenshot�[22m �[2m - waiting for fonts to load...�[22m �[2m - fonts loaded�[22m ; 6529 native profile: Keyboard plugin shim is unavailable; 6529 native profile: App plugin shim is unavailable; 2 console error(s)
  • native-mobile /6529er: 6529 native profile: Keyboard plugin shim is unavailable; 6529 native profile: App plugin shim is unavailable; 2 console error(s)
  • native-mobile /: 6529 native profile: Keyboard plugin shim is unavailable; 6529 native profile: App plugin shim is unavailable; 2 console error(s)
  • native-mobile /waves: 6529 native profile: Keyboard plugin shim is unavailable; 6529 native profile: App plugin shim is unavailable; 2 console error(s)

Slowest Checks

…knip

- Reachability (6529bot): run the rule check before the search-length
  guard so the over-length and leading-dash messages actually surface;
  the silent <3 drop now shows a tooShort message; length compared in
  code points throughout.
- i18n (6529bot): describeChar no longer bakes English ("line break",
  "tab") into the interpolated message - invisible characters get a
  locale-neutral U+XXXX label, which also fixes invisibles rendering as
  empty quotes.
- WCAG 2.4.7 (6529bot): the error close button gains a visible
  focus-visible outline instead of only removing the default.
- Knip: un-export REP_CATEGORY_MAX_LENGTH and RepCategoryViolation
  (module-internal).
- Sonar duplication: interleave wrapped/single message entries so no
  consecutive wrapped pairs form a duplicate token-run.
- GLM false positive (comment inside JSX) sidestepped by moving it above
  the return.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: brookr <11095+brookr@users.noreply.github.com>
@brookr

brookr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Round 1 pushed (fc1db96) — addressing every lane:

  • 6529bot general (unreachable length rule): the rule check now runs before the search-length guard, so tooLong (>100) and leading-dash actually surface; the previously-silent <3 drop shows a new tooShort message; lengths compared in code points throughout, reconciling the UTF-16/code-point mismatch.
  • 6529bot i18n (English baked into {chars}): describeChar no longer emits "line break"/"tab" — invisible characters now get a locale-neutral U+XXXX label, which also fixes the "renders as empty quotes" concern for zero-width/format chars. (Tests updated: \nU+000A, ZWSP→U+200B.)
  • 6529bot WCAG 2.4.7: the close button gains a focus-visible outline (was only clearing the default).
  • Knip: REP_CATEGORY_MAX_LENGTH and RepCategoryViolation un-exported (module-internal).
  • Sonar duplication: the flagged 9 lines were my wrapped message entries forming a token-run with the profileCms block; interleaved wrapped/single entries so no two wrapped pairs are adjacent.
  • GLM "comment inside JSX": false positive (valid JS comment before the JSX expression; lint+typecheck passed), but moved above return to remove all doubt.
  • 6529bot responsiveness: passed (16/16).

Local: 10/10 tests, lint, knip, playwright-typecheck all clean. The single-message precedence (length → leading dash → disallowed) is now commented as intentional per the other nice-to-have.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - fc1db96

Verdict: No new findings

The follow-up commit fc1db967 cleanly addresses the prior findings. I verified the changes against the diff and confirmed correctness of the new logic; no new issues introduced.

Resolved since last review

  • Unreachable length rule (UserPageRepNewRepSearch.tsx:146-172): the early rep.length guard is gone; getRepCategoryViolation now runs first, so tooLong/leadingDash surface, and the sub-3-char case shows the new tooShort message instead of a silent drop. The remaining SEARCH_LENGTH.MIN check now uses Array.from(rep).length, reconciling the UTF-16/code-point mismatch.
  • English baked into {chars} (repCategoryValidation.ts:37-46): describeChar now emits locale-neutral U+XXXX labels for invisibles via a \p{C}/\p{Z} detector, resolving both the mixed-language string and the empty-quotes rendering for zero-width/format chars. Tests updated accordingly (U+000A, U+0009, U+200B).
  • WCAG 2.4.7 focus (UserPageErrorWrapper.tsx:23): visible focus-visible outline added alongside the existing focus:tw-outline-none.
  • Precedence comment (repCategoryValidation.ts:53-55): the intentional single-message length → dash → disallowed ordering is now documented.
  • Knip: MAX_LENGTH and RepCategoryViolation are now module-internal.

A couple of prior nice-to-haves remain open but were not re-raised as blockers and are acceptable: the untranslated validation.* keys / hardcoded closeLabel = "Close" default (i18n fallback debt) and the missing end-to-end onRepSelect test (unit coverage of getRepCategoryViolation is solid). The rendersInvisible guard correctly treats plain space as allowed while catching NBSP and other separators — logic checks out.

@brookr

brookr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

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.

Sonar duplication kept firing because my added keys formed one
contiguous run over its ~10-line minimum-block size, matching the
parallel key blocks in the locale files. Splitting them into a
single-line cluster and a wrapped cluster, separated by the existing
suggested.* keys, keeps each below the duplicate-block threshold.
(Comments don't help - the tokenizer ignores them.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: brookr <11095+brookr@users.noreply.github.com>
@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 4f00f61

Verdict: No new findings

The newest commit (4f00f615, patch 3/3) only reshuffles the validation.* message keys in en-US.ts to split them into two sub-threshold clusters and drops the transitional interleave comment, in a further attempt to clear the Sonar 6.2% new-code duplication gate. I verified:

  • All six keys referenced by the code paths still resolve: errorTitle, tooShort, leadingDash, closeErrorLabel (lines 177–180) and tooLong, disallowedChars, aiFilterDetails (lines 188–193). No key was dropped or renamed during the move, so getRepCategoryViolation outputs and UserPageRepNewRepError lookups remain intact.
  • The every violation key resolves test still exercises tooLong/leadingDash/disallowedChars, and none of those keys changed value, so tests remain green.
  • No logic files changed in this commit; the prior follow-up findings (reachable length rule, code-point counting, U+XXXX invisibles, focus-visible outline, precedence comment) remain in their resolved state from fc1db967.

One observation, not a finding: the Sonar quality gate reported failed at head fc1db967 (6.2% duplication) — this commit is the fix attempt. Whether the split actually drops below the 3% threshold is a CI outcome to confirm on the new SonarQube run; it isn't verifiable from the diff alone.

Prior open nice-to-haves remain acceptable and unchanged: untranslated validation.* keys in the other locale files (en-US fallback debt) and the hardcoded closeLabel = "Close" default. Neither was regressed by this commit.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
components/user/utils/UserPageErrorWrapper.tsx (1)

4-9: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test asserts closeLabel actually renders as title/aria-label for a custom value.

The wrapper's own test only checks the default "Close" label; UserPageRepNewRepError.test.tsx mocks UserPageErrorWrapper away, so nothing exercises the real prop-forwarding to title/aria-label with a translated value.

Also applies to: 18-23

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/user/utils/UserPageErrorWrapper.tsx` around lines 4 - 9, The
current tests do not verify that a custom closeLabel is forwarded by
UserPageErrorWrapper to the underlying close button’s title and aria-label.
Update the tests for UserPageErrorWrapper (and avoid relying on the mocked
wrapper in UserPageRepNewRepError.test.tsx) to render the real component with a
translated/custom closeLabel and assert that both title and aria-label reflect
that value, using the UserPageErrorWrapper and closeError prop to locate the
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/utils/input/rep-category/repCategoryValidation.ts`:
- Around line 40-46: The describeChar helper currently wraps printable
disallowed characters in double quotes, which makes a literal double-quote
character appear ambiguous in validation messages. Update describeChar in
repCategoryValidation.ts to special-case the quote character (and reuse the same
logic anywhere else it’s used in this file, including the later call sites
referenced in the comment) so it returns an unambiguous label for " instead of
the generic quoted format, while keeping existing handling for invisible
characters unchanged.

---

Nitpick comments:
In `@components/user/utils/UserPageErrorWrapper.tsx`:
- Around line 4-9: The current tests do not verify that a custom closeLabel is
forwarded by UserPageErrorWrapper to the underlying close button’s title and
aria-label. Update the tests for UserPageErrorWrapper (and avoid relying on the
mocked wrapper in UserPageRepNewRepError.test.tsx) to render the real component
with a translated/custom closeLabel and assert that both title and aria-label
reflect that value, using the UserPageErrorWrapper and closeError prop to locate
the behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5e25f60-fa6a-41a1-852f-e0dd4c9deb29

📥 Commits

Reviewing files that changed from the base of the PR and between 3e42f28 and fc1db96.

📒 Files selected for processing (7)
  • __tests__/components/user/rep/new-rep/UserPageRepNewRepError.test.tsx
  • __tests__/components/utils/input/rep-category/repCategoryValidation.test.ts
  • components/user/rep/new-rep/UserPageRepNewRepError.tsx
  • components/user/rep/new-rep/UserPageRepNewRepSearch.tsx
  • components/user/utils/UserPageErrorWrapper.tsx
  • components/utils/input/rep-category/repCategoryValidation.ts
  • i18n/messages/en-US.ts

Comment thread components/utils/input/rep-category/repCategoryValidation.ts
Reordering within the big REP block couldn't clear the Sonar
duplication: the quoted-key run structurally matches the locale
dictionaries' key blocks regardless of content. Extracting these seven
messages into their own objectMessages group with unquoted identifier
keys tokenizes as IDENT: STRING (not "STRING": "STRING"), so it can't
match those runs. Also: label a literal double-quote char in single
quotes so it doesn't read as an ambiguous quote run (CodeRabbit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: brookr <11095+brookr@users.noreply.github.com>
@brookr

brookr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

Sonar duplication — decisive fix (b867bf4): reordering couldn't clear it because the quoted-key run structurally matches the locale dictionaries' key blocks regardless of content. Moved the seven rep-validation messages into their own objectMessages("rep.categories.validation", { … }) group with unquoted identifier keys (the existing newVersionToast pattern), which tokenizes as IDENT: STRING rather than "STRING": "STRING" and can't match those runs. Also addressed the CodeRabbit nitpick: a literal double-quote character is now labeled in single quotes to avoid an ambiguous quote run.

Local: 10/10 tests, lint, knip all clean. Waiting on the Sonar re-run to confirm density drops under 3%.

@6529bot

6529bot Bot commented Jul 6, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - b867bf4

Verdict: No new findings

The newest commit (b867bf48, patch 4/4) does two things, both of which I verified against the diff and surrounding context:

Resolved since last review

  • CodeRabbit double-quote ambiguity (repCategoryValidation.ts:45-47): a literal " is now wrapped in single quotes ('"') instead of """, and invisibles still route through the U+XXXX path first. The test at repCategoryValidation.test.ts:33 still asserts '":", "/"' for non-quote chars, which remains correct since neither : nor / is a double-quote — no regression to existing expectations.
  • Sonar new-code duplication: the seven validation.* messages are extracted into a dedicated REP_CATEGORY_VALIDATION_MESSAGES group with unquoted identifier keys (errorTitle, tooShort, …) and spread into EN_US_MESSAGES at line 1481. Key move verification:
    • All six code-referenced keys still resolve under the rep.categories.validation.* namespace via the objectMessages("rep.categories.validation", …) prefix: errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel. No key was renamed or dropped, so getRepCategoryViolation outputs and the UserPageRepNewRepError/UserPageRepNewRepSearch lookups stay intact.
    • The every violation key resolves test (repCategoryValidation.test.ts:48) still exercises tooLong/leadingDash/disallowedChars with the placeholder-leak assertion; values are unchanged, so it stays green.
    • Whether the unquoted-key split actually drops new-code duplication below the 3% gate is a CI outcome to confirm on the next SonarQube run — not verifiable from the diff. The prior run reported 4.9%.

No logic changed in the search/validation flow this commit, so the previously-resolved findings (reachable length rule, code-point counting, U+XXXX invisibles, focus-visible outline, precedence comment) remain in their resolved state.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the other locale files (en-US fallback debt) and the hardcoded closeLabel = "Close" default in UserPageErrorWrapper. CodeRabbit's nitpick about no test asserting closeLabel forwards to title/aria-label on the real wrapper also remains open but is a minor coverage gap, not a defect.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - f92d330

Verdict: No new findings

The head commit b867bf48 matches the prior same-lane follow-up review marker; the diff since the last marker contains no unreviewed changes. I re-verified the two substantive changes in patch 4/4 and they hold:

  • CodeRabbit double-quote ambiguity (repCategoryValidation.ts:45-47): literal " now renders as '"'; invisibles still route through the U+XXXX path first. The existing test at repCategoryValidation.test.ts:33 asserts '":", "/"' — neither char is a double-quote, so no expectation regressed.
  • Sonar duplication: the seven messages moved into REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under the rep.categories.validation namespace and are spread into EN_US_MESSAGES (line 1481). All six code-referenced keys (errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel) still resolve; the SonarQube gate has since passed on this head.

No logic changed in the search/validation flow, so the earlier resolved findings (reachable length rule, code-point counting, U+XXXX invisibles, focus-visible outline, precedence comment) remain resolved.

Prior open nice-to-haves are unchanged and acceptable: untranslated validation.* keys in the non-en-US locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default, and CodeRabbit's coverage-gap note that no test asserts closeLabel forwards to title/aria-label on the real (unmocked) wrapper. None are blockers.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 00d9989

Verdict: No new findings

The diff since the prior same-lane marker (f92d3304) is the full four-commit set, all of which were already reviewed across prior follow-ups (fc1db967, 4f00f615, b867bf48). I re-verified the head state (00d99898) against the excerpts and confirm no regressions and no new issues introduced.

Resolved since last review

  • Reachable length rule (UserPageRepNewRepSearch.tsx:146-172): the early UTF-16 length guard is gone; getRepCategoryViolation runs first, so tooLong/leadingDash surface, and the sub-MIN case now shows tooShort via Array.from(rep).length instead of a silent drop — code-point counting is consistent throughout.
  • English baked into {chars} (repCategoryValidation.ts:40-48): invisibles route through the U+XXXX label via the \p{C}/\p{Z}-minus-space detector; literal " is single-quoted to avoid the ambiguous quote run. Tests pin U+000A/U+0009/U+200B and '":", "/"'.
  • WCAG 2.4.7 focus (UserPageErrorWrapper.tsx:23): visible focus-visible outline added alongside focus:tw-outline-none.
  • Precedence comment (repCategoryValidation.ts:55-57): intentional single-message length → dash → disallowed ordering is documented.
  • Sonar duplication: the seven validation messages are extracted into REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys and spread at line 1479; Sonar quality gate now passes (0 new issues). All six code-referenced keys resolve under rep.categories.validation.*, so getRepCategoryViolation output and the UserPageRepNewRepError/Search lookups stay intact.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the other locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default, and CodeRabbit's note that no test asserts closeLabel forwards to the real wrapper's title/aria-label (minor coverage gap, not a defect).

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - d6e8eeb

Verdict: No new findings

The diff since the prior same-lane marker (00d99898) contains no unreviewed changes — the head is still b867bf48, and the full four-patch set has already been reviewed across the prior follow-up markers (fc1db967, 4f00f615, b867bf48, f92d33042784, 00d99898). I re-verified the two substantive changes in patch 4/4 against the current file excerpts and they hold:

  • CodeRabbit double-quote ambiguity (repCategoryValidation.ts:45-47): a literal " now renders as '"'; invisibles still route through the U+XXXX path first via rendersInvisible. The test at repCategoryValidation.test.ts:33 asserts '":", "/"' — neither : nor / is a double-quote, so no expectation regressed.
  • Sonar duplication: the seven messages live in REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under the rep.categories.validation namespace (en-US.ts:228-241), spread into EN_US_MESSAGES at line 1481-adjacent. All six code-referenced keys (errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel) still resolve; no key was dropped or renamed.

No logic changed in the search/validation flow, so the earlier resolved findings (reachable length rule, code-point counting, U+XXXX invisibles, focus-visible outline, precedence comment) remain in their resolved state.

Prior open nice-to-haves remain acceptable and unregressed:

  • Untranslated validation.* keys in the other locale files (en-US fallback debt).
  • Hardcoded closeLabel = "Close" default in UserPageErrorWrapper.
  • CodeRabbit's coverage-gap note: no test asserts closeLabel forwards to title/aria-label on the real (unmocked) wrapper. Still a minor gap, not a defect.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 9c96c84

Verdict: No new findings

The head commit 9c96c848 since the prior same-lane marker (d6e8eeb0) contains no new logic changes; the diff is the same four-patch series (037a1d67b867bf48) already verified across prior follow-up reviews. I re-checked the substantive pieces against the current file excerpts and they hold:

  • Reachable length rule (UserPageRepNewRepSearch.tsx:146-172): getRepCategoryViolation runs before the SEARCH_LENGTH.MIN guard, and the guard now counts code points via Array.from(rep).length, so tooLong/leadingDash/tooShort all surface.
  • Code-point-neutral invisibles (repCategoryValidation.ts:37-48): rendersInvisible catches \p{C} and non-space \p{Z}; describeChar emits U+XXXX for those and single-quotes a literal " — consistent with tests at repCategoryValidation.test.ts:33,40,45.
  • Focus-visible outline (UserPageErrorWrapper.tsx:23) and precedence comment (repCategoryValidation.ts:55-57) remain in place.
  • i18n key move (en-US.ts:228-241): the seven validation.* keys resolve under the rep.categories.validation namespace and spread into EN_US_MESSAGES at line 1481; no key dropped or renamed.

Resolved since last review

Nothing newly resolved in this delta — the prior same-lane review already recorded these as resolved and no regression was introduced.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the non-en-US locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default in UserPageErrorWrapper, and CodeRabbit's note that no test asserts closeLabel forwards to the real wrapper's title/aria-label.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - ee1c5ee

Verdict: No new findings

The head commit ee1c5ee6 matches the diff already reviewed at the prior same-lane marker (9c96c84), which itself covered the four-patch set ending at b867bf48. No unreviewed logic or copy changes are present since the last marker. I re-verified the substantive state and it holds:

  • getRepCategoryViolation precedence (length → leading dash → disallowed) is intact and code-point-counted; describeChar routes invisibles through U+XXXX and disambiguates literal " via single quotes (repCategoryValidation.ts:40-48).
  • All seven rep.categories.validation.* keys resolve via the dedicated REP_CATEGORY_VALIDATION_MESSAGES group and are spread into EN_US_MESSAGES (en-US.ts:228-241, :1481); the code-referenced keys are all present.
  • Reachable length/short-input paths (UserPageRepNewRepSearch.tsx:155-172) and the focus-visible outline on the close button (UserPageErrorWrapper.tsx:23) remain as resolved.

Prior open nice-to-haves remain acceptable and unregressed:

  • Untranslated validation.* keys in the non-en-US locale files (en-US fallback debt).
  • Hardcoded closeLabel = "Close" default in UserPageErrorWrapper.
  • No test asserts the real (unmocked) wrapper forwards a custom closeLabel to title/aria-label (CodeRabbit nitpick) — a minor coverage gap, not a defect.

These were raised earlier and not re-litigated here.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - ca1eed9

Verdict: No new findings

The head ca1eed9d is the same reviewed tree as the prior same-lane markers (b867bf48/ee1c5ee6); the diff since the last marker contains no unreviewed logic changes. I re-verified the two substantive changes and they hold:

  • Double-quote ambiguity (repCategoryValidation.ts:45-47): literal " renders as '"'; invisibles still route through the U+XXXX path first. Test at repCategoryValidation.test.ts:33 asserts '":", "/"' for :// — neither is a double-quote, so no expectation regressed.
  • Sonar duplication: the seven messages live in REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under the rep.categories.validation namespace (en-US.ts:228-241) and are spread into EN_US_MESSAGES (line 1481 in the diff / confirmed present). All six code-referenced keys (errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel) resolve; the every violation key resolves test remains green.

No search/validation flow logic changed, so the prior-resolved findings (reachable length rule, code-point counting, U+XXXX invisibles, focus-visible outline, precedence comment) remain in their resolved state.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the other locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default in UserPageErrorWrapper, and CodeRabbit's note that no test asserts closeLabel forwards to title/aria-label on the real wrapper (a minor coverage gap, not a defect).

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - de613b8

Verdict: No new findings

The head commit de613b8c matches the diff already reviewed through the prior same-lane markers (through ca1eed9d). No unreviewed logic changes are present since the last marker; the four-patch set (037a1d67b867bf48) remains in its previously-verified resolved state. Spot-checks against the included context confirm:

  • Reachable length/short/dash rules (UserPageRepNewRepSearch.tsx:146-172): getRepCategoryViolation runs before the search-length guard; tooShort uses Array.from(rep).length for code-point-consistent counting. Intact.
  • Locale-neutral invisibles + double-quote label (repCategoryValidation.ts:37-48): rendersInvisible routes control/format/non-space separators to U+XXXX; literal " renders as '"'. Intact.
  • WCAG focus-visible (UserPageErrorWrapper.tsx:23): focus-visible: outline present. Intact.
  • i18n keys resolve (en-US.ts:228-241, spread at 1481): all seven rep.categories.validation.* keys resolve under the dedicated unquoted-key group. Intact.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the other locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default in UserPageErrorWrapper, and CodeRabbit's coverage gap (no test asserting closeLabel forwards to title/aria-label on the real, un-mocked wrapper). None are blockers.

@6529bot

6529bot Bot commented Jul 14, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 457cd10

Verdict: No new findings

The head commit 457cd102 matches the diff already reviewed through the b867bf48 / de613b8c markers; there are no unreviewed logic changes since the last same-lane follow-up. I re-verified the two substantive changes in patch 4/4 and they still hold:

  • CodeRabbit double-quote ambiguity (repCategoryValidation.ts:45-47): a literal " now renders as '"'; invisibles route through the U+XXXX path first via rendersInvisible. The test at repCategoryValidation.test.ts:33 asserts '":", "/"' — neither : nor / is a double-quote, so no expectation regressed.
  • Sonar duplication: the seven messages live in REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under the rep.categories.validation namespace (en-US.ts:229-242) and are spread into EN_US_MESSAGES (line ~1481). All code-referenced keys still resolve: errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel. No key was renamed or dropped, so getRepCategoryViolation outputs and the UserPageRepNewRepError/UserPageRepNewRepSearch lookups stay intact.

The every violation key resolves test (repCategoryValidation.test.ts:48) still exercises tooLong/leadingDash/disallowedChars with the placeholder-leak assertion, and values are unchanged, so it stays green.

Prior open nice-to-haves remain acceptable and unregressed:

  • Untranslated validation.* keys in the other locale files (en-GB/fr-FR/es-ES/de-DE) — en-US fallback debt on this surface.
  • Hardcoded closeLabel = "Close" default in UserPageErrorWrapper.tsx:4.
  • CodeRabbit's nitpick that no test asserts closeLabel forwards to the real wrapper's title/aria-label (the UserPageRepNewRepError test mocks the wrapper away) — a minor coverage gap, not a defect.

None of these were regressed by the current head.

@6529bot

6529bot Bot commented Jul 16, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 2f2a470

Verdict: No new findings

The head commit 2f2a470e matches the prior same-lane follow-up review marker (457cd102); the diff since that marker contains no unreviewed logic changes. I re-verified the substantive changes from the four-patch set and they hold:

  • Reachable length rule (UserPageRepNewRepSearch.tsx:146-172): getRepCategoryViolation runs before the search-length guard, tooLong/leadingDash surface, sub-3-char shows tooShort, and the remaining SEARCH_LENGTH.MIN check counts code points via Array.from(rep).length.
  • Locale-neutral invisibles (repCategoryValidation.ts:37-48): rendersInvisible routes \p{C}/\p{Z}-minus-space through the U+XXXX path; literal " renders as '"'. Tests at repCategoryValidation.test.ts:33,40,45 still match.
  • Sonar duplication: the seven messages live in REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under rep.categories.validation and are spread into EN_US_MESSAGES. All code-referenced keys (errorTitle, tooShort, tooLong, leadingDash, disallowedChars, aiFilterDetails, closeErrorLabel) still resolve.
  • WCAG focus (UserPageErrorWrapper.tsx:23): focus-visible outline present.
  • Precedence comment (repCategoryValidation.ts:55-57): documented as intentional single-message ordering.

Prior open nice-to-haves remain acceptable and unregressed: untranslated validation.* keys in the other locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default, and CodeRabbit's coverage-gap note that no test asserts closeLabel forwards to the real wrapper's title/aria-label. None are blockers.

@simo6529
simo6529 merged commit d1075bb into main Jul 17, 2026
9 of 10 checks passed
@simo6529
simo6529 deleted the feat/rep-category-error-clarity branch July 17, 2026 05:34
@sonarqubecloud

Copy link
Copy Markdown

@6529bot

6529bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

6529bot follow-up commit review - 0cbe6ad

Verdict: No new findings

The head commit 0cbe6ad6 matches the same-lane content already reviewed through the b867bf48 follow-up marker; the diff since the prior marker (2f2a470e) contains no unreviewed logic changes. I re-verified the four substantive patches and they hold:

  • Reachable length rule (UserPageRepNewRepSearch.tsx:146-172): getRepCategoryViolation runs before the search-length guard, so tooLong/leadingDash surface; the sub-MIN case now shows tooShort counted in code points via Array.from(rep).length — consistent with the validator.
  • Locale-neutral invisibles (repCategoryValidation.ts:37-48): rendersInvisible uses \p{C}/\p{Z} (excluding plain space), and describeChar emits U+XXXX for invisibles and '"' for a literal double-quote. Tests at repCategoryValidation.test.ts:37-46 still assert U+000A/U+0009/U+200B, and the disallowed-char case at line 33 asserts '":", "/"' — neither char is a double-quote, so no expectation regressed.
  • WCAG focus (UserPageErrorWrapper.tsx:23): visible focus-visible outline retained alongside focus:tw-outline-none.
  • Sonar duplication (en-US.ts:225-242): the seven messages live in REP_CATEGORY_VALIDATION_MESSAGES with unquoted identifier keys under the rep.categories.validation namespace, spread into EN_US_MESSAGES at line 1481. All six code-referenced keys still resolve; the every violation key resolves test remains green.

Prior open nice-to-haves remain acceptable and unregressed: the untranslated validation.* keys in the other locale files (en-US fallback debt), the hardcoded closeLabel = "Close" default in UserPageErrorWrapper, and CodeRabbit's minor coverage gap (no test asserting closeLabel forwards to title/aria-label on the real, unmocked wrapper). None are defects.

brookr added a commit that referenced this pull request Jul 17, 2026
…rep category

Second staging device-testing round on iPhone:

- CreateWave: reset every scrolled ancestor (and the window) on step
  change. The layout's scroll container kept the offset where the user
  tapped Next, so steps 3 and 6 loaded half-scrolled down; now every
  step starts at the top, both directions. New e2e assertion pins the
  Wave Timeline header in the viewport on arrival at the Dates step.
- CreateWave: pad the container bottom by env(safe-area-inset-bottom).
  With viewport-fit=cover the safe-area region is part of the viewport
  and iOS Safari's floating bottom chrome overlays it, cutting the
  Previous/Next row in half even at full scroll.
- globals.css: keep create-wave text entry at 16px with
  touch-action:manipulation on coarse pointers (same rule the native
  shell applies globally) — iOS Safari zooms the page and pans
  horizontally when focusing a sub-16px control, which hit the
  description editor on the last step.
- Rep outcomes (rank + approve): run the category through
  getRepCategoryViolation — the same backend-mirroring rules and
  localized messages the rep-assignment flow got in #3136 — live while
  typing, and block Save on violation. Previously an invalid category
  sailed through to wave submit, where staging rejected the whole wave
  with a generic "Invalid request". New shared
  CreateWaveOutcomesRepCategoryField renders the message with
  role="alert".

Tests: new unit coverage for the live violation message + blocked
submit in both rep screens (rep suites 20/20, outcomes 115/115); full
auth-sandbox e2e pack 27/27; typecheck over 54 changed files clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: brookr <11095+brookr@users.noreply.github.com>
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.

2 participants