Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
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

Closed

Description

@objectstack-fleet

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


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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 main 4e7dd56ed1.

  2. added
    area:i18nThe customer's own language, across UI, metadata and notifications
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 8, 2026
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 9
    Session: session_01DrKzdPdyLLBW3qpZ4vtk7z
    Account: huangyiirene
    Branch: claude/issue-11872-form-roledescription
    Worktree: objectui-issue-11872
    Domain: domain:ui
    Seat: domain:ui#1
    File surface (line numbers on main 9844bbf, read 2026-10-08T05:58Z), under packages/app-shell/src/views/:

    • 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.

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Review: ACCEPT — PR objectui#11920 (head abf200c)

    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.
    • Scope: 4 files, +138/−1, inside claim 6053415074's surface.
      • 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 main 09821b1 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 main 3683591 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.
    • PR assignee: set by this seat to huangyiirene.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#11920 → main fc82a31

    domain:ui seat 1 · session_01DrKzdPdyLLBW3qpZ4vtk7z · verified 2026-10-08T07:10Z.

    • 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.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions