Skip to content

console: a state-machine status offers every option in the edit form and every column on the board, and says "任务状态流转无效" only after submit, without naming the allowed next states #25933

Closed

Description

@objectstack-fleet

Filing-gate: ① product defect · reach: the showcase task edit form and the Task Board, as admin; the server's task_status_flow refusal arrives as designed
Reader: objectui triage seat → the lane that owns the record form's select field and plugin-kanban; the input is the object's state_machine validation (field, transitions), which the object metadata already serves
Duplicate search: semantic search "state machine status select dropdown offers transitions not allowed invalid transition only shown after submit" in objectui, incl. closed → 0 hits.

Measured during an exploratory showcase walkthrough on 2026-10-23: objectstack main b9c7a8f (PORT=3000 pnpm dev, stock showcase), objectui main 6fb31dc console (Vite dev, :5180), locale zh-CN.

What happens

showcase_task.status is governed by an error-severity state_machine rule. For example, backlog → [todo] and in_review → [done, in_progress].

  • Edit form: on a backlog task, the Status select lists all five values. If you pick 已完成 and save, the form answers 请检查表单中标记的字段:状态 / 任务状态流转无效。 It does not say which states are allowed, so the user has to guess again.
  • Board: a backlog card can be dragged onto any column. The drop is refused by the server and rolled back with a toast, which works correctly. Nothing during the drag marks the columns the card cannot enter.

The server side is right. The cost falls on the client: an invalid choice is offered first and refused afterwards.

Expected

When the object declares an error-severity state_machine on the field, the form's select offers the current value plus the declared next states (or shows the others disabled, with the reason). The board marks the non-target columns while a card is dragged. A severity: 'warning' machine, such as project_health_progression, keeps every option.

Session: https://claude.ai/code/session_01A48x1XYLTmiM2pgQxsgeKJ

Activity

  1. objectstack-fleet commented on Oct 23, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: lands in the record form's select field and plugin-kanban (read the object's error-severity state_machine validation); rationale: the server refuses invalid transitions correctly; the client offers them first and the refusal does not name the allowed next states.
    Path: records · 写入规则在服务端兑现 | records-forms.validation-rule-type-matrix (P1, passes server-side) | step ②
    Grade: priority:p3 — nothing wrong is stored; the cost is a retry.
    Type: Bug

    Session: session_01AkmasAfXt3cu83xGzkuKhe (objectstack triage, run on the maintainer's request in the project thread)


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 25, 2026

    @objectstack-fleet
    ContributorAuthor

    Pause lifted · domain:ui seat 4 (vacant; written by its signed-off session session_01XWGTsmqrtrt1Zqh1ioWqsT) · 2026-10-25T14:12Z

    The os-sales tenure held this card back for the planned objectui → objectstack repo move. The maintainer lifted that pause in this project on 2026-10-25T14:08Z: 「你放回队列就写」. The card already carries pm:queue and is dispatchable under the normal rules. Nothing dispatches it now, because the domain:ui lane is off duty, so a future seat picks it up from the queue.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Transferred 2026-10-26T04:46Z from objectstack-ai/objectui#13400, with its comments and timeline; the old URL redirects here. objectui moved into this repository as the ui/ workspace (epic #25677) and is being archived (#25854). The body is left exactly as filed.

    Paths. The body names no file paths; the UI code it describes lives under ui/ here (the Console is ui/apps/console, the libraries ui/packages/*).

    No stale pm:dispatched or assignee to strip.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first touch — bug · priority:p3 · domain:ui · area:records · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-26T07:00Z

    ⛔ Not a claim, ⛔ not a dispatch. First touch after the transfer from objectui. The card had no triage audit comment there or here, which is what the domain:ui seat (#25966) needs before it can pick it.

    • p3, as filed: the server refuses invalid transitions as designed. The form and the board offer options the state_machine already rules out. The fix: the select and the board columns read transitions from the served object metadata. There is no spec change.

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 · 2026-10-26T08:49Z
    Session: session_01SMps7wq7rBTk53e8KTSEup
    Account: os-steve
    Branch: claude/project-thread-aetoxp
    Worktree: card thread (its own container)
    Domain: domain:ui
    Seat: domain:ui#2
    File surface: PR 1 — ui/packages/core/src/validation/** (a new pure stateMachineNextValues export beside object-validation-engine.ts, plus its index export), ui/packages/plugin-grid/src/inline-edit-options.ts (re-export only) and inline-edit-options.test.ts, ui/packages/plugin-form/src/ObjectForm.tsx (select options narrowing) and a plugin-form test. PR 2 — ui/packages/plugin-kanban/src/KanbanImpl.tsx, KanbanBoardCore.tsx, ObjectKanban.tsx and plugin-kanban tests. Each PR: scripts/sdui-manifest.record.json (re-record) and one root .changeset/25933-*.md (stop on breach; explain in the report). ⛔ ObjectGrid.tsx, ui/packages/fields/src/index.tsx, ui/packages/plugin-detail/**, ui/.github/** and the root .github/workflows/ci.yml are out of bounds.
    Container & model: M, mode:thread, model: opus (dispatch-gates --tier --repo objectstack-ai/objectstack: no path-derived mandate), effort: high
    Clause-②: yes
    Metadata-protocol: none
    Responsibility: the edit form and the board, which offer every option and column | the server's state_machine rule already refuses the move | every member editing a state-machine field, who learns only after submit
    Thread-read: 6410200039
    Premise: corrected 2026-10-26T08:30Z — on origin/main a4eae064 a narrowing helper already exists (ui/packages/plugin-grid/src/inline-edit-options.ts stateMachineNextValues, used only by the grid's inline editor) but ignores severity, active and events; neither ObjectForm nor ObjectKanban reads objectSchema.validations.
    Serial constraints cleared: none — no open claim holds these files; @object-ui/core placement agreed with seat 1 (#25966).
    Writes: PR only via the relay op pr_create; comments only via the relay write tools; ⛔ no GitHub MCP writes.

    • Ruling section: triage 6410200039 — read transitions from the served object metadata; no spec change.
    • Seat calls (within the card's own options): the form filters options (no disabled-with-reason, so no new copy); the board dims non-target columns while dragging and refuses a disallowed drop client-side, server rollback kept; only error-severity, active, update-event rules narrow, so the grid's inline editor stops narrowing warning-only rules — a visible, intended change to name in PR 1.
    • Route: two PRs on this branch — PR 1 Part of #25933 (core helper + form), PR 2 Fixes #25933 (board) after PR 1 merges. Form "from" value = persisted initialData, never the live value; create mode unconstrained; the current value always stays offered. Board gate applies only when schema.groupBy is the rule's field.
  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Surface amendment · domain:ui seat 2 · 2026-10-26T08:50Z
    Session: session_01SMps7wq7rBTk53e8KTSEup
    Seat: domain:ui#2
    Branch: claude/project-thread-aetoxp
    Amends: Claim 6411958755

    File surface (PR 1, added): ui/packages/plugin-form/src/fieldWriteGate.ts (gateFormFields takes an optional previousValues and narrows there) and the one-line wiring in ModalForm.tsx, DrawerForm.tsx, TabbedForm.tsx, SplitForm.tsx, WizardForm.tsx. Reason: the card thread's premise read found that the console's edit form is ModalForm (AppContent renders it directly), and every form arm draws its fields through gateFormFields, so ObjectForm.tsx alone would leave the reported symptom unfixed. No other claim holds these files (#25955 holds sectionGroups.ts only).

  7. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25933,
      "status": "done",
      "branch": "claude/project-thread-aetoxp",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26033",
      "session": "session_015Kzn5VKHAGKe251waHoUqj",
      "premise_still_valid": true,
      "summary": "PR 1 of 2 (Part of #25933, head 80690c5b). @object-ui/core gains stateMachineNextValues (validation/validators/state-machine-next-values.ts): it narrows only on state_machine rules the server enforces on update (severity error by default, active not false, events covering update), leaves an undeclared or empty from-state unconstrained, and intersects several rules. plugin-grid re-exports it (ObjectGrid.tsx untouched); the grid's inline editor therefore stops narrowing warning / info / inactive / insert-only machines, as the seat asked to name. plugin-form narrows in gateFormFields via a new optional previousValues (the persisted record), on both formField.options and formField.field.options; create is unconstrained and the current value stays offered. Premise correction, seat-approved (amendment 6411987055): the console's edit dialog is ModalForm rendered directly by AppContent.tsx, not ObjectForm's default arm, so all six layouts (simple, modal, drawer, tabbed, split, wizard) pass their persisted record into the one gate. PR 2 (the board) follows after this merges.",
      "tests": "head 8e28b0ff..80690c5b, from ui/ under os-verify-lock: core state-machine-next-values.test.ts 12/12, validation-engine-stays-unwired.test.ts green, plugin-grid inline-edit-options.test.ts green (+ warning case), plugin-form stateMachineOptions.test.ts 11/11, __tests__/stateMachineSelect-25933.test.tsx 5/5 (rendered ModalForm edit backlog -> [backlog,todo], in_review -> [in_progress,in_review,done], simple ObjectForm edit done -> [in_progress,done]; controls: create and warning machine offer all five). Full core + plugin-form at 8e28b0ff: 413 files, 6484 passed, 28 skipped, 1 failed (column-identity.ratchet read the multi-value guard as a dual read) -> guard split in 573a23e7, ratchet + the four pinned files re-run 39/39 green. type-check (@object-ui/core, plugin-form, plugin-grid): exit 0. Ablation (fix committed first, scripts/ablation-replace.mjs, mutation landed: anchor 1 -> 0, blob c4c409e1 -> a4146d92, restored to HEAD blob, git diff HEAD empty): previousValues: undefined in gateFormFields -> 4 narrowing pins red, 12 others green. plugin-grid suite narrowed to inline-edit-options.test.ts (declared); full grid suite left to CI Test Core.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 46 derived at 573a23e7; 45 exit 0; node scripts/check-sdui-manifest.mjs exit 1 there (record predated the last source commit) -> re-recorded (80690c5b, only scripts/sdui-manifest.record.json moved) -> exit 0 at 80690c5b. --ran: 46 derived, 46 run, 0 NOT-MEASURED (derived zero, every row carries its exit code). CI check-runs on #26033: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-② carries no (narrowing) arm",
      "mcp_calls": "3 — mcp__github__issue_read (get, get_comments x2); no MCP write",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST /issues/26033/assignees), this os-dev-report comment via post-stamped (POST /issues/25933/comments)",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed — a state_machine nested in a conditional rule's then/otherwise is not read by the narrowing; permissive direction only, never stricter than the server. In PR Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed — the invalid_transition refusal still does not name the allowed next states; with the select narrowed the form no longer reaches it. In PR Acceptance notes.",
        "carrier: none · environment, not repo — in this card container pnpm (and its self-fetch of pnpm@10.31.0) hung because registry.npmjs.org is in NO_PROXY while direct egress times out; via the proxy it answers 200. Worked around per command; recorded in project memory."
      ]
    }

    Generated by Claude Code

  8. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer · domain:ui seat 2 · 2026-10-26T09:53Z

    PR 1 (form): ACCEPT → PR #26033 comment 6413112466. The contract review (TIER_DEFAULT) PASS is comment 6413093140 at head 80690c5b. The PR is waiting on a merge of main (it conflicts) and then green checks. PR 2 (board) follows on the same branch after it merges.

  9. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed (PR 1 of 2) · seat 2 · 2026-10-26T16:24Z

    probe reading
    PR #26033 MERGED 2026-10-26T16:23Z, merge commit 4f48d2bb, head 54883e1e (patch round re-recorded the core, plugin-form and plugin-grid sources)
    origin/main at df8aac41, contains 4f48d2bb (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26033-*: 0 left
    card open, because #26033 is Part of #25933

    Status: PR 1 (the edit form's state-machine select) is done. PR 2 (the board) continues on the same card thread from current main. The card stays pm:dispatched.

  10. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 25933,
      "status": "done",
      "branch": "claude/project-thread-aetoxp",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/26253",
      "session": "session_015Kzn5VKHAGKe251waHoUqj",
      "premise_still_valid": true,
      "summary": "PR 2 of 2 (Fixes #25933, head c0333981, branch rebuilt from origin/main df8aac41 after #26033 merged as 4f48d2bb). KanbanBoardProps (KanbanImpl.tsx) gains an optional canDropCard(fromColumnId, toColumnId), forwarded by KanbanBoardCore.tsx. While a card is dragged, every lane the gate closes is dimmed (opacity-40), draws no drop highlight, and carries aria-disabled + data-kanban-drop-blocked, on the flat layout and in each swimlane row. handleDragEnd refuses a cross-column drop the gate closes before the optimistic move and before onCardMove, so nothing is written. A new onDragCancel handler clears the dimming (the board had no cancel handler before). ObjectKanban.tsx derives the gate from objectDef.validations via stateMachineNextValues (@object-ui/core) on schema.groupBy only; an undeclared from-state and the Uncategorized lane stay open; KanbanRenderer passes no gate. Server refusal and handleCardMove rollback unchanged. No new visible text, so no i18n. Only plugin-kanban's sdui sources record moved.",
      "tests": "head c0333981, from ui/ under os-verify-lock: new ObjectKanban.stateMachineDropGate-25933.test.tsx 6/6 (backlog drag dims only Done; drop on Done writes nothing, no toast, card stays, dimming cleared; drop on To Do writes {status:'todo'}; cancel clears dimming; CONTROLS warning-severity machine and machine on a non-grouped field close no lane and the drop is written). ObjectKanban.rejectedMoveRollback.test.tsx green. Whole @object-ui/plugin-kanban package: 79 files, 531 passed. pnpm --filter @object-ui/plugin-kanban run type-check (src + tsconfig.test.json): exit 0. Ablations (fix committed first, scripts/ablation-replace.mjs, anchor hit 1 time, restored to HEAD blob, git diff HEAD empty): (A) canDropCard={undefined} in ObjectKanban.tsx -> 3 red (dims, refuses, cancel), 3 green; (B) the handleDragEnd guard replaced by false in KanbanImpl.tsx -> exactly the refusal pin red.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 47 derived at c0333981; all 47 exit 0 (incl. node scripts/check-sdui-manifest.mjs). --ran: 47 derived, 47 run, 0 NOT-MEASURED (derived zero, every row carries its exit code). Console build, pnpm lint and the full check:* farm left to CI. CI check-runs on #26253: not yet started at report time. Main's Test UI red (48 != 47 ObjectSchema guards, #26005 / fix #26218) is not this PR's.",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-② carries no (narrowing) arm",
      "mcp_calls": "0 in this PR's round; no MCP write",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls + POST /issues/26253/assignees), this os-dev-report comment via post-stamped (POST /issues/25933/comments)",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (承接者:无) · noted, not filed — a refused drop is silent to a screen reader (cross-column drop announcements come from the write's outcome since #13054, and no write happens); adding a sentence would be new copy, which this card ruled out. Dimmed lanes carry aria-disabled. In PR Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed — collision detection still lets the pointer hover a closed lane (it just draws no highlight and the drop is refused); filtering droppables in collisionDetection would be a deeper change to the dnd wiring. In PR Acceptance notes.",
        "carrier: none (承接者:无) · noted, not filed — like the form, a state_machine nested in a conditional rule leaves lanes open; permissive direction only, never stricter than the server."
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · seat 2 · 2026-10-26T18:23Z

    probe reading
    PR #26253 MERGED 2026-10-26T18:18Z, merge commit c14e9109, head c0333981 (+345/−7, ACCEPT on #26253)
    origin/main at e05e0de8, contains c14e9109 (git merge-base --is-ancestor exit 0)
    queue branch gh-readonly-queue/main/pr-26253-*: none left (consumed)
    card closed (completed) by Fixes #25933 on #26253

    Status: both halves are on main. PR 1 gave the record form and the grid editor the state_machine next-value read. PR 2 makes the board dim the lanes a dragged card's machine closes, and refuse a drop into them before any write. pm:dispatched removed; the card is done.

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:uipriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions