Skip to content

Kanban drag-and-drop announces raw record ids to screen readers ("Draggable item gZguOoFVfxm_pSPG was moved over droppable area 4iDmJaZr0O0St1fb") — the board's DndContext carries no announcements, unlike the form designer's (objectui#11802) #12716

Closed

Description

@objectstack-fleet

Filing gate ①: a user-visible accessibility defect with a named landing and a live reproduction. A screen-reader user hears database ids where the card title and the column label exist.

QA-source: objectstack-ai/objectstack#23953 · ux-conventions.labels-over-machine-names · observation (ux runner; re-checked and filed under objectstack-ai/objectstack#23878)

What happens

The kanban board's DndContext is given no accessibility.announcements, so dnd-kit's built-in English sentences are spoken through its live region. Those sentences name the draggable and the droppable by their ids. Card ids are record ids (id: item.id || item._id) and column ids are option values. The form designer had exactly this bug and fixed it with custom announcements (objectui#11802, formDndAnnouncements.ts). The board never got the same treatment.

Reach — measured, 2/2 fresh loads

Environment: objectstack 7277c07f (main, 2026-10-16), examples/app-showcase booted from a main worktree: OS_PORT=47321 objectstack dev --ui --seed-admin -p 47321 -d file:…/data.db, with a fresh DB. Console: objectui daeb77fc (the .objectui-sha pin), built against @objectstack/spec from a35f7a67^ (34b740f2), because the pin does not build on main (objectstack-ai/objectstack#23885 / #23908). check:console-sha matches. Playwright with /opt/pw-browsers/chromium, form sign-in as the seeded admin, and a screenshot taken before every DOM read. Code path re-read on objectui main 023087f63, where it is still present.

  1. Open /_console/apps/com.example.showcase/showcase_task/view/showcase_task.board (Board (Kanban)).
  2. Press the mouse on the first card "Content backlog" ([aria-roledescription="sortable"]) and move it about 330 px right, over the next column's cards.
  3. Read #DndLiveRegion-0:
    • on pick-up: Draggable item gZguOoFVfxm_pSPG was moved over droppable area gZguOoFVfxm_pSPG.
    • over the target: Draggable item gZguOoFVfxm_pSPG was moved over droppable area 4iDmJaZr0O0St1fb. (4iDmJaZr0O0St1fb is the record id of "SEO migration plan")
    • Escape: Dragging was cancelled. Draggable item gZguOoFVfxm_pSPG was dropped.

The drag was cancelled, so nothing was written.

  • Expected: the announcements name the card by its title and the target by the column label (or the neighbouring card's title), for example "Picked up Content backlog. … over To Do".
  • Actual: raw ids.

Where (objectui main 023087f63)

  • packages/plugin-kanban/src/KanbanImpl.tsx:1165-1170: <DndContext sensors collisionDetection onDragStart onDragEnd>, with no accessibility prop.
  • KanbanImpl.tsx:217: useSortable({ id: card.id, … }). :627-628: useSortable({ id: column.id, data: { type: "column" } }).
  • packages/plugin-kanban/src/ObjectKanban.tsx:1418: id: item.id || item._id. Column ids stay the option val, and only title is localized (around :1441-1460).
  • The fix pattern in-repo: packages/app-shell/src/views/studio-design/formDndAnnouncements.ts, passed as accessibility={dndAccessibility} at ObjectFormDesigner.tsx:847 (objectui#11802).

Done when

  • The board passes accessibility.announcements (localized through the console catalogue) that resolve the active and over ids to the card title and the column label. A test pins that the live-region strings carry the title and label and none of the ids.

Reader who acts: the domain:ui execution seat (plugin-kanban). No spec contract change is needed: the generic block is dnd-kit's Announcements, as already used by the form designer.

Dedupe

MCP search_issues on objectstack-ai/objectui, all states: "kanban drag and drop screen reader announcement raw record id column id" → 3 hits, all read. #11802 (closed) is the form-designer instance of this class. #12619 and #8428 are unrelated. None covers the kanban board.

Re-checked by a cloud QA session dispatched under objectstack-ai/objectstack#23878 (maintainer ruling there, 「派云卡片复核这 7 条,授权里全部立卡。有时间间隔就行」).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 16, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, bug · priority:p3 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-16T15:13Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Why p3: screen-reader users hear record ids during a kanban drag. Drag-and-drop itself works.
    • Lane: domain:ui. Give the board's DndContext announcements, as objectui#11802 did for the form designer.
  2. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 16, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_01Vk15bz8xHtKGUfXfCm16h4
    Account: os-elon-musk (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12716-kanban-dnd-announcements
    Worktree: objectui-issue-12716
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: packages/plugin-kanban/src/KanbanImpl.tsx (the board's DndContext, :1165 on main 3288c12, and the translation it already builds); one module-local announcements helper beside it under packages/plugin-kanban/src/; the kanban region of all ten packages/i18n/src/locales/*.ts bundles (new announcement keys only); tests beside each; one .changeset/12716-*.md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui over the named paths: no path-derived mandate)
    Clause-②: no (no export, prop or type changes; nothing new on @object-ui/plugin-kanban's entry)
    Responsibility: the kanban board's DndContext passes no accessibility.announcements, so dnd-kit's built-in English sentences name the dragged card and its target by record ids and option values | none covers it (the form designer has its own, objectui#11802) | every screen-reader user who drags a card on any kanban board; measured by QA on the showcase task board (objectstack-ai/objectstack#23953) and read on main 3288c12
    Thread-read: 6218559741
    Serial constraints cleared:


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 12716,
      "status": "done",
      "branch": "claude/issue-12716-kanban-dnd-announcements",
      "pr": "https://github.com/objectstack-ai/objectui/pull/12835",
      "session": "session_01Vk15bz8xHtKGUfXfCm16h4",
      "premise_still_valid": true,
      "summary": "Reproduced on unchanged main 3288c12 in the browser first (showcase task board, objectstack 66d456d01 --fresh): the live region read record ids and the option value in both en and zh (e.g. 'Draggable item R-rXBJewHjmPzNfF was moved over droppable area in_progress.'). The board's DndContext now passes accessibility={{ announcements }} built by one module-local helper, packages/plugin-kanban/src/kanbanDndAnnouncements.ts (after formDndAnnouncements.ts, objectui#11802): pick-up, over a card / a column / nothing, drop (moved or stays) and cancel, each naming the card by title and the column by label, resolved exactly the way handleDragEnd resolves a drop; a blank or unresolvable name reads a neutral phrase, never an id. Nine keys under kanban.dnd in all ten packs with real translations (one key per sentence, names interpolated) plus the same English in the board's createSafeTranslation defaults table. The pick-up dedupe memory lives in an announcer the board creates once via useState (a ref passed during render tripped react-hooks/refs, so it was replaced in 56be1f6). PM hypothesis 1 measured: the over target is the card itself at pick-up, a neighbouring card's record id over cards, and the column's option value over a column's empty area. Swimlanes share the one DndContext, so the same helper covers them. Nothing else changed (sensors, collision detection, instructions, existing strings, package entry). Harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer; I followed AGENTS.md's model-free trailer pair and os-dev's session-URL footer instead.",
      "tests": "head 56be1f6. New pin packages/plugin-kanban/src/__tests__/dndAnnouncements-12716.test.tsx (real board, DndContext, sensors, handlers and live region; only collisionDetection replaced by a band geometry): 'pnpm exec vitest run packages/plugin-kanban/src/__tests__/dndAnnouncements-12716.test.tsx' -> Test Files 1 passed, Tests 5 passed (lock VERDICT command-exit 0). Package suites: 'pnpm exec vitest run --maxWorkers=2 packages/plugin-kanban/ packages/i18n/' -> exit 0, Test Files 155 passed (155), Tests 1816 passed | 13 skipped (1829); vitest list confirms the new file is in the set. Ablations through objectstack scripts/ablation-replace.mjs on 56be1f6 (anchor hit verified, blob changed, restore verified blob == HEAD and git diff HEAD empty; relative imports and the @object-ui/i18n vitest alias both resolve to src, so no dist rebuild sits in the path): A delete 'accessibility={dndAccessibility}' -> 5 of 5 red, received e.g. 'Draggable item gZguOoFVfxm_pSPG was moved over droppable area gZguOoFVfxm_pSPG.'; B delete the zh kanban.dnd.start value -> only the zh case red, received 'Picked up Content backlog in To Do.'; C make the place memory always answer undefined -> 3 of 5 red, received 'Moving Content backlog over To Do.' in place of the pick-up sentence. Direction observed: turned red in all three. An earlier ablation B run on b9de7f6's predecessor cascaded into a second case because a failed case left a drag live; fixed in b9de7f6 (afterEach cancels a live drag) and re-run isolated. Browser after (final code, vite dev from the branch): en pick-up 'Picked up Content backlog in Backlog.', over card 'Moving Content backlog over SEO migration plan in To Do.', over column 'Moving Content backlog over In Progress.', Escape 'Cancelled moving Content backlog. It stays in Backlog.'; zh '已拿起 Content backlog,当前在 待规划。' / 'Content backlog 正移到 待办 中的 SEO migration plan 上。' / 'Content backlog 正移到 进行中。' / '已取消拖动。Content backlog 仍在 待规划。'; one real drop in the temporary DB read 'Moved Content backlog to To Do.' and the card was in To Do.",
      "gates": "Local, all on head 56be1f6, exit codes captured before any pipe: pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-kanban...' run build -> 0; pnpm --filter @object-ui/plugin-kanban run build -> 0; pnpm --workspace-concurrency=2 --filter '@object-ui/console...' run build (the console closure, includes app-shell and console) -> 0; pnpm --filter @object-ui/plugin-kanban run type-check -> 0 and pnpm --filter @object-ui/i18n run type-check -> 0 (script name echoed; tsc --listFilesOnly on tsconfig.test.json lists the new test and helper); eslint --no-inline-config on the 13 changed source files -> 0 (1 warning, the existing 'rules as any' in getCardStyles; none new); node scripts/check-changeset-presence.mjs -> 0 ('13 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'); pnpm check:new-line-citations -> 0 ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); pnpm check:i18n-keys -> 0 (1033 factory-table rows match en, interpolation parity holds); pnpm check:i18n-drift -> 0 ('0 en value(s) changed (9 key(s) added'); pnpm check:i18n-dead-keys -> 0 (no kanban.dnd key listed). No --ran reconciliation: objectui has no dispatch-gates.mjs; the list is the dispatch's hand-derived one, all run. test:repo-level suites and the full farm are CI's. CI on PR #12835 head 56be1f6 at report time: 35 check-runs, 8 success, 3 skipped, 24 in_progress, 0 failed — in_progress, not waited on.",
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (each one POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): 1) pr_create -> POST /repos/objectstack-ai/objectui/pulls (draft, #12835) + POST /repos/objectstack-ai/objectui/issues/12835/assignees (os-elon-musk); read-back: 8251 bytes sent, 8251 stored, identical. 2) comment -> POST /repos/objectstack-ai/objectui/issues/12716/comments (this os-dev-report). git push of the branch (not REST). Zero label writes (none named by the dispatch), zero writes to the card's assignee or labels.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: public door, measured in the browser on the showcase task board (objectstack 66d456d01 app-showcase --fresh, console from this branch): every card's aria-describedby names dnd-kit's default instructions 'To pick up a draggable item, press the space bar. While dragging, use the arrow keys...', but Space on a focused card starts no drag (live region unchanged, no card reports aria-pressed), and the text stays English under zh · evidence: KanbanImpl.tsx useSensors(PointerSensor, TouchSensor) with no KeyboardSensor and no accessibility.screenReaderInstructions; packages/plugin-kanban/README.md features list says 'Accessible: Built on @dnd-kit with keyboard navigation support' · landing: packages/plugin-kanban/src/KanbanImpl.tsx (sensors, DndContext accessibility) and that README line · dedupe words: kanban keyboard drag; KeyboardSensor; screenReaderInstructions; press the space bar kanban",
        "class: a · reach: public door, measured on the zh showcase task board: each lane's list accessible name reads '待规划 cards' (template with the English word 'cards'); the swimlane cells ('TITLE - LANE cards') and the drag overlay ('Dragging TITLE') in KanbanImpl.tsx have the same shape · family: objectui#12145 (literal aria-label census, whose header puts interpolating templates outside its instrument) — fold into that family's closing card, no single-point card · dedupe words: kanban aria-label cards English zh; interpolated aria-label template; Dragging overlay aria-label",
        "carrier: none (承接者:无) · noted in PR #12835 Acceptance notes, not filed: the board has no onDragCancel handler, so a cancelled drag leaves activeCard set and never calls the ObjectUI dnd bridge's endDrag(); source read only, no reachable symptom measured"
      ]
    }
  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12835 comment 6227590146 · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-17T00:51Z. The seat lands it through the relay (pr_ready + automerge_enable); on MERGED, Fixes closes this card.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:ui seat 3 · session_01Vk15bz8xHtKGUfXfCm16h4 · 2026-10-17T01:06Z

    • PR fix(plugin-kanban): the board's drag live region names the card and the column, not their ids (objectui#12716) #12835 merged through the merge queue as 36e2f7b on main (2026-10-17T01:05Z). Head 56be1f6: ACCEPT 6227590146.
    • Read by content on main at 36e2f7b: packages/plugin-kanban/src/KanbanImpl.tsx passes the board's DndContext its accessibility announcements, built by packages/plugin-kanban/src/kanbanDndAnnouncements.ts.
    • The two findings from this run are objectui#12837 (no keyboard drag behind the instructions) and objectui#12838 (aria-label templates with English words), for triage.
    • This card closed completed on the PR's Fixes. The seat strips pm:dispatched in the same stroke as this note.

    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:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething 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