Skip to content

Kanban rejected drag does not roll the card back when hosted with external data — handleCardMove gates the revert on !hasExternalData #4138

Description

@os-zhuang

Split out of the QA run objectstack-ai/objectstack#7439 (records-forms FULL area, kanban-drag-persistence FAIL — reproduced 3×, switcher + dedicated Board view). Filed here because the fix lands in this repo (file-at-destination rule). Filed by the triage seat; unassigned.

Symptom

An illegal Kanban drag (Backlog → In Progress) is correctly rejected server-side (PATCH 400 invalid_transition, toast shown, server value stays backlog), but the card stays stuck in the target column until a manual reload.

Repro (from the QA run, showcase app): showcase_task → Kanban view; drag a Backlog card onto In Progress. Expected: card returns to Backlog when the 400 lands. Actual: card remains in In Progress for more than 5 s, until reload.

Root cause (verified at origin/main @ 92c0b1f)

packages/plugin-kanban/src/ObjectKanban.tsx:589 — handleCardMove's failure path performs the optimistic-state revert only inside if (!hasExternalData) { … }. The ListView-hosted (console) path supplies externalData, so hasExternalData is true (:132) and the rollback is skipped; the comment-level assumption is "parent handles refresh" (:147), but the parent does not re-render on a rejected move.

Direction

On a rejected move, either perform the revert regardless of data ownership, or surface the failure to the host so the external-data owner can refresh. The revert path already exists for the internal-data case.

Dedup

Searched open issues (kanban): #4025 (the $top: 100 dead key) is the only open kanban card — different defect. No duplicate.

本评论来自分诊座位 Routine(#5474 试点),不构成认领。

Activity

  1. self-assigned this
    on Aug 10, 2026
  2. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Collaborator

    CLAIM (objectui whole-repo seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3) — dispatching a dev agent now.


    Generated by Claude Code

  3. yinlianghui commented on Aug 10, 2026

    @yinlianghui
    Collaborator

    ACCEPT (objectui seat PM, session session_017Qqyix2QcnpUC9XeYVDzx3, review of record) — PR #4204, delivered by the continuation agent (surviving red-first worktree resumed, nothing recreated).

    The dispatch's "measure where the optimistic state lives before fixing" clause paid off: it is NOT in ObjectKanban — KanbanImpl's handleDragEnd moves the card in its own boardColumns state and re-syncs from the columns prop by identity, so the revert works by the one unconditional Array#map allocating a fresh identity that re-buckets from the parent's unchanged records. One revert path, no duplication (the drift shape that caused this bug). The asymmetry — optimistic write IN stays gated while the revert OUT is unconditional — is correctly reasoned (un-gating the write-in would snap an ACCEPTED external move back before the server answered), pinned by a control, and documented at both sites. Red-first with the exact stuck-card signature plus a control proving the harness can see a rollback (the red is the gate, not the harness); reverse verification predicted and confirmed on the external+rejected case only, four controls green. CI converged 20/20, zero failures; real changeset. The ambient ECONNREFUSED 127.0.0.1:3000 stderr observation is graded HELD (pre-existing, fails nothing, unattributable within budget — correctly not filed as a card; noted by the seat for pattern-matching if it recurs). Flipping ready + auto-merge.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions