a11y(inbox): label-first accessible names on the capture composer after moving its test selectors to testids - #2700
Conversation
… by accessible name The Composer's textarea, board select, label input and due input were reached by their `aria-label` from `PaperInboxView.spec.ts` (47 selectors) and from three Playwright specs. That made the four accessible names test API: they had to keep their pre-extraction English byte for byte through the #1871 catalog extraction, and two of them therefore still fail WCAG 2.5.3 label-in-name. Each control gains a stable `data-testid` in the composer's existing `paper-composer-*` naming, and every selector that read a name moves onto it. No accessible name, no catalog value and no behaviour changes in this commit, so the unchanged suites passing IS the proof that the migration is inert. The route-affordance inventory's source pin for `inbox.capture-submit` moves from PaperCaptureComposer.vue:369 to :373, the four added attribute lines having pushed the Capture button down. The E2E line pins are unaffected: each spec had exactly one line replaced in place. Refs #1871
…ur accessible names With the specs off the accessible names (previous commit), the four Composer names are copy again and take the WCAG 2.5.3 shape the board controls and the triage board-pick already use (PR #2675): the visible eyebrow first, then what the control does. bodyAria Capture body -> Body: write the text of this capture labelsAria Add label -> Labels: type a label and press Enter to add it dueAria Due date -> Due (optional): set a due date for this capture composerAria Board picker -> Board: choose where this new capture will land Two of them failed label-in-name outright before this: "Add label" did not contain the visible "Labels" and "Due date" did not contain "Due (optional)". The two board names now differ only in their second half, which is the only part a screen-reader user hears as a distinguisher, so the Composer's says "this new capture" (an unfiled draft) against the triage row's "this capture". es and it are rewritten to the same shape. Their composer docblocks also stop claiming the placeholders stay lowercase: that is true of labelsPlaceholder only, bodyPlaceholder is a capitalized sentence by design in all three locales, and the wrong rule invited a native reviewer (#1770) to "fix" it. They now mirror the English rule instead - a hint about the shape of the value, never the field's name. Tests. The composer spec asserts the label-first RELATION, not only the four literals: every accessible name must start with the eyebrow rendered above its control, so a future reword cannot silently break the rule. Each component also gains one post-mount locale switch, because the existing Italian cases set the locale before mounting and would pass against a non-reactive t() snapshot. Red-first, before the catalogs changed: 4 failures in PaperCaptureComposer.spec.ts - "renders the field chrome in English on the default locale", "starts every field accessible name with the visible eyebrow above it", "re-renders the field chrome in Italian when the locale switches" and the new post-mount case. The es/it wording is this lane's, not native-reviewed (OUTSTANDING_TASKS #1770). Refs #1871
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Review record (alpha product-trust lane, review-and-ship round 1 at head Reviewer: one fresh-context independent reviewer subagent (read-only; Codex credits exhausted, SC-9), given the worktree at the head and the merge-base diff. Verdict: SHIP, no merge-blocking items; two MEDIUMs fixed in round 2 because both are copy and specs this PR introduced. Lenses that found nothing: label-in-name holds in all three locales (each accessible name starts with the exact eyebrow rendered above it, compared key by key); no remaining selection by the old names in unit or E2E specs and no runtime selector keyed on the aria-label; the four testids are unique and reach the right elements ( Findings, triaged once:
Round count: 2 after the fix push. Merge gate: |
…the label-first rule in every locale Round 2 of PR #2700, two MEDIUMs and one LOW from the fresh-context review. MEDIUM 1 -- product truth. `inbox.boardPicker.composerAria` said "Board: choose where this new capture will land". Nothing lands on a board from here. Every capture lands in Inbox; the board choice LINKS the capture so triage can propose against that board, and nothing reaches the board without approve and execute (ADR-0003). The composer's own footer and `nib.destinationWithBoard` already say so, so the name contradicted the surface it sits on. All three locales are rewritten to the linking form, still label-first: en Board: choose which board this capture is linked to for triage es Tablero: elige a qué tablero se vincula esta captura para el triage it Bacheca: scegli a quale bacheca collegare questa cattura per il triage The es and it wording reuses each catalog's own triage vocabulary from the sibling `nib.destinationWithBoard` ("vinculada a ... para el triage", "collegata a ... per il triage"). `boardPicker.triageAria` is NOT touched: the two names still differ only in their second half, and the docblocks now say why the composer's may not use the word "land". MEDIUM 2 -- the es and it docblocks claimed `PaperCaptureComposer.spec.ts` checks the eyebrow-to-name relation, but that test mounted on the default locale only, and Spanish composer chrome was asserted nowhere in the suite. The relation test becomes an `it.each` over en, it and es, reading both halves of each pair off the DOM, so the claim those docblocks make to a translator is now true. The en docblock's "in every locale it covers" is replaced by the locales it actually runs in. LOW -- seven selectors in `PaperInboxView.spec.ts` had lost their element-type qualifier in the testid migration, in places where the element type WAS the point (the composer body existing or not existing per variant, and the board select's value). They read `textarea[data-testid="paper-composer-body"]` and `select[data-testid="paper-composer-board"]` again. Red-first for the widened guard: with `es.composer.dueLabel` temporarily changed to "Vencimiento (opcional)", only the new es case failed, reporting the pair ["Vencimiento (opcional)", "Fecha límite (opcional): elige cuándo vence esta captura"]; en and it stayed green. The break was reverted before the verifying run. The es and it wording is this lane's, not native-reviewed (OUTSTANDING_TASKS #1770). Refs #1871
|
Round-2 record (alpha product-trust lane; fix head Fixed:
Verified at the fix head: the five named suites 5 files, 230 passed; Not verified: es/it wording not native-reviewed (#1770); no screen-reader run. The PR body is being updated to the round-2 strings and counts. Merge gate remaining: |
…pointers, #2701 record, #2630 correction clause) Answers the docs review of PR #2704: the #2703 bullet no longer says only force guarantees a request (it gets past the throttle only; the in-flight share and demo mode still apply, the very overclaim round 2 removed from the code); the #2700 and #2703 bullets name every open residual the not-shipped line names; the #2701 pointer says the sixteenth block's subsection is its record; the #2700 bullet says its round 2 touched copy and specs only; the corrections line quotes the thirteenth block's clause that the pinned warning itself sticks.
Summary
The Paper capture composer's four accessible names now lead with the field's visible eyebrow and then say what the control does, the WCAG 2.5.3 shape the board controls took in PR #2675 and the triage board-pick took in PR #2696. Two of the four failed label-in-name outright before this:
Add labeldid not contain the visibleLabels, andDue datedid not contain the visibleDue (optional).PR #2696 could not make that change.
PaperInboxView.spec.tsand three Playwright specs selected these controls by their accessible name, which made the English text test API rather than copy. So this PR migrates those selectors todata-testidattributes first, proves the unchanged suites still pass, and only then rewrites the names in en, es and it.The board select's new name says the capture is linked to the chosen board for triage. It deliberately does not say the capture lands there. Every capture lands in Inbox, the board choice links it so triage can propose against that board, and nothing reaches the board without approve and execute (ADR-0003). The composer's own footer and
inbox.nib.destinationWithBoardalready say exactly that.The PR also closes the two LOWs the fresh-context review of PR #2696 recorded on the issue: the it and es composer docblocks claimed a placeholder rule that
bodyPlaceholderbreaks by design, and the Italian locale cases set the locale before mounting, so they covered first render only.Refs #1871. This is the last item of the board-picker half. The review-i18n half (items 1 to 4 of the issue body) and the bare English outside the coordinator's residual list stay open.
Refs PR #2696 (the extraction this follows), PR #2675 (the label-first pattern).
Changes
Commit 1,
909c5f146- selector migration, no behaviour and no copy change.PaperCaptureComposer.vue:data-testidon the four fields, in the file's existingpaper-composer-*naming:paper-composer-body(textarea),paper-composer-board(select),paper-composer-label-input(label input),paper-composer-due(due input, reaching the<input>throughTdDateField'sv-bind="$attrs").PaperInboxView.spec.ts: 47 name selectors moved onto those testids.capture-loop.spec.ts,first-run.spec.ts,review-proposals.spec.ts:getByRole('textbox', { name: 'Capture body' })becomesgetByTestId('paper-composer-body'), one line each. No E2E spec depended on the name for anything but selection.PaperCaptureComposer.spec.ts: the selection selectors move too, including thefieldChromehelper. The assertions ON the names stay, unchanged in this commit.routeAffordanceInventory.ts: theinbox.capture-submitsource pin moves fromPaperCaptureComposer.vue:369to:373, the four added attribute lines having pushed the Capture button down. The E2E line pins (capture-loop.spec.ts:55,first-run.spec.ts:49,review-proposals.spec.ts:75,:88,:97) are unaffected: each spec had exactly one line replaced in place, verified by reading those lines after the edit.Commit 2,
6a01c43b7- the label-first names.inbox.composer.bodyAria:Capture bodybecomesBody: write the text of this capture.inbox.composer.labelsAria:Add labelbecomesLabels: type a label and press Enter to add it.inbox.composer.dueAria:Due datebecomesDue (optional): set a due date for this capture.inbox.boardPicker.composerAria:Board pickerbecomes a label-first form, superseded in commit 3 below.bodyPlaceholderis a capitalized sentence by design whilelabelsPlaceholderis a lowercase fragment. The previous wording said the placeholder stays lowercase, which is true oflabelsPlaceholderonly and invited a native reviewer under Seed an i18n translation layer (vue-i18n) with Italian and Spanish locales #1770 to lowercase the body one.attachmentsUnavailable,triageAriaand theboardPickcopy are untouched.PaperCaptureComposer.spec.ts: the English and Italian chrome cases assert the new names; a new case asserts the label-first RELATION (every accessible name must start with the eyebrow rendered above its control) so a future reword cannot silently break the rule; a new post-mount locale switch mounts in English, switches to Italian, awaitsnextTickand asserts the eyebrows andbodyAriachanged.PaperTriageTable.spec.ts: the same post-mount switch case for the region name and the board-pick chrome. Nothing else in that file changed.Commit 3,
a563d4460- review round 2: two MEDIUMs and one LOW.Product truth of the board name.
Board: choose where this new capture will landcontradicted the surface it sits on, because nothing lands on a board from the composer. All three locales take the linking form, still label-first:Board: choose which board this capture is linked to for triageTablero: elige a qué tablero se vincula esta captura para el triageBacheca: scegli a quale bacheca collegare questa cattura per il triageThe es and it wording reuses each catalog's own triage vocabulary from the sibling
nib.destinationWithBoard(vinculada a ... para el triage,collegata a ... per il triage), so the composer name and the Nib destination line now say the same thing in the same words.inbox.boardPicker.triageAriais NOT touched in any locale: the two names still differ only in their second half, and the docblocks now record why the composer's may not use the word "land".Locale coverage of the label-first guard. The es and it docblocks claimed
PaperCaptureComposer.spec.tschecks the eyebrow-to-name relation, but that test mounted on the default locale only, and Spanish composer chrome was asserted nowhere in the suite. The relation test is now anit.eachover['en', 'it', 'es']that sets the locale, mounts, and reads both halves of each pair off the DOM, so the promise those docblocks make to a translator is true in every supported locale. The en docblock's "in every locale it covers" is replaced by the locales it actually runs in.Element types back on seven selectors (LOW). In
PaperInboxView.spec.ts, seven selectors had lost their element-type qualifier in the testid migration in places where the element type WAS the point: lines 128 and 143 readtextarea[data-testid="paper-composer-body"]again (the composer variant present, and absent in archived history), and lines 780, 1443, 1540, 1645 and 1670 readselect[data-testid="paper-composer-board"]again (including the twofind<HTMLSelectElement>value reads).Test plan
Verified, from
frontend/taskdeck-webafternpm ci:npx vitest --run --maxWorkers=2 src/tests/views/paper/inbox/PaperCaptureComposer.spec.ts src/tests/views/paper/inbox/PaperTriageTable.spec.ts src/tests/views/paper/PaperInboxView.spec.ts src/tests/i18n/catalogs.spec.ts src/tests/guards/routeAffordanceCoverage.spec.tsat commit 1, names still unchanged: Test Files 5 passed, Tests 225 passed. That green is the migration's proof: the suites that were rewritten to testids still pass with the old copy in place, so the selector change is inert.PaperCaptureComposer.spec.ts.renders the field chrome in English on the default locale(Capture bodyreceived,Body: write the text of this captureexpected).starts every field accessible name with the visible eyebrow above it(all four pairs reported as violations: Body/Capture body, Board/Board picker, Labels/Add label, Due (optional)/Due date).re-renders the field chrome in Italian when the locale switches(Testo della catturareceived).re-renders the field chrome when the locale switches after mount(same, on the already-mounted component).The new
PaperTriageTable.spec.tspost-mount case passed at that point by construction: it covers names PR i18n(inbox): move the Paper triage table's and composer's remaining English to the inbox catalogs #2696 already made label-first, and it is a coverage addition, not a name change.es.composer.dueLabeltemporarily changed toVencimiento (opcional): Tests 1 failed, 40 passed, only the es case, reporting the offending pair["Vencimiento (opcional)", "Fecha límite (opcional): elige cuándo vence esta captura"]. The en and it cases stayed green, which is the point of widening it. The break was reverted before every verifying run above.npm run typecheck(vue-tsc -b): clean, exit 0, at all three commits.npx eslinton the changed files: 0 errors at every commit. One pre-existing warning survives,vuejs-accessibility/label-has-foron the composer's due-date label, which wrapsTdDateFieldrather than a bare input. Confirmed pre-existing by lintinggit show HEAD:...PaperCaptureComposer.vuebefore the change, where it reports at line 352 instead of 355.git diff --check: clean.env -u Llm__Gemini__BaseUrl -u Llm__Gemini__ApiKey -u Llm__Gemini__Model -u Llm__Gemini__TimeoutSeconds npx playwright test tests/e2e/capture-loop.spec.ts tests/e2e/first-run.spec.ts tests/e2e/review-proposals.spec.ts --project=chromium --reporter=line: 11 passed (1.2m), the config's own webServer starting the stack. Run at commit 2 and not repeated for commit 3, which changed no E2E file:git diff --stat HEAD -- frontend/taskdeck-web/tests/e2e/is empty for that commit.inbox.triage.edit.*keys in a different namespace, which this PR does not touch.NOT verified:
OUTSTANDING_TASKS.mdSeed an i18n translation layer (vue-i18n) with Italian and Spanish locales #1770 tracks that review and it stays open.routeAffordanceCoverage.spec.ts, but that guard only validates thesrc/<file>:<line>form. That the line still holds the Capture button was confirmed by reading it, not by an automated check.Boundaries and risks
Owned and touched:
PaperCaptureComposer.vue(testids only),src/locales/{en,es,it}/inbox.ts(the four keys and the docblocks),PaperCaptureComposer.spec.ts,PaperTriageTable.spec.ts(the added switch case only),PaperInboxView.spec.ts(selectors and one comment), the three E2E specs (one selector each),routeAffordanceInventory.ts(the one moved pin).Not touched:
PaperTriageTable.vue,PaperInboxView.vue,useInboxOrchestrator.ts,captureStore.ts, the review files, the #2698 draft-notice keys, the #2696 triage keys, andinbox.boardPicker.triageAria.Risks. The composer's rendered behaviour is unchanged: the only production edit is four
data-testidattributes and four catalog strings, no control, binding, guard or emitted payload moved. The one real exposure is that something outside the frontend selects these controls by their accessible name; the grep above found nothing, and the three E2E specs used the name purely as a locator. A future copy edit that breaks label-in-name, in any of the three locales, now fails a test rather than passing silently.