You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
a11y(form designer): every field card's aria-roledescription is dnd-kit's English "sortable", whatever the locale #11872
Filing gate ①: a measured defect, class (a): user-facing copy read to screen-reader users, left in English. Reach: Studio → an object → the Form tab's designer, in zh-CN. Measured by the objectui#11802 dev in real Chromium (the same dev-only harness as objectui#11802; not committed).
Who acts on it: objectui triage, grade and route. ⛔ Not a claim. Filed by the domain:ui execution seat 1 (session_01DrKzdPdyLLBW3qpZ4vtk7z) from the objectui#11802 dev report (out-of-scope finding 2).
What happens
Every field card in the form designer carries aria-roledescription="sortable" in English, whatever the locale.
A screen reader reads it when the card takes focus.
objectui#11802 localizes the drag announcements and instructions, but not this attribute: it sits outside that card's DndContext-props fence.
Where it comes from (read in source)
dnd-kit's useSortable sets roleDescription: 'sortable' unless it is given attributes.roleDescription.
The designer's SortableField (packages/app-shell/src/views/studio-design/ObjectFormDesigner.tsx) passes none.
Done when
Each field card's aria-roledescription is a localized engine string (EN and ZH), passed through useSortable's attributes.roleDescription.
A pin reads the attribute in both locales.
Duplicate check
Semantic issue search on objectstack-ai/objectui, aria-roledescription sortable English untranslated useSortable roleDescription: 0 hits.
Positive control in the same session: form designer keyboard drag no drop target … returned objectui#11802.
Dedupe words: aria-roledescription sortable English · form designer card role description localized · useSortable roleDescription
Path: write metadata — the form designer by keyboard and screen reader | 缺项 | P3
Triage: first grade, bug · priority:p3 · domain:ui · area:i18n · pm:queue. Direction: a localized role description on the field cards
Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-08T04:00Z. ⛔ Not a claim, ⛔ not a dispatch.
Triage: lands in the form designer's field cards (useSortable's default aria-roledescription) ⇒ domain:ui; rationale: a Studio or console surface in objectui. Read on objectui main4e7dd56ed1.
Why p3: a screen reader announces English "sortable" in every locale; nothing else is wrong.
Direction:
pass a localized roleDescription through useSortable's attributes, with an i18n key in every locale
studio-design/ObjectFormDesigner.tsx: SortableField's useSortable call (about :231), passing a localized attributes.roleDescription. Nothing else in the file.
metadata-admin/i18n.ts: one new engine string beside engine.studio.designer.fieldAria, in ENGINE_STRINGS_EN (about :2943) and ENGINE_STRINGS_ZH (about :5984), and in any other locale table that file keeps for the designer's strings.
The tests beside these (a new ObjectFormDesigner.roleDescription-11872.test.tsx), and .changeset/11872-*.md.
⛔ Not on it:
The group cards' sortable or droppable role descriptions, unless the dev measures that they carry the same English default. If they do, the dev reports it first.
formDndAnnouncements.ts and the DndContext props (objectui#11802's, landed).
formCollision and the keyboard coordinate getter (objectui#11871's, landed; objectui#11898 queued behind this card).
packages/i18n/** and packages/components/src/ui/**.
Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model: M, mode:subagent, model: opus (dispatch-gates --tier --repo objectstack-ai/objectui over these paths: no path-derived mandate; default tier)
Clause-②: no
Responsibility: objectui app-shell Studio form designer: every field card's aria-roledescription is dnd-kit's English "sortable" in every locale, so a screen reader reads English to a zh-CN author | the platform path is right: useSortable takes attributes.roleDescription, and the designer's other strings already come from the engine string tables | every screen-reader author in Studio's Form tab outside English
Thread-read: 6051909736
Serial constraints cleared: none blocking.
The same file, in order: objectui#11802 landed (33e3583), and objectui#11871 landed (a476dee) through PR objectui#11897, merged 2026-10-08T05:53:23Z. Triage's grade 6051909736 asked for this card to run after both.
area:i18n in flight: none on the board.
Open objectui PRs: objectui#11899 (seat 3's objectui#11855) touches metadata-admin/i18n.ts at about :3074 and :6113, away from the designer rows at about :2943 and :5984. No open PR touches ObjectFormDesigner.tsx. Read 2026-10-08T05:58Z.
This seat: dispatch is parallel, batch 3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.
objectui#11878's dev has finished (PR objectui#11901), which freed this slot.
Working with this card: objectui#11862's dev and objectui#11889's.
Why Clause-②: no: an attribute value inside the designer and one engine string. No export, prop, @object-ui/types member or accepted input changes.
{
"issue": 11872,
"status": "done",
"branch": "claude/issue-11872-form-roledescription",
"pr": "https://github.com/objectstack-ai/objectui/pull/11920",
"session": "session_01DrKzdPdyLLBW3qpZ4vtk7z — the session the PM claim comment 6053415074 names (subagent run, so the parent session id; the container reads CLAUDE_CODE_REMOTE_SESSION_ID=cse_01DrKzdPdyLLBW3qpZ4vtk7z)",
"premise_still_valid": true,
"summary": "SortableField's useSortable call in ObjectFormDesigner.tsx now passes attributes.roleDescription: the new engine string engine.studio.designer.fieldRole (en 'draggable field', zh '可拖动字段', beside engine.studio.designer.fieldAria in ENGINE_STRINGS_EN and ENGINE_STRINGS_ZH, the only two tables the file keeps) for a writable card, read through the same t(key, locale) / useMetadataLocale() the card's aria-label uses, and a blank value for a read-only package's card, which real Chromium does not expose, so that card reads as the plain button it is (H3). The premise held on main 9844bbf: every card carried dnd-kit's English 'sortable'. The group sections are useDroppable only and carry no role description, so there was no scope question. Draft PR objectui#11920 (first line 'Fixes #11872', a 'Clause-②: no' body line, @object-ui/app-shell patch changeset). The card assignee and labels were not touched; size/path labels on the PR come from the labeler, not from this run.",
"tests": "HEAD the gate union ran on: abf200c (git rev-parse --short HEAD; merge commit of origin/main 09821b1 into the branch, no rebase; the merge was clean, and objectui#11899 had touched i18n.ts elsewhere). Commits: dd86396 (fix + pin), 4c4b1af (changeset), abf200c (merge). No commit after the union. || GATES (command → exit → verdict line): [1] os-verify-lock -c \"pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build\" → 0 → \"VERDICT command-exit 0\" (Scope: 29 of 47 workspace projects); the first attempt was \"VERDICT queue-timeout (exit 99)\" behind objectui#11862's union, and the same OS_VERIFY_LOCK_SLOT re-run acquired the lock. [2] os-verify-lock -c union.sh → 0 → \"VERDICT command-exit 0\", containing: pnpm --filter @object-ui/app-shell type-check → 0 (tsc --noEmit && tsc -p tsconfig.test.json; tsconfig.test.json --listFilesOnly lists the new test, 1 hit); pnpm exec vitest run --maxWorkers=2 over 8 files → 0 → \"Test Files 8 passed (8) / Tests 57 passed (57)\". The 8 files: ObjectFormDesigner.roleDescription-11872.test.tsx (new), ObjectFormDesigner.test.tsx, ObjectFormDesigner.dndAnnouncements-11802.test.tsx, ObjectFormDesigner.keyboardDrop-11871.test.tsx, StudioDesignSurface.readOnlyWording-11781.test.tsx, StudioDesignSurface.accessGuard.test.tsx, StudioDesignSurface.pillarNavGuard.test.tsx, metadata-admin/studio-locale.i18n.test.tsx. [3] lock-free, each exit 0: check:control-bytes (\"check-control-bytes: OK\"), check:new-line-citations (\"VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0\"), check:changeset-claims (\"No pending changeset names a file this change touches.\"), check:pending-changeset-literals (\"No test source names a pending changeset.\"), check:i18n-designer-parity (\"Every en row has a zh row, and every shared row carries the same placeholders.\"), check:i18n-dead-keys (report; fieldRole not listed), check:i18n-keys (\"Every in-scope call-site key resolves against the en pack\"), check:i18n-drift (\"No designer-table en value changed in this range.\"; 1 key added), check:test-path-roots (OK), check:vi-mock-specifiers (OK). Derived by hand from package.json and the diff (objectui has no dispatch-gates.mjs): node scripts/check-changeset-presence.mjs → 0 (\"3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)\"), pnpm changeset:check → 0 (\"No changeset declares a major bump.\"), node scripts/check-governed-queue-guard.mjs --test (the 4 paths) → \"NOT GOVERNED\". [4] NOT RUN locally: the repo-wide pnpm lint and the full test farm (CI-owned). eslint --no-inline-config over the 3 touched source files → 0 with 1 warning that was already there (react-hooks/set-state-in-effect on the draft re-sync effect, not a changed line). That is a spot check, not a narrowing claim. || REVERSE CHECK (ablation, after committing the fix in dd86396): node objectstack/scripts/ablation-replace.mjs --delete on the line \"attributes: { roleDescription: … }\" → \"anchor x1 → x0\", blob e6efb31f → 1fe26612, then the pin ran: 3 failed / 1 passed, each failing case (en, zh, read-only) \"Received: aria-roledescription=\\\"sortable\\\"\". The group-sections case stayed green, as predicted: it counts the described elements, which are still the 3 cards. Restore: \"blob == HEAD (e6efb31f593c) and git diff HEAD is empty\". Direction: the pin went red, as predicted. No build or dist was involved: the test imports ./ObjectFormDesigner from src. || H1 CONFIRMED: at the installed @dnd-kit/sortable 10.0.0 and @dnd-kit/core 6.3.1, useSortable passes { ...defaultAttributes, ...userDefinedAttributes } with defaultAttributes = { roleDescription: 'sortable' } into useDraggable, which writes 'aria-roledescription': roleDescription. Its own destructuring default 'draggable' applies only to undefined (source read). The ablation measures both sides: without the argument the cards read sortable, and with it they read the localized row. || H2 CONFIRMED: {...attributes} is spread before aria-label. The returned attributes are role, tabIndex, aria-disabled, aria-pressed, aria-roledescription and aria-describedby, and nothing after the spread writes aria-roledescription. Real Chromium shows the DOM attribute exactly as the row: \"draggable field\" for en and 可拖动字段 for zh. || H3 MEASURED, the answer is no role description, which is taken and pinned: the read-only card is role=button tabindex=0, a click opens the inspector, and it is not draggable (sensors={readOnly ? [] : sensors}). Inside the fenced call, undefined falls through to useDraggable's English 'draggable', so the read-only value is ''. Real Chromium (CDP Accessibility.getFullAXTree, on a dev-only harness mounting the real ObjectFormDesigner, not committed, now removed): static control rows read absent → no roledescription, blank → no roledescription, sortable → sortable. The read-only cards (en and zh) read role button with roledescription null, and the writable cards read roledescription \"draggable field\". Instrument note: the CDP roledescription property returns non-ASCII values as their UTF-8 bytes read as Latin-1 (code units e5 8f af … for 可拖动字段), including for static zh and French controls. That is DevTools serialization, while the DOM attribute is exact. What a real screen reader speaks for zh is NOT MEASURED (no screen reader in the container). The wording suggestion was kept. || FILES_CHANGED: packages/app-shell/src/views/studio-design/ObjectFormDesigner.tsx (the SortableField useSortable call only), packages/app-shell/src/views/metadata-admin/i18n.ts (the fieldRole row in EN with a 2-line comment, and in ZH), packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx (new, 4 cases: en, zh, read-only, group sections only drop zones), .changeset/11872-form-roledescription.md (new, @object-ui/app-shell patch). All are inside the claim's file surface. || DEVIATIONS: (1) The order's gate glob \"*.guard.test.*\" matched no file in either directory, so the two StudioDesignSurface.*Guard.test.tsx files ran in its place. No metadata-admin/i18n*.test file exists, so studio-locale.i18n.test.tsx ran instead. StudioDesignSurface.readOnlyWording-11781.test.tsx was added because the read-only card changed. (2) The changeset names the zh wording as \"its zh translation\" rather than the literal string, per AGENTS.md #-1 (English-only docs). (3) The commit trailers are the model-free pair AGENTS.md rules, not the harness reminder's model-named line. (4) A dev-only browser harness (apps/console/rd-11872.html, apps/console/src/rd-11872.tsx, node_modules/.drive-11872*.mjs) was created untracked and then deleted. My own vite on :5197 was killed by its pids, and git status is clean. (5) The worktree is left in place for the seat, per the order.",
"mcp_calls": "0 — no MCP GitHub tool was called (reads went through gh api REST: the card, its comments, objectui#11871 comments for the report format, objectui#11898, PR objectui#11920 read-back)",
"api_writes": "2 relay writes, within the order's budget of three: (1) pr_create through fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches → run 37738218435 success → POST /repos/objectstack-ai/objectui/pulls (draft forced), read-back \"7020 byte(s) sent, 7020 stored — identical\"; (2) this os-dev-report comment through post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11872/comments. Plus git push of the branch (the probe and 3 pushes of the same branch; not REST). Zero label writes, zero assignee writes, no PATCH.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: real Chromium on a dev-only harness that mounts the same ObjectFormDesigner the Form tab mounts, with readOnly true (the harness class this card's own filing used; the Studio public door, a read-only package's Form tab, was not driven) · evidence: CDP getFullAXTree gives each read-only field card the computed description \"To pick up a field, press Space or Enter. While dragging, use the arrow keys to move it. …\" (en) and the zh instructions (zh). A card that \"neither says nor looks draggable\" (objectui#11781) still tells a screen reader how to drag it. Cause: useDraggable always writes aria-describedby to the DndContext instructions, and useSortable's attributes argument cannot remove it. A change needs the card JSX after the spread (aria-describedby unset when readOnly) or the DndContext accessibility props (objectui#11802's surface), both outside this card's fence. It is not inside objectui#11898's scope (keyboard reach and commit place), so it is an independent card in objectui, same file, serial after objectui#11898 · dedupe words: read-only form designer card aria-describedby drag instructions · readOnly field card screen reader pick up instructions · useDraggable aria-describedby read-only",
"carrier: 承接者:无 · noted, not filed (source read only, no public-door reading): the same family, a dnd-kit role description that is English whatever the locale, at the other useSortable call sites: packages/layout NavigationRenderer (its NavigationRenderer.groupedReorder-11626 test pins aria-roledescription \"sortable\"), packages/plugin-dashboard DashboardRenderer, packages/plugin-kanban KanbanImpl cards, packages/plugin-view ManageViewsDialog, and packages/plugin-view ViewTabBar, which hard-codes roleDescription: 'view tab'. If the seat keeps a family card for untranslated dnd-kit a11y strings, these join it · dedupe words: aria-roledescription sortable English · useSortable roleDescription untranslated · view tab roleDescription"
]
}
domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · reviewed against the PR's file list and diff hunks, git merge-tree, check-governed-merges.mjs, its check runs and the dev's report 6053976451, read 2026-10-08T06:37Z.
Shape: draft → main. The first line is Fixes #11872, and Clause-②: no starts a later line.
ObjectFormDesigner.tsx (+8/−1): only SortableField's useSortable call, which now passes attributes.roleDescription. A writable card gets the localized row, and a read-only card gets a blank value, which ARIA does not expose.
metadata-admin/i18n.ts (+4): one new row, engine.studio.designer.fieldRole ("draggable field", and its ZH translation), beside fieldAria in both tables.
A new pin (4 cases) and an @object-ui/app-shell patch changeset.
Clause-② re-read: no export, prop, type member or accepted input changes. no holds.
Governed surface: 0 paths governed, 139 changed lines. That is an ordinary queue landing.
Done-when:
Each card's aria-roledescription is a localized engine string (EN and ZH), passed through useSortable's attributes.roleDescription. Met. In real Chromium the DOM attribute reads "draggable field" (EN) and the ZH row (ZH).
A pin reads the attribute in both locales. Met.
H3, taken: a read-only card is a plain button that opens the inspector. It is not draggable, so it gets no role description. In real Chromium it reads as role button with no role description. Pinned.
The group sections use useDroppable only and carry no role description, so there was no scope question.
Tests:
Local union on abf200c (with main09821b1 merged in): 8 files and 57 tests green, including the three ObjectFormDesigner suites, the read-only wording suite (objectui#11781), both Studio guard suites and the locale suite. The type-check, check:i18n-designer-parity, check:i18n-keys, check:i18n-drift and the derived check:* set are green.
Reverse check: with the attributes argument deleted, 3 cases failed (EN, ZH, read-only, each "sortable") and 1 passed (the group-section control), as predicted. The restore was proven by blob hash.
Overlap: an in-memory git merge-tree against main3683591 is clean.
CI at abf200c: converging at this read: 25 green, 3 expected skips, the rest running. The PR is queued when every check completes.
Filed from this card: objectui#11924. A read-only card still points aria-describedby at the drag instructions. It is serial after objectui#11898 (the same file).
Noted, not filed (no public-door reading): the other useSortable call sites (NavigationRenderer, DashboardRenderer, KanbanImpl, ManageViewsDialog, and ViewTabBar's hard-coded 'view tab') carry an English role description too.
Merged through the merge queue at 2026-10-08T07:10:07Z, after it was queued at 2026-10-08T06:51:09Z.
CI on head abf200c before queueing: 40 of 43 check runs passed, and the 3 skipped are the expected ones.
check-governed-merges.mjs: not governed, +138/−1.
Content check:
fc82a31 is one squash commit on parent 0feabe8. It changes 4 files, +138/−1, the same as the PR.
3 files are blob-identical to the reviewed head abf200c.
metadata-admin/i18n.ts differs only because main moved under it. Its -U0 patch-id matches (8730d433…), and so does the whole diff's (a0a96ab6…).
Card: closed completed by the PR's Fixes. pm:dispatched was removed in the same pass and read back.
On record:
A writable field card's aria-roledescription is the localized "draggable field". A read-only card carries none and reads as the button it is (ACCEPT 6054013050).
objectui#11898 (the same file, keyboard reach on a multi-column canvas) is now released for dispatch, and objectui#11924 follows it.
Activity
objectstack-fleet commentedon Oct 8, 2026
Path: write metadata — the form designer by keyboard and screen reader | 缺项 | P3
Triage: first grade,
bug·priority:p3·domain:ui·area:i18n·pm:queue. Direction: a localized role description on the field cardsTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-08T04:00Z. ⛔ Not a claim, ⛔ not a dispatch.Triage: lands in the form designer's field cards (
useSortable's defaultaria-roledescription) ⇒domain:ui; rationale: a Studio or console surface in objectui. Read on objectuimain4e7dd56ed1.roleDescriptionthroughuseSortable's attributes, with an i18n key in every localeClause-②: no. Patch changeset in objectui.objectstack-fleet commentedon Oct 8, 2026
Claim: PM loop round 9
Session:
session_01DrKzdPdyLLBW3qpZ4vtk7zAccount:
huangyiireneBranch:
claude/issue-11872-form-roledescriptionWorktree:
objectui-issue-11872Domain:
domain:uiSeat:
domain:ui#1File surface (line numbers on
main9844bbf, read 2026-10-08T05:58Z), underpackages/app-shell/src/views/:studio-design/ObjectFormDesigner.tsx:SortableField'suseSortablecall (about:231), passing a localizedattributes.roleDescription. Nothing else in the file.metadata-admin/i18n.ts: one new engine string besideengine.studio.designer.fieldAria, inENGINE_STRINGS_EN(about:2943) andENGINE_STRINGS_ZH(about:5984), and in any other locale table that file keeps for the designer's strings.ObjectFormDesigner.roleDescription-11872.test.tsx), and.changeset/11872-*.md.⛔ Not on it:
formDndAnnouncements.tsand theDndContextprops (objectui#11802's, landed).formCollisionand the keyboard coordinate getter (objectui#11871's, landed; objectui#11898 queued behind this card).packages/i18n/**andpackages/components/src/ui/**.Any file outside this list: the dev reports it before opening the PR (stop on breach; explain in the report).
Container & model:
M,mode:subagent,model: opus(dispatch-gates --tier --repo objectstack-ai/objectuiover these paths: no path-derived mandate; default tier)Clause-②: no
Responsibility:
objectui app-shell Studio form designer: every field card's aria-roledescription is dnd-kit's English "sortable" in every locale, so a screen reader reads English to a zh-CN author | the platform path is right: useSortable takes attributes.roleDescription, and the designer's other strings already come from the engine string tables | every screen-reader author in Studio's Form tab outside EnglishThread-read:
6051909736Serial constraints cleared:
noneblocking.33e3583), and objectui#11871 landed (a476dee) through PR objectui#11897, merged 2026-10-08T05:53:23Z. Triage's grade6051909736asked for this card to run after both.area:i18nin flight: none on the board.metadata-admin/i18n.tsat about:3074and:6113, away from the designer rows at about:2943and:5984. No open PR touchesObjectFormDesigner.tsx. Read 2026-10-08T05:58Z.batch3 working devs, on the maintainer's word in this session's chat (「改为并行派发,batch 3」, 「继续派发」), paced by the shared hourly write budget.Why
Clause-②: no: an attribute value inside the designer and one engine string. No export, prop,@object-ui/typesmember or accepted input changes.objectstack-fleet commentedon Oct 8, 2026
os-dev-report
{ "issue": 11872, "status": "done", "branch": "claude/issue-11872-form-roledescription", "pr": "https://github.com/objectstack-ai/objectui/pull/11920", "session": "session_01DrKzdPdyLLBW3qpZ4vtk7z — the session the PM claim comment 6053415074 names (subagent run, so the parent session id; the container reads CLAUDE_CODE_REMOTE_SESSION_ID=cse_01DrKzdPdyLLBW3qpZ4vtk7z)", "premise_still_valid": true, "summary": "SortableField's useSortable call in ObjectFormDesigner.tsx now passes attributes.roleDescription: the new engine string engine.studio.designer.fieldRole (en 'draggable field', zh '可拖动字段', beside engine.studio.designer.fieldAria in ENGINE_STRINGS_EN and ENGINE_STRINGS_ZH, the only two tables the file keeps) for a writable card, read through the same t(key, locale) / useMetadataLocale() the card's aria-label uses, and a blank value for a read-only package's card, which real Chromium does not expose, so that card reads as the plain button it is (H3). The premise held on main 9844bbf: every card carried dnd-kit's English 'sortable'. The group sections are useDroppable only and carry no role description, so there was no scope question. Draft PR objectui#11920 (first line 'Fixes #11872', a 'Clause-②: no' body line, @object-ui/app-shell patch changeset). The card assignee and labels were not touched; size/path labels on the PR come from the labeler, not from this run.", "tests": "HEAD the gate union ran on: abf200c (git rev-parse --short HEAD; merge commit of origin/main 09821b1 into the branch, no rebase; the merge was clean, and objectui#11899 had touched i18n.ts elsewhere). Commits: dd86396 (fix + pin), 4c4b1af (changeset), abf200c (merge). No commit after the union. || GATES (command → exit → verdict line): [1] os-verify-lock -c \"pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build\" → 0 → \"VERDICT command-exit 0\" (Scope: 29 of 47 workspace projects); the first attempt was \"VERDICT queue-timeout (exit 99)\" behind objectui#11862's union, and the same OS_VERIFY_LOCK_SLOT re-run acquired the lock. [2] os-verify-lock -c union.sh → 0 → \"VERDICT command-exit 0\", containing: pnpm --filter @object-ui/app-shell type-check → 0 (tsc --noEmit && tsc -p tsconfig.test.json; tsconfig.test.json --listFilesOnly lists the new test, 1 hit); pnpm exec vitest run --maxWorkers=2 over 8 files → 0 → \"Test Files 8 passed (8) / Tests 57 passed (57)\". The 8 files: ObjectFormDesigner.roleDescription-11872.test.tsx (new), ObjectFormDesigner.test.tsx, ObjectFormDesigner.dndAnnouncements-11802.test.tsx, ObjectFormDesigner.keyboardDrop-11871.test.tsx, StudioDesignSurface.readOnlyWording-11781.test.tsx, StudioDesignSurface.accessGuard.test.tsx, StudioDesignSurface.pillarNavGuard.test.tsx, metadata-admin/studio-locale.i18n.test.tsx. [3] lock-free, each exit 0: check:control-bytes (\"check-control-bytes: OK\"), check:new-line-citations (\"VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only → exit 0\"), check:changeset-claims (\"No pending changeset names a file this change touches.\"), check:pending-changeset-literals (\"No test source names a pending changeset.\"), check:i18n-designer-parity (\"Every en row has a zh row, and every shared row carries the same placeholders.\"), check:i18n-dead-keys (report; fieldRole not listed), check:i18n-keys (\"Every in-scope call-site key resolves against the en pack\"), check:i18n-drift (\"No designer-table en value changed in this range.\"; 1 key added), check:test-path-roots (OK), check:vi-mock-specifiers (OK). Derived by hand from package.json and the diff (objectui has no dispatch-gates.mjs): node scripts/check-changeset-presence.mjs → 0 (\"3 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)\"), pnpm changeset:check → 0 (\"No changeset declares a major bump.\"), node scripts/check-governed-queue-guard.mjs --test (the 4 paths) → \"NOT GOVERNED\". [4] NOT RUN locally: the repo-wide pnpm lint and the full test farm (CI-owned). eslint --no-inline-config over the 3 touched source files → 0 with 1 warning that was already there (react-hooks/set-state-in-effect on the draft re-sync effect, not a changed line). That is a spot check, not a narrowing claim. || REVERSE CHECK (ablation, after committing the fix in dd86396): node objectstack/scripts/ablation-replace.mjs --delete on the line \"attributes: { roleDescription: … }\" → \"anchor x1 → x0\", blob e6efb31f → 1fe26612, then the pin ran: 3 failed / 1 passed, each failing case (en, zh, read-only) \"Received: aria-roledescription=\\\"sortable\\\"\". The group-sections case stayed green, as predicted: it counts the described elements, which are still the 3 cards. Restore: \"blob == HEAD (e6efb31f593c) and git diff HEAD is empty\". Direction: the pin went red, as predicted. No build or dist was involved: the test imports ./ObjectFormDesigner from src. || H1 CONFIRMED: at the installed @dnd-kit/sortable 10.0.0 and @dnd-kit/core 6.3.1, useSortable passes { ...defaultAttributes, ...userDefinedAttributes } with defaultAttributes = { roleDescription: 'sortable' } into useDraggable, which writes 'aria-roledescription': roleDescription. Its own destructuring default 'draggable' applies only to undefined (source read). The ablation measures both sides: without the argument the cards read sortable, and with it they read the localized row. || H2 CONFIRMED: {...attributes} is spread before aria-label. The returned attributes are role, tabIndex, aria-disabled, aria-pressed, aria-roledescription and aria-describedby, and nothing after the spread writes aria-roledescription. Real Chromium shows the DOM attribute exactly as the row: \"draggable field\" for en and 可拖动字段 for zh. || H3 MEASURED, the answer is no role description, which is taken and pinned: the read-only card is role=button tabindex=0, a click opens the inspector, and it is not draggable (sensors={readOnly ? [] : sensors}). Inside the fenced call, undefined falls through to useDraggable's English 'draggable', so the read-only value is ''. Real Chromium (CDP Accessibility.getFullAXTree, on a dev-only harness mounting the real ObjectFormDesigner, not committed, now removed): static control rows read absent → no roledescription, blank → no roledescription, sortable → sortable. The read-only cards (en and zh) read role button with roledescription null, and the writable cards read roledescription \"draggable field\". Instrument note: the CDP roledescription property returns non-ASCII values as their UTF-8 bytes read as Latin-1 (code units e5 8f af … for 可拖动字段), including for static zh and French controls. That is DevTools serialization, while the DOM attribute is exact. What a real screen reader speaks for zh is NOT MEASURED (no screen reader in the container). The wording suggestion was kept. || FILES_CHANGED: packages/app-shell/src/views/studio-design/ObjectFormDesigner.tsx (the SortableField useSortable call only), packages/app-shell/src/views/metadata-admin/i18n.ts (the fieldRole row in EN with a 2-line comment, and in ZH), packages/app-shell/src/views/studio-design/ObjectFormDesigner.roleDescription-11872.test.tsx (new, 4 cases: en, zh, read-only, group sections only drop zones), .changeset/11872-form-roledescription.md (new, @object-ui/app-shell patch). All are inside the claim's file surface. || DEVIATIONS: (1) The order's gate glob \"*.guard.test.*\" matched no file in either directory, so the two StudioDesignSurface.*Guard.test.tsx files ran in its place. No metadata-admin/i18n*.test file exists, so studio-locale.i18n.test.tsx ran instead. StudioDesignSurface.readOnlyWording-11781.test.tsx was added because the read-only card changed. (2) The changeset names the zh wording as \"its zh translation\" rather than the literal string, per AGENTS.md #-1 (English-only docs). (3) The commit trailers are the model-free pair AGENTS.md rules, not the harness reminder's model-named line. (4) A dev-only browser harness (apps/console/rd-11872.html, apps/console/src/rd-11872.tsx, node_modules/.drive-11872*.mjs) was created untracked and then deleted. My own vite on :5197 was killed by its pids, and git status is clean. (5) The worktree is left in place for the seat, per the order.", "mcp_calls": "0 — no MCP GitHub tool was called (reads went through gh api REST: the card, its comments, objectui#11871 comments for the report format, objectui#11898, PR objectui#11920 read-back)", "api_writes": "2 relay writes, within the order's budget of three: (1) pr_create through fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches → run 37738218435 success → POST /repos/objectstack-ai/objectui/pulls (draft forced), read-back \"7020 byte(s) sent, 7020 stored — identical\"; (2) this os-dev-report comment through post-stamped.mjs: POST /repos/objectstack-ai/objectui/issues/11872/comments. Plus git push of the branch (the probe and 3 pushes of the same branch; not REST). Zero label writes, zero assignee writes, no PATCH.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: real Chromium on a dev-only harness that mounts the same ObjectFormDesigner the Form tab mounts, with readOnly true (the harness class this card's own filing used; the Studio public door, a read-only package's Form tab, was not driven) · evidence: CDP getFullAXTree gives each read-only field card the computed description \"To pick up a field, press Space or Enter. While dragging, use the arrow keys to move it. …\" (en) and the zh instructions (zh). A card that \"neither says nor looks draggable\" (objectui#11781) still tells a screen reader how to drag it. Cause: useDraggable always writes aria-describedby to the DndContext instructions, and useSortable's attributes argument cannot remove it. A change needs the card JSX after the spread (aria-describedby unset when readOnly) or the DndContext accessibility props (objectui#11802's surface), both outside this card's fence. It is not inside objectui#11898's scope (keyboard reach and commit place), so it is an independent card in objectui, same file, serial after objectui#11898 · dedupe words: read-only form designer card aria-describedby drag instructions · readOnly field card screen reader pick up instructions · useDraggable aria-describedby read-only", "carrier: 承接者:无 · noted, not filed (source read only, no public-door reading): the same family, a dnd-kit role description that is English whatever the locale, at the other useSortable call sites: packages/layout NavigationRenderer (its NavigationRenderer.groupedReorder-11626 test pins aria-roledescription \"sortable\"), packages/plugin-dashboard DashboardRenderer, packages/plugin-kanban KanbanImpl cards, packages/plugin-view ManageViewsDialog, and packages/plugin-view ViewTabBar, which hard-codes roleDescription: 'view tab'. If the seat keeps a family card for untranslated dnd-kit a11y strings, these join it · dedupe words: aria-roledescription sortable English · useSortable roleDescription untranslated · view tab roleDescription" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Review: ACCEPT — PR objectui#11920 (head
abf200c)domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· reviewed against the PR's file list and diff hunks,git merge-tree,check-governed-merges.mjs, its check runs and the dev's report6053976451, read 2026-10-08T06:37Z.main. The first line isFixes #11872, andClause-②: nostarts a later line.6053415074's surface.ObjectFormDesigner.tsx(+8/−1): onlySortableField'suseSortablecall, which now passesattributes.roleDescription. A writable card gets the localized row, and a read-only card gets a blank value, which ARIA does not expose.metadata-admin/i18n.ts(+4): one new row,engine.studio.designer.fieldRole("draggable field", and its ZH translation), besidefieldAriain both tables.@object-ui/app-shellpatch changeset.noholds.aria-roledescriptionis a localized engine string (EN and ZH), passed throughuseSortable'sattributes.roleDescription. Met. In real Chromium the DOM attribute reads "draggable field" (EN) and the ZH row (ZH).buttonwith no role description. Pinned.useDroppableonly and carry no role description, so there was no scope question.abf200c(withmain09821b1merged in): 8 files and 57 tests green, including the threeObjectFormDesignersuites, the read-only wording suite (objectui#11781), both Studio guard suites and the locale suite. The type-check,check:i18n-designer-parity,check:i18n-keys,check:i18n-driftand the derivedcheck:*set are green.attributesargument deleted, 3 cases failed (EN, ZH, read-only, each "sortable") and 1 passed (the group-section control), as predicted. The restore was proven by blob hash.git merge-treeagainstmain3683591is clean.abf200c: converging at this read: 25 green, 3 expected skips, the rest running. The PR is queued when every check completes.aria-describedbyat the drag instructions. It is serial after objectui#11898 (the same file).useSortablecall sites (NavigationRenderer,DashboardRenderer,KanbanImpl,ManageViewsDialog, andViewTabBar's hard-coded'view tab') carry an English role description too.huangyiirene.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Landed: PR objectui#11920 →
mainfc82a31domain:uiseat 1 ·session_01DrKzdPdyLLBW3qpZ4vtk7z· verified 2026-10-08T07:10Z.abf200cbefore queueing: 40 of 43 check runs passed, and the 3 skipped are the expected ones.check-governed-merges.mjs: not governed, +138/−1.fc82a31is one squash commit on parent0feabe8. It changes 4 files, +138/−1, the same as the PR.abf200c.metadata-admin/i18n.tsdiffers only becausemainmoved under it. Its-U0patch-id matches (8730d433…), and so does the whole diff's (a0a96ab6…).completedby the PR'sFixes.pm:dispatchedwas removed in the same pass and read back.aria-roledescriptionis the localized "draggable field". A read-only card carries none and reads as the button it is (ACCEPT6054013050).Generated by Claude Code
fix(app-shell): form designer field cards announce a localized role, …