…re (objectui#8826)
Dragging a card inside its own column reordered the board on the spot and
recorded that order nowhere: the same-column branch notified no one, the
mover returns early when the source and target columns are equal, and the
write body carries the grouped field and no positional key. The next data
round-trip re-synced the mirror from `columns` and put the card back, with
no error, no toast and no rollback animation — a success the user watched
happen and then watched disappear.
Ruled letter B: the board stops claiming a capability it does not have.
- same-column drop: no optimistic reorder; the card returns to its position.
- cross-column move: the membership change stays and is still persisted; the
landing index is still handed to `onCardMove` and still discarded there, but
it is no longer painted — the branch appends instead of splicing at the
dropped slot, so no position is claimed that the round-trip cannot reproduce.
Letter A (persist the order) was refused as a STARTING POINT, not on the
merits: there is no declared ordering slot in `@objectstack/spec` to write one
to, and inventing a UI-local one is Prime Directive 5's workaround. Letter C
(document the false success) was refused as formalising it — the two README
lines that advertised in-column reordering say what is true instead.
Pins, both legs, in `sameColumnDropIsNotClaimed-8826.test.tsx`: a same-column
drop leaves DOM order equal to the data order on both data ownerships, and the
lit control shows a cross-column move still emitting the new membership
snapshot and still reaching the wire.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EpHzwH4wTy5sd7ibkD2yq
Fixes #8826
Ruled on the card as batch #141 item 3, letter B, maintainer 「同意」: the board stops claiming a capability it does not have. The fence the ruling asked this body to carry, verbatim: 「order persistence is not a capability of this board; a spec card would be its entry」.
What changed
KanbanImpl'shandleDragEnd:persistCardMove. The landing index is still computed and still handed toonCardMove, where it is still discarded; what changed is that it is no longer painted. The branch appends to the target column instead of splicing at the slot the card was released over, so the board claims membership and says nothing about position.No new prop, no new persistence path, no new authorable key —
Clause-②: noholds, and this is a strict narrowing of a claim rather than a widening of any surface.Two README bullets advertised in-column reordering as a feature. They now say what is true. Letter C was refused for formalising the false success; leaving it in the docs would have been letter C by the back door.
The ruling's execution note reads 「
ObjectKanban.tsx: the same-column branch ofhandleDragEnd…」. Re-derived by symbol (git grep -nw handleDragEnd):ObjectKanban.tsxhas nohandleDragEnd. The drag handler and the optimistic reorder live inKanbanImpl.tsx;ObjectKanban.tsxownshandleCardMove, whose_newIndexparameter andvoid _newIndexdiscard are exactly where the ruling says they are and are unchanged by this branch. Everything the ruling asked for is done, one file over from where it says.KanbanImpl's mirror-sync timing (objectui#8534 / PR #8825) is untouched. The mirror itself stays — the cross-column optimistic write still needs it, which is now what defends it in that card's pin.The two pins — both legs, in one file
packages/plugin-kanban/src/__tests__/sameColumnDropIsNotClaimed-8826.test.tsx, on the recorder objectui#8826 was measured with (a module mock ofKanbanImplcapturing thecolumnsprop per render, plus a capture of the board's realonDragEnd):columnssnapshot and reaches noupdate.update('task', 'a', { status: 'in_progress' })). Without this leg, LEG 1 would read green on a board whose drag path was broken outright.The fixture puts two cards in the source column and asks for their reverse order, so "did not move" is distinguishable from "nothing was driven".
Reverse verification — two arms, mutation proven on disk, restore proven by hash
Run from the committed fix (
1f1ec56); every arm restores withgit checkout HEAD --under a trap with absolute paths, and each restore is verified by the blob hash plus an emptygit diff HEAD, never by an exit code.columnsReachDomSameCommit-8534. LEG 2 stayed green, so the instrument distinguishes the defect from a dead board.crossColumnLandingPosition-8826; LEG 1 and everything else stayed green.KanbanImpl.tsxHEAD blob5cd6f3b0eaf34acafa223271f8619b4de09366e1; mutated blobs7a93f0098ec53e68f839bf8fb4fc8823040b3575and3f9b95415364fc026770ba297b4c30a76f2c3dea; restored blob reproduces the HEAD blob byte for byte on both arms, and the tree was verified clean afterwards. The mutation scripts assert their anchor is present exactly once before writing, so a no-op edit aborts instead of passing as a run.Verification
All run at
1f1ec56, exit codes captured to files before being read, never through a pipe. Heavy runs serialized through the shared verify lock withOS_VERIFY_LOCK_SLOT=objectui-8826, verdicts read from its printedVERDICT command-exitline.pnpm exec vitest run packages/plugin-kanban/(repo ROOT)pnpm --filter @object-ui/plugin-kanban type-checktsc --noEmitandtsc -p tsconfig.test.json, the second being what covers the test filespnpm --filter @object-ui/plugin-kanban lintcheck:changeset-presencecheck:doc-snippets·check:doc-examples·check:readme-exportscheck:doc-types·check:doc-fences·check:doc-example-idscheck:control-bytes·check:new-line-citations·check:test-path-roots·check:unreferenced-sourcescheck:vi-mock-specifiers·-inherit·-override-shapevi.mockcall sitescheck:changeset-claims·check:changeset-no-major·check:handler-key-readscheck-governed-queue-guard --testA dependency-closure build (
--filter '@object-ui/plugin-kanban^...' build) and the doc gates' own 35-task scoped build were both run to a real 0 first; the type-check and the three README/doc gates were failing only for want of them.Acceptance notes
columnsprop alone: each column's cards are a record-ordered subsequence, and nothing carries their global interleaving. Appending is therefore the only slot the board can offer without claiming to know where the round-trip will sort the card — it is not itself a promise that the card will stay last. On the ownership that re-renders, the prop-driven re-sync corrects it inside the same commit; on the external ownership, the card sits at the end of the target column until the parent reflows. What is removed is the claim that the user's drop slot was honoured.KanbanEnhanced.tsxcarries a byte-for-byte twin of the removed reorder. Left alone deliberately: it has zero non-test importers, its registry key was retired by objectui#8257, and the packageexportspublishes only.and./style.css, so it is unreachable from outside this package. Dead code is not a fileable class. Successor: none — no open PR touches this package.useQuickAddReorderhook keeps its own local-order state. It is a published export with no in-repo consumer and is not on this board's drag path. Successor: none.packages/plugin-kanban/src/. This branch also touchespackages/plugin-kanban/README.md,packages/components/src/renderers/complex/README-KANBAN.mdand.changeset/. The changeset is mechanically required. The two README bullets are the docs half of this very behaviour change (AGENTS.md commandment Add automated testing infrastructure and CI/CD workflows #2), and each was a one-line claim of the exact capability the ruling removes; shipping them unchanged would leave the board advertising in-column reordering in print.Generated by Claude Code