Repository navigation
Commit b24f93a
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)
Part of #11083
Clause-②: no
The page root now renders the `aria` bag the spec declares on it, and
the two blocks that kept their own mapping of that bag (`ListView`'s
list region, `plugin-detail`'s `useRecordAriaProps`) now go through
`resolveInlineAriaProps`, the shared reader PR objectui#11078 added to
`@object-ui/react`. There is no second mapping table.
**Why `Part of` and not the order's `Fixes`.** One sub-item needs a
decision and is reported on the card, not done here: whether `ListView`
stops reading `aria.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
1. **Page root (the defect).** `PageRenderer` gives its wrapper
`toDomProps(props)`, which keeps only DOM attributes, so the `aria`
object was dropped and `{ type: 'page', aria: { ariaLabel: 'Page name' }
}` rendered no `aria-label` anywhere. The wrapper (the element carrying
`data-page-type`) now also spreads `resolveInlineAriaProps(schema.aria,
useDisplayLocale())`. It is spread after `pageProps`, 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 no `aria` renders
the same DOM as before.
2. **ListView (consolidation).** The inline mapping (label,
described-by, role) is replaced by `const regionAria =
resolveInlineAriaProps(schema.aria, displayLocale)`, spread on the
region, plus `role={regionAria.role ?? 'region'}` as ListView's default.
Measured: the label had **no** non-aria fallback. It came only from
`schema.aria.ariaLabel`, so there was nothing else to keep. One visible
difference: an authored `role: ''` used to render an empty `role`
attribute, and now falls back to `region` (pinned). The `aria.live` read
is kept unchanged (see below).
3. **`useRecordAriaProps` (consolidation).** Built on the shared reader
and the display locale. It keeps its signature, the caller's
`defaultRole` and `defaultLabel`, the `region` fallback, and the
refused-`aria.label` report. The report stays in an effect keyed on the
message. The internal `useRecordAriaName` (one caller, never exported
from the package) became a report-only hook, so this module no longer
resolves the label itself.
4. **Docs.** `content/docs/api/schema-reference.md`, the PageSchema
table, gains an `aria` row (AGENTS.md #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 needed
Triage 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 `live` followed by `polite`,
`assertive` or `off` (any quoting), which also catches a multi-line bag.
- objectui at base `3e71a9825`: 5 hits in 4 files, **all test
fixtures**: `normalize-list-view.test.ts`, `ListView.test.tsx`,
`inlineAria.test.ts` and `p1-spec-alignment.test.ts`.
- objectstack at `origin/main` `cd901d7a`: 2 hits, both in
`packages/spec/src/ui/i18n.test.ts`.
- There are **0** in examples, apps, seeds or docs.
- The lit control is the value form of `ariaDescribedBy` over 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 `live` is a **declared** key with a named producer of
guidance:
- objectui declares it. The zod mirror's `ListViewSchema.aria` is
`stripImportedDefaults(SpecAriaPropsSchema).extend({ live })`, described
as "objectui-only — promote rather than grow this extension". The
`NamedListView.aria` interface also carries `live`. objectui#2890's
changeset kept it on purpose: "`aria.live` stays as a documented local
extension — it has no spec counterpart, and dropping it would silently
disable a shipped capability."
- The **published** spec, `@objectstack/spec` 17.4.0 as installed here,
refuses `live` on 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 in `i18n.test.ts`, together with a reconstruction of
objectui's `.extend({ live })` that accepts it.
- objectstack#5058, the promotion question, was closed `not_planned` on
2026-09-24 on the maintainer's word. Its rationale names "objectui's own
extension carries the rendering".
Dropping only the read would leave `live` accepted 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 904eed2 f4969f5 --
packages/` is empty, so the `packages/` tree is byte-identical to head
`f4969f552` (the later commits add the changeset and the docs row).
- New pins:
- `page-root-aria-11083.test.tsx`: real `SchemaRenderer` and registry,
under `I18nProvider`. 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-`ariaLabel` control. 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, and `live` kept. 6 tests.
- `recordAriaSharedReader-11083.test.tsx`: a plain string and a locale
map under zh, a locale map under en, and `record:path`'s default role
carrying a zh name. 4 tests.
- Targeted run, new pins plus the existing aria pins
(`ListView.ariaLabelInlineLocale`, `recordComponentAria-9556`,
`recordAriaLabelRetired-9945`, `record-quick-actions.ariaLabel`): `Test
Files 7 passed (7)`, `Tests 82 passed (82)`.
- Wider run: `packages/components/src/renderers/layout/`, every `page-*`
/ `react-page-*` / `html-page-lazy-blocks` / `guide-layout-page-buttons`
/ `page*ItemMembers` test in `packages/components/src/__tests__/`,
`ListView.test.tsx`, `i18nLabel-resolver-parity.test.ts`, and all of
`packages/plugin-detail/src/renderers/`. Result: `Test Files 107 passed
(107)`, `Tests 1247 passed (1247)`.
- Type-check, after building the dependency closure (`--filter` with the
`^...` suffix on the three packages, 13 packages): `pnpm --filter
@object-ui/components type-check`, `@object-ui/plugin-list` and
`@object-ui/plugin-detail`. Each exited 0, and each ran `tsc --noEmit &&
tsc -p tsconfig.test.json`. Each package's `tsconfig.test.json` includes
`src/**/*.test.tsx`, so the new pins are type-checked.
- **Ablation (page root).** This used `ablation-replace.mjs` in wrap
mode on the committed fix. It deleted the `{...pageAria}` spread (anchor
1 → 0, blob `f5185737dbf2` → `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 and `git diff HEAD` is empty.
The same file on the restored tree: 7 passed.
## Gates
- eslint on the 6 changed `.ts`/`.tsx` files: 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 no
`parserOptions.project` and no `projectService`, so linting is not
type-aware and this diff cannot move the verdict on an untouched file.
`pnpm lint` over the whole repo belongs to CI.
- Exit 0 on the tree at head `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`.
- Self-scan for control bytes over the changed files: no hits.
## Acceptance notes
These are out of scope and noted only, not filed.
- **The page root has no default role.** An authored `aria.ariaLabel`
with no `aria.role` lands on the wrapper `div`, which is `generic`, and
browsers expose no name on it. This is the same question triage kept off
this card for `element: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.
- **A pending changeset quotes the old ListView source.**
`.changeset/9556-record-component-aria-honoured.md` quotes ListView's
old `role=` expression for the region's precedence. The precedence (the
authored role, else `region`) 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](https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 2fb0f9a commit b24f93a
8 files changed
Lines changed: 409 additions & 48 deletions
File tree
- .changeset
- content/docs/api
- packages
- components/src
- __tests__
- renderers/layout
- plugin-detail/src/renderers
- __tests__
- plugin-list/src
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
| 142 | + | |
142 | 143 | | |
143 | 144 | | |
144 | 145 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
18 | 24 | | |
| 25 | + | |
19 | 26 | | |
20 | 27 | | |
21 | 28 | | |
| |||
563 | 570 | | |
564 | 571 | | |
565 | 572 | | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
566 | 585 | | |
567 | 586 | | |
568 | 587 | | |
| |||
658 | 677 | | |
659 | 678 | | |
660 | 679 | | |
| 680 | + | |
661 | 681 | | |
662 | 682 | | |
663 | 683 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
50 | | - | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
51 | 60 | | |
52 | 61 | | |
53 | 62 | | |
54 | 63 | | |
55 | | - | |
56 | | - | |
57 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
58 | 67 | | |
59 | 68 | | |
60 | 69 | | |
| |||
92 | 101 | | |
93 | 102 | | |
94 | 103 | | |
95 | | - | |
| 104 | + | |
96 | 105 | | |
97 | 106 | | |
98 | 107 | | |
| |||
142 | 151 | | |
143 | 152 | | |
144 | 153 | | |
145 | | - | |
146 | | - | |
147 | | - | |
148 | | - | |
149 | | - | |
150 | | - | |
| 154 | + | |
| 155 | + | |
151 | 156 | | |
152 | | - | |
153 | | - | |
154 | | - | |
| 157 | + | |
| 158 | + | |
155 | 159 | | |
156 | | - | |
| 160 | + | |
| 161 | + | |
157 | 162 | | |
158 | | - | |
159 | | - | |
160 | | - | |
| 163 | + | |
161 | 164 | | |
162 | | - | |
163 | | - | |
164 | 165 | | |
165 | 166 | | |
166 | 167 | | |
167 | | - | |
168 | 168 | | |
169 | 169 | | |
170 | 170 | | |
| |||
177 | 177 | | |
178 | 178 | | |
179 | 179 | | |
180 | | - | |
181 | | - | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
182 | 183 | | |
183 | 184 | | |
184 | 185 | | |
| |||
188 | 189 | | |
189 | 190 | | |
190 | 191 | | |
191 | | - | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
192 | 195 | | |
193 | | - | |
194 | | - | |
195 | | - | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
196 | 199 | | |
197 | 200 | | |
198 | 201 | | |
| |||
0 commit comments