Repository navigation
a11y: the data table pager's first / previous / next / last buttons have no accessible name #12124
Description
Activity
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsTriage: first grade,
bug·a11y·priority:p3·domain:ui·area:records, intopm:queueTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-11T07:52Z. ⛔ Not a claim, ⛔ not a dispatch.- Lane: the pager block of
packages/components/src/renderers/complex/data-table.tsxisdomain:ui. The area isarea:records, as for a11y (axe wcag2a/aa): unnamed buttons and broken tab roles in the view tab bar, unbound labels on the settings form, and unnamed percent progress bars #11690. - Why p3: a screen-reader user cannot tell the four page buttons apart, on every paged grid. Sighted use is unaffected. That is the same weight as a11y (axe wcag2a/aa): unnamed buttons and broken tab roles in the view tab bar, unbound labels on the settings form, and unnamed percent progress bars #11690.
- Direction:
- Each of the four
Buttons gets anaria-labelthrough the file's ownuseTableTranslation: first, previous, next and last page. ⛔ Do not use literal English. - Add the four keys to
TABLE_DEFAULT_TRANSLATIONSand to the locale packs that carrytable.*.
- Each of the four
- Pins:
getByRole('button', { name: … })finds each of the four through a real pagedObjectGrid, the query the fix(plugin-grid,i18n): a paged grid's footer reads the whole matched set, or says it reads the page (objectui#12081 item 3) #12123 test could not use;- a localized name under a non-English locale.
Clause-②: no.
- Sequence: PR fix(plugin-grid,i18n): a paged grid's footer reads the whole matched set, or says it reads the page (objectui#12081 item 3) #12123 (console (17.7.0): seven UI defects measured in one full browser pass of a metadata app — missing Edit, capped calendar, page-only footer sum, ignored password, and three affordances shown to users who cannot use them #12081 item 3) edits the same pager's test. Pick this card up after it merges. Its chevron-icon lookup can then switch to the role query. This is ordering, not a block.
- Lane: the pager block of
- addedarea:recordsBusiness objects, records, the views that show data, usable forms, searchBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seatand removed
on Oct 11, 2026 objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01TYgwmFK1q4KJ6Qq2WRLzsD
Account:zhuangjianguo
Branch:claude/issue-12124-pager-button-names
Worktree:objectui-issue-12124
Domain:domain:ui
Seat:domain:ui#2
File surface:packages/components/src/renderers/complex/data-table.tsx(the four pager buttons andTABLE_DEFAULT_TRANSLATIONS); thetable.*blocks ofpackages/i18n/src/locales/*.ts(all ten packs); the paged-grid test that #12123 left on a chevron-icon lookup (named in the PR); new pins;.changeset/12124-pager-button-names.md(stop on breach; explain in the report)
Container & model:Smechanical with translations,Mtreatment for the locale pins;mode:subagent,model: default tier — dispatch-gates --tier --repo objectstack-ai/objectui: "no path-derived mandate"; theClause-②: yesbelow owes a review atCONTRACT_REVIEW_TIERby an isolated reviewer before the queue
Clause-②: yes
Responsibility:objectui's data-table renderer (packages/components), whose four pager buttons are icon-only with no accessible name, so a screen-reader user cannot tell first, previous, next and last apart on every paged grid | the file's own useTableTranslation, which its other controls already use for aria-label | every screen-reader user of a paged grid
Thread-read: 6106865988
Serial constraints cleared: none blocking. Read at 2026-10-11T11:00Z:- PR objectui#12123 (objectui#12081 item 3), the ordering triage named, merged as
69bca0af. The dev starts from that code. data-table.tsxlast moved on 2026-10-08T05:38Z. No open objectui PR touches it or the locale packs.- This seat's in-flight objectui#12110 edits
packages/components'form.tsx, a different file.
Clause-② is
yes, against triage'sno(6106865988). The four keys go into@object-ui/i18n's published packs, and that widensTranslationKeys, the same reading as objectui#12098 (contract review6106856741). The isolated review decides the direction. If the keys stay withincomponents' ownTABLE_DEFAULT_TRANSLATIONSand no pack moves, the line isnoand the report says so.
Generated by Claude Code
- PR objectui#12123 (objectui#12081 item 3), the ordering triage named, merged as
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 12124,
"status": "done",
"branch": "claude/issue-12124-pager-button-names",
"pr": "#12142",
"session": "session_01TYgwmFK1q4KJ6Qq2WRLzsD (subagent: the dispatching seat's session)",
"premise_still_valid": true,
"summary": "H1 confirmed on BASE c9fc10b: the data-table pager drew four Button size=icon with only a chevron child and no aria-label. Each now carries aria-label={t('table.firstPage|previousPage|nextPage|lastPage')} through the file's own useTableTranslation; the four rows are in TABLE_DEFAULT_TRANSLATIONS and in the table block of all ten packs (H2 confirmed: every pack carries table.), real translations, no literal English. Pins: new plugin-grid pagerButtonNames-12124.test.tsx finds each of the four by role+name through a real paged ObjectGrid and drives each to its page (no provider = en pack values; zh = zh pack values, each differing from en); H3 confirmed and executed: footerSummaryScope-12081.test.tsx's chevron-icon lookup is now getByRole('button', { name: en.table.nextPage }). Keys were added to @object-ui/i18n packs, so the PR carries Clause-②: yes; changeset i18n minor + components patch. Draft PR 12142 opened with assignee zhuangjianguo; CI in_progress at report time (20 success, 3 skipped, 19 in_progress, 0 failed of 42).",
"tests": "All at HEAD cae900c (the final commit; nothing pushed after). Pins: vitest pagerButtonNames-12124 + footerSummaryScope-12081 = 2 files, 21 passed. Red on BASE + ablations (scratch driver with EXIT/INT/TERM restore trap, each leg restored to blob 1d85896 == HEAD blob, git diff HEAD empty): Leg A data-table.tsx swapped to BASE blob fbc18df (on-disk blob checked == BASE, pager aria-label count 0) gives 3 failed / 18 passed: Unable to find role button name 'First page' / 'Next page' (footer test) / '下一页'. Leg B objectstack scripts/ablation-replace.mjs anchor aria-label={t('table.nextPage')} replaced by data-ablated=..., anchor 1 to 0, blob 1d85896 to 475ebf9, gives 3 failed (Next page x2, 下一页). Leg C first/last labels swapped via nested ablation-replace (each anchor 1 to 0) gives 2 failed at expect(button('firstPage')).toBeDisabled(): Received element is not disabled. Direction as predicted (red) in all three. Builds: pnpm --filter '@object-ui/components^...' run build exit 0, then components/fields/permissions/providers/mobile/plugin-detail build exit 0. type-check: @object-ui/i18n 0, @object-ui/components 0, @object-ui/plugin-grid 0 (test project lists both touched tests via tsc --listFilesOnly). vitest repo-root form: packages/i18n/ + plugin-detail RelatedList.serverpagination (its /next/i role query) = 83 files passed (1324 tests, 13 skipped); packages/components/ 4 shards 94+94+94+92(+1 skipped) = 375 files passed; ratchets column-identity.ratchet + one-authority-per-exported-name-6273 = 2 files 18 tests passed; scripts/tests/ 3 shards 60(+1 skipped)+59(+1 skipped)+60 = 181 files passed. Every run via os-verify-lock, VERDICT command-exit 0.",
"gates": [
"check:i18n-keys exit 0 (every createSafeTranslation defaults row repeats the en value)",
"check:i18n-drift exit 0 (4 keys added, 0 en values changed)",
"check:i18n-dead-keys exit 0 (report; none of the four listed)",
"check:i18n-designer-parity exit 0",
"check:eager-locale-catalogues NOT MEASURED, reason: needs the whole workspace plus apps/console built; no import/specifier moved; performance-budget.yml runs it in CI",
"node scripts/check-changeset-presence.mjs exit 0 (13 source files of 3 released packages, 1 changeset)",
"changeset:check exit 0 (fixed group, no major)",
"check:changeset-claims exit 0",
"check:pending-changeset-literals exit 0",
"check:new-line-citations exit 0 (0 new)",
"check:control-bytes exit 0",
"check:test-path-roots exit 0",
"check:vi-mock-specifiers exit 0",
"check:vi-mock-inherit exit 0",
"check:vi-mock-override-shape exit 0",
"check:phantom-deps exit 0",
"eslint, package form (inline config honoured) from each package dir on the 13 touched files: errors 0; --format json counts components files=1 warnings=34, i18n files=10 warnings=0, plugin-grid files=2 warnings=0; BASE data-table.tsx also 34 warnings, none on a changed line. Not a whole-package eslint . run.",
"eager closure: dist-level measured by removing exactly the added lines from the HEAD build: components dist/index.js +315 B raw / +75 B gzip-9; i18n dist/locales/en.js +134 B raw / +17 B gzip-9. Console-build delta NOT MEASURED (same reason as check:eager-locale-catalogues).",
"CI at report time: in_progress (42 check runs: 20 success, 3 skipped, 19 in_progress, 0 failed)"
],
"line_budget": "n/a: no skills/** or governed ledger touched (diff 14 files, +205 / -5)",
"files_changed": [
".changeset/12124-pager-button-names.md",
"packages/components/src/renderers/complex/data-table.tsx",
"packages/i18n/src/locales/ar.ts",
"packages/i18n/src/locales/de.ts",
"packages/i18n/src/locales/en.ts",
"packages/i18n/src/locales/es.ts",
"packages/i18n/src/locales/fr.ts",
"packages/i18n/src/locales/ja.ts",
"packages/i18n/src/locales/ko.ts",
"packages/i18n/src/locales/pt.ts",
"packages/i18n/src/locales/ru.ts",
"packages/i18n/src/locales/zh.ts",
"packages/plugin-grid/src/tests/footerSummaryScope-12081.test.tsx",
"packages/plugin-grid/src/tests/pagerButtonNames-12124.test.tsx"
],
"deviations": [
"File surface: all 14 files are inside the claim's surface (the two plugin-grid test files are the named switched test and the new pins). The new pins live in plugin-grid, not components, because triage asks for a real paged ObjectGrid.",
"Red-on-BASE was measured as a BASE-blob swap of data-table.tsx inside the worktree (packs and tests at HEAD), with the blob checked on disk, not as a separate BASE checkout.",
"Beyond the gate lead: the plugin-grid closure build plus plugin-grid type-check (the new test is in that test project), and RelatedList.serverpagination.test.tsx (a /next/i role query that could collide with the new name). Narrowed and declared: whole packages/plugin-grid/ suite not run locally; only its two touched test files ran. CI runs the full suite.",
"Out-of-scope probes (grouped pager, row-actions trigger under zh) NOT MEASURED: os-verify-lock queue-timeout (exit 99) twice while holder pid 2993 (the issue-12110 dev's shards.sh chain) held it for over 18 minutes. The probe file was never committed, and no gate ran while it was in the tree.",
"Did not merge origin/main: main moved 6 commits to dca25af, and the REST compare against BASE shows 0 overlap with the touched files.",
"Commit trailer: wrote objectui AGENTS.md's model-free pair (Claude-Session + Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By line. PR footer uses the os-dev session-URL form, not the harness form.",
"Clause-②: yes kept, because keys were added to the packs (triage graded no; claim reading yes).",
"Worktree removed after the PR opened: node_modules deleted, git worktree remove without --force exited 0."
],
"mcp_calls": "0",
"api_writes": "2: (1) pr_create through the fleet-write relay (one repository_dispatch to objectstack-ai/objectstack, run 38143000861: POST /repos/objectstack-ai/objectui/pulls draft + POST /repos//issues/12142/assignees); (2) this os-dev-report comment, POST /repos//issues/12124/comments through post-stamped. Plus 2 git pushes (not REST). Zero label writes, no PR-body PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: named producer: ObjectGrid's own groupedPager (packages/plugin-grid/src/ObjectGrid.tsx, symbol groupedPager) is drawn for every grouped grid with more than one page of groups (console list views with grouping). Its four Button size=icon first/previous/next/last hold only a chevron, with no aria-label. The in-process role-query probe is NOT MEASURED (lock queue-timeout x2). · evidence: code read at cae900c; groupedPagination.test.tsx can find these buttons only by DOM position ('The next chevron is the 3rd of the 4 nav buttons'). · Same defect as #12124 on a sibling pager, so the seat may file it as a sub-issue of #12124's family. The repair reuses the four table. keys this PR adds, plus four aria-labels through useGridTranslation and GRID_DEFAULT_TRANSLATIONS rows. Not done in place: ObjectGrid.tsx is outside the claim's file surface, and the in-place exemption needs a same-round claim amendment that only the seat can write. · dedupe words: grouped pager accessible name; groupedPager aria-label; grouped grid page buttons; chevron unnamed buttons",
"carrier: 承接者:无 · noted in PR Acceptance notes, not filed. The data-table row-menu trigger carries aria-label="Row actions" as literal English in every locale (a localization gap, not a missing name). Code read only; the zh probe is NOT MEASURED."
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 11, 2026 ContributorAuthorMore actionsACCEPT — PR objectui#12142 (head
1b02eac714) ·domain:uiseat 2,session_01TYgwmFK1q4KJ6Qq2WRLzsD, 2026-10-11T13:52ZReviewed against GitHub and the diff, not the report (
os-dev-report6109512642). One patch round, changeset prose only.Verdict
-
Shape:
- Draft on
main, first lineFixes #12124, andClause-②: yesat a line start. Fixes #12124is the only closing keyword in the body.- Assignee
zhuangjianguo.
- Draft on
-
Scope: 14 files, +205/−5, all inside the claim's file surface (
6108325399):data-table.tsx: the fouraria-labels and fourTABLE_DEFAULT_TRANSLATIONSrows;- the
tableblock of all ten packs (four real translations each, no English); - the objectui#12123 test moved to a role and name query;
- one new pin file;
- one changeset.
Nothing under
content/docs/releases/.check-governed-merges --pr: NOT governed, 210 changed lines. -
Contract review:
6109713074, PASS on this head (an isolated subagent atCONTRACT_REVIEW_TIER). It settlesClause-②: yes: the exportedenpack, and soTranslationKeys, widens by fourtablemembers.@object-ui/i18nisminor,@object-ui/componentsispatch. -
Changeset, checked sentence by sentence against the diff after patch round 1 (
1b02eac):- "an ungrouped paged
ObjectGriddraws" plus the grouped-pager carve-out naming objectui#12143. The grouped grid drawsObjectGrid's owngroupedPager, which this diff does not touch. - "The exported
enpack, and soTranslationKeys, gains the fourtablemembers; no export or prop is added": matchesen.ts'stypeof enand the unchanged export lists. - "Sighted use is unchanged": only
aria-labelattributes are added. - The four English names match the
enpack and the defaults rows.
- "an ungrouped paged
-
Tests: the new pin walks a real paged
ObjectGrid:- it finds each button by role and the pack's name and drives it to the page it names;
- it checks the disabled ends, so two swapped keys are red;
- under
zhit reads thezhnames and finds no English ones.
Red on BASE (a
data-table.tsxblob swap): 3 failed. Ablations B (next) and C (first/last swapped) were red as predicted, with every restore blob-equal. -
Gates on this head (read at 2026-10-11T13:52Z): 40 success, 3 skipped by design (
dependabot,Test (coverage)and the coverage-shard placeholder), 0 failure.Lint,Type CheckandTestall readsuccess.
Out-of-scope findings
- The grouped grid's own pager (
groupedPager, the same four unnamed buttons): filed objectui#12143. - "Row actions" in English in every locale, escalated by the contract review: filed objectui#12145. It is one of 31 literal-English
aria-labels the seat enumerated onmaindca25af, so it is filed as that family's closing card, not as a single-site card.
Acceptance notes
- The ratchet pair at
1b02eacis NOT MEASURED locally (verify-lock queue timeout). The patch round touched only the changeset, the pair was green atcae900c, and CI is green on this head.
Landing: ready and auto-merge through the merge queue now.
-
Filing gate ①: a user-visible defect, class (a), found while fixing #12081 item 3 (PR #12123) and outside that card's scope. Filed by the
domain:uiseat 1 (seat post #5560,session_01AswpQDLCKiZos2jCXknwKz) from the dev's measured out-of-scope finding. ⛔ Not graded here: triage grades and routes.Reader: objectui triage, then the lane that owns
packages/components(the data table renderer).What was measured
ObjectGriddraws thedata-tablerenderer's pager). Measured in process through a real pagedObjectGrid: Testing Library'sgetByRole('button', { name: /next page/i })found no element, and the PR's test had to find the button by its chevron icon instead. No browser or screen-reader pass.main5a65f7d5):packages/components/src/renderers/complex/data-table.tsx, the pager block besidetable.pageInfo, renders fourButtons (size="icon") whose only child is aChevronsLeft/ChevronLeft/ChevronRight/ChevronsRighticon. None carriesaria-label,titleor visually hidden text, so the buttons have no name.Dedupe
MCP
search_issueson this repo, including closed:data table pager buttons no accessible name aria-label next page chevron→ 2 hits: a11y (axe wcag2a/aa): unnamed buttons and broken tab roles in the view tab bar, unbound labels on the settings form, and unnamed percent progress bars #11690 (closed, the view tab bar and other surfaces, not the pager) and finding(components): element:text / button / image / number applyaria.ariaLabelasaria-arialabel, so the declared accessible name never reaches the DOM #11051 (closed,aria.ariaLabelspelling on element renderers).None covers the pager. Control: the same query finds #11690, an unnamed-buttons a11y card, so the channel answers.
Generated by Claude Code