Repository navigation
fix(components,plugin-list,plugin-detail): the page root reads its nested aria bag, and ListView and useRecordAriaProps map through the shared reader (objectui#11083) - #11102
Conversation
…sted aria bag, and ListView and useRecordAriaProps map through the shared reader The page renderer hands its wrapper toDomProps, which drops the `aria` object, so a page's declared accessible name reached no element. The wrapper now spreads resolveInlineAriaProps(schema.aria, locale). ListView's list region and useRecordAriaProps map the three declared keys through the same reader and keep only their own defaults (the region role; defaultRole, defaultLabel and the refused-label report). ListView keeps its aria.live read, the ListViewSchema extension. Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…seRecordAriaProps on the shared reader Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…t element Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Inputs: card #11083 (body; comments Check-runs on the head: 43 latest runs, all completed by 2026-09-29T13:16Z — 40 ① Derived judgments
② Semver level
③ Boundary flags
Findings (the FAIL):
Implemented-by: VERDICT: FAIL Generated by Claude Code |
Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
…abel at BaseSchema, where it is described Claude-Session: https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Contract reviewServed-tier: Round 2, the delta since r1. r1 is record Inputs: the PR at the new head (body unchanged, first line Check-runs on the head: 43 latest runs, all completed by 2026-09-29T13:36Z — 40 ① Derived judgments
② Semver levelUnchanged from r1: ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #11083
Clause-②: no
The page root now renders the
ariabag the spec declares on it, and the two blocks that kept their own mapping of that bag (ListView's list region,plugin-detail'suseRecordAriaProps) now go throughresolveInlineAriaProps, the shared reader PR objectui#11078 added to@object-ui/react. There is no second mapping table.Why
Part ofand not the order'sFixes. One sub-item needs a decision and is reported on the card, not done here: whetherListViewstops readingaria.live. The measurements are in the section on it below. This PR keeps that read exactly as it was. The card's other items are all in this PR.What changed
PageRenderergives its wrappertoDomProps(props), which keeps only DOM attributes, so theariaobject was dropped and{ type: 'page', aria: { ariaLabel: 'Page name' } }rendered noaria-labelanywhere. The wrapper (the element carryingdata-page-type) now also spreadsresolveInlineAriaProps(schema.aria, useDisplayLocale()). It is spread afterpageProps, so when a flat key and the nested bag name the same attribute, the page's own declared slot wins. The page adds no default role, and a page with noariarenders the same DOM as before.const regionAria = resolveInlineAriaProps(schema.aria, displayLocale), spread on the region, plusrole={regionAria.role ?? 'region'}as ListView's default. Measured: the label had no non-aria fallback. It came only fromschema.aria.ariaLabel, so there was nothing else to keep. One visible difference: an authoredrole: ''used to render an emptyroleattribute, and now falls back toregion(pinned). Thearia.liveread is kept unchanged (see below).useRecordAriaProps(consolidation). Built on the shared reader and the display locale. It keeps its signature, the caller'sdefaultRoleanddefaultLabel, theregionfallback, and the refused-aria.labelreport. The report stays in an effect keyed on the message. The internaluseRecordAriaName(one caller, never exported from the package) became a report-only hook, so this module no longer resolves the label itself.content/docs/api/schema-reference.md, the PageSchema table, gains anariarow (AGENTS.md Add automated testing infrastructure and CI/CD workflows #2). This file is outside the claim's declared surface, and the report says so.aria.live: measured, the read is kept, and a decision is neededTriage direction 2: "Measure whether any document authors it first. None ⇒ the read goes (objectui#2890's residue). Some ⇒ stop and report it on this card."
Census. The pattern is the value form
livefollowed bypolite,assertiveoroff(any quoting), which also catches a multi-line bag.3e71a9825: 5 hits in 4 files, all test fixtures:normalize-list-view.test.ts,ListView.test.tsx,inlineAria.test.tsandp1-spec-alignment.test.ts.origin/maincd901d7a: 2 hits, both inpackages/spec/src/ui/i18n.test.ts.ariaDescribedByover the same trees: 18 lines in objectui and 6 in objectstack.So no document authors it. But the premise that it is "residue" does not hold, because
liveis a declared key with a named producer of guidance:ListViewSchema.ariaisstripImportedDefaults(SpecAriaPropsSchema).extend({ live }), described as "objectui-only — promote rather than grow this extension". TheNamedListView.ariainterface also carrieslive. objectui#2890's changeset kept it on purpose: "aria.livestays as a documented local extension — it has no spec counterpart, and dropping it would silently disable a shipped capability."@objectstack/spec17.4.0 as installed here, refusesliveon every other surface with this guidance: "On an objectui list view the key is valid as-is; anywhere else, drop it." The spec pins that text ini18n.test.ts, together with a reconstruction of objectui's.extend({ live })that accepts it.not_plannedon 2026-09-24 on the maintainer's word. Its rationale names "objectui's own extension carries the rendering".Dropping only the read would leave
liveaccepted by objectui's own schema and recommended by the spec's refusal text, then silently dropped at render. That is declared ≠ enforced, the trap this card exists to remove. Retiring it properly also moves a type (@object-ui/types, so Clause-② becomes yes) and changes the spec's published guidance text in the other repo. So this PR stops on that half and reports it on the card, with options.Tests
The runs are from the worktree root with root-relative paths. They were measured at
904eed298.git diff --stat 904eed298 f4969f552 -- packages/is empty, so thepackages/tree is byte-identical to headf4969f552(the later commits add the changeset and the docs row).page-root-aria-11083.test.tsx: realSchemaRendererand registry, underI18nProvider. A plain string under en and zh, a locale map under en and zh, described-by and role, no bag adds nothing, and a flat-ariaLabelcontrol. 7 tests.ListView.nestedAriaSharedReader-11083.test.tsx: described-by, an authored role, the region default, an empty role falling back to region, a zh locale map, andlivekept. 6 tests.recordAriaSharedReader-11083.test.tsx: a plain string and a locale map under zh, a locale map under en, andrecord:path's default role carrying a zh name. 4 tests.ListView.ariaLabelInlineLocale,recordComponentAria-9556,recordAriaLabelRetired-9945,record-quick-actions.ariaLabel):Test Files 7 passed (7),Tests 82 passed (82).packages/components/src/renderers/layout/, everypage-*/react-page-*/html-page-lazy-blocks/guide-layout-page-buttons/page*ItemMemberstest inpackages/components/src/__tests__/,ListView.test.tsx,i18nLabel-resolver-parity.test.ts, and all ofpackages/plugin-detail/src/renderers/. Result:Test Files 107 passed (107),Tests 1247 passed (1247).--filterwith the^...suffix on the three packages, 13 packages):pnpm --filter @object-ui/components type-check,@object-ui/plugin-listand@object-ui/plugin-detail. Each exited 0, and each rantsc --noEmit && tsc -p tsconfig.test.json. Each package'stsconfig.test.jsonincludessrc/**/*.test.tsx, so the new pins are type-checked.ablation-replace.mjsin wrap mode on the committed fix. It deleted the{...pageAria}spread (anchor 1 → 0, blobf5185737dbf2→b05ab1b57c9e) and ran the page pin. The predicted direction was 5 red and 2 green (the no-bag case and the flat control). Measured:Tests 5 failed | 2 passed (7), exactly those five. After the restore, the blob equals HEAD andgit diff HEADis empty. The same file on the restored tree: 7 passed.Gates
.ts/.tsxfiles: exit 0, and the JSON output lists 6 files with 0 errors. The warnings are all on lines this diff does not touch. The linted population comes from eslint's own config (files: ['**/*.{ts,tsx}']). The config has noparserOptions.projectand noprojectService, so linting is not type-aware and this diff cannot move the verdict on an untouched file.pnpm lintover the whole repo belongs to CI.f4969f552:check-changeset-presence,check-control-bytes,check-new-cross-file-line-citations;check-changeset-claims,check-changeset-no-major,check-pending-changeset-literals;check-phantom-dependencies,check-unused-dependencies;check-handler-key-read-sites,check-unreferenced-sources,check-test-path-roots;check-doc-component-types,check-doc-fence-languages,check-doc-links,check-doc-example-ids.Acceptance notes
These are out of scope and noted only, not filed.
aria.ariaLabelwith noaria.rolelands on the wrapperdiv, which isgeneric, and browsers expose no name on it. This is the same question triage kept off this card forelement:text/element:number(its own card, once there is an assistive-technology measurement). The page follows that ruling and adds no default. Carrier: that future card. No other carrier..changeset/9556-record-component-aria-honoured.mdquotes ListView's oldrole=expression for the region's precedence. The precedence (the authored role, elseregion) still holds, but the quoted expression is no longer literal source. This PR does not edit another PR's pending changeset. Carrier: none.Generated by Claude Code