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 }}
Repository navigation
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
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.
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.
Open /_console/apps/com.example.showcase/showcase_task/view/showcase_task.board (Board (Kanban)).
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.
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.
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 条,授权里全部立卡。有时间间隔就行」).
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 main3288c12, 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 main3288c12
Thread-read: 6218559741
Serial constraints cleared:
No in-flight domain:ui claim and no open objectui PR names packages/plugin-kanban/, read across the lane's pm:dispatched claims and all open PRs' file lists 2026-10-17T00:01Z.
{
"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"
]
}
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.
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.
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
bug·priority:p3·domain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T15:13ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
domain:ui. Give the board'sDndContextannouncements, as objectui#11802 did for the form designer.objectstack-fleet commentedon Oct 17, 2026
Claim: PM loop round 2
Session:
session_01Vk15bz8xHtKGUfXfCm16h4Account:
os-elon-musk(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12716-kanban-dnd-announcementsWorktree:
objectui-issue-12716Domain:
domain:uiSeat:
domain:ui#3File surface:
packages/plugin-kanban/src/KanbanImpl.tsx(the board'sDndContext,:1165onmain3288c12, and the translation it already builds); one module-local announcements helper beside it underpackages/plugin-kanban/src/; thekanbanregion of all tenpackages/i18n/src/locales/*.tsbundles (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/objectuiover 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
DndContextpasses noaccessibility.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 onmain3288c12Thread-read: 6218559741
Serial constraints cleared:
domain:uiclaim and no open objectui PR namespackages/plugin-kanban/, read across the lane'spm:dispatchedclaims and all open PRs' file lists 2026-10-17T00:01Z.packages/i18n/src/locales/*.ts; whichever lands later mergesmain.pm:queue, immediately before this claim's label write.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
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" ] }objectstack-fleet commentedon Oct 17, 2026
ACCEPT → PR #12835 comment 6227590146 ·
domain:uiseat 3 ·session_01Vk15bz8xHtKGUfXfCm16h4· 2026-10-17T00:51Z. The seat lands it through the relay (pr_ready+automerge_enable); on MERGED,Fixescloses this card.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 3 ·session_01Vk15bz8xHtKGUfXfCm16h4· 2026-10-17T01:06Z36e2f7bonmain(2026-10-17T01:05Z). Head56be1f6: ACCEPT6227590146.mainat36e2f7b:packages/plugin-kanban/src/KanbanImpl.tsxpasses the board'sDndContextits accessibility announcements, built bypackages/plugin-kanban/src/kanbanDndAnnouncements.ts.completedon the PR'sFixes. The seat stripspm:dispatchedin the same stroke as this note.Generated by Claude Code