Skip to content

showcase: the New Project wizard offers status values the state machine refuses on create, and the refusal only lands at step 3 as an unlocalized "Invalid project status transition." #14311

Closed

Description

@hotlong

What happens

Showcase → Workspace → New Project (Wizard), signed in as the seeded admin, zh-CN session:

  1. Step 1: name + account (Northwind). Next.
  2. Step 2: 状态 = Active, 健康度 = Green. Next.
  3. Step 3: budget 50000, dates. Create → POST /api/v1/data/showcase_project → 400 VALIDATION_FAILED, banner + toast Invalid project status transition.
  4. Back to step 2, 状态 = Planned, Next, Create → 201.

So the wizard lets the author pick a status the project state machine does not accept as an initial state, and the only feedback comes three steps later, in English, without naming the field.

Three things to decide

  • Showcase content: the wizard's status select should only offer the state machine's initial states on create (or default to planned and hide the select), so the demo of "state machine + wizard" does not demo a dead end.
  • Product: a form for a create (recordId absent) could derive the allowed initial values from the object's stateMachine / status transitions and constrain the select — the metadata already says which transitions exist.
  • Error surface: the validation error should be located (status) so the wizard can jump back to the step that owns the field, and the message should go through the translation packs (it is the only English error on an otherwise zh-CN form).

Evidence

Network: POST /api/v1/data/showcase_project → 400 then → 201 after changing the status. Showcase source: examples/app-showcase/src/ui/pages/new-project-wizard.page.ts, examples/app-showcase/src/data/objects/project.object.ts (status state machine).

Also observed on the same run: the sticky error toast stays on screen next to the later success toast ("Your new project is ready…"), so the final screen shows both.

Found during the objectui pin-bump dogfood (PR #14295).

Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage (R+89, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): pm:queue · priority:p2 · domain:engine · type Bug. The card names three things to decide; they are decided here so the dispatch is one bounded job:

    1. Showcase content — in scope. examples/app-showcase/src/ui/pages/new-project-wizard.page.ts: the status select on create offers only the state machine's initial states (or defaults to planned and hides the select). The demo of "state machine + wizard" must not demo a dead end; the example exercises the engine's state machine, hence the engine lane.
    2. Error surface — the engine half is in scope. checkStateMachine's refusal must be field-located (fields[] naming status, the shape VALIDATION_FAILED already declares in the ledger) so a multi-step form can jump to the owning step, and its fallback message must go through the built-in validation catalog (which already ships zh-CN) rather than be emitted verbatim in English — the localisation channel exists (i18n: three authored display surfaces have no bundle key at all (bulk-action defs, custom validation messages, dataset labels) #14253's scoping comment measured it); only this message is not on it.
    3. Product half — ⛔ not filed. "A create form derives its allowed initial values from the object's stateMachine" is a console feature (objectui) with one demo as its pull; it is recorded here and reopens when a second app asks. The sticky error toast surviving next to the later success toast is a console defect and is filed to objectui as a linked card (multi-repo rule 3).

    Size/model suggestion: S–M, opus; the deliverable's proof is the wizard creating with planned, refusing active with a field-located, zh-CN message.


    _Generated by Claude Code


    Generated by Claude Code

  2. added
    bugSomething isn't working
    pm:queueReady for the PM dispatch loop
    domain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they land
    on Sep 2, 2026
  3. added theissue type on Sep 2, 2026
  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 2, 2026
  5. self-assigned this
    on Sep 2, 2026
  6. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: PM loop round 2 (dogfood dispatch)
    Session: session_62849a16-0144-4728-8942-9f60bb3a73f1
    Branch: claude/issue-14311-wizard-initial-status
    Worktree: objectstack-issue-14311
    Domain: domain:engine (triage label; showcase content + the create-form/state-machine seam — PM expects the landing point in examples/app-showcase, spec untouched)
    File surface: examples/app-showcase/src/ui/pages/new-project-wizard.page.ts, examples/app-showcase/src/data/objects/project.object.ts (+ their tests); if the fix belongs in the renderer (objectui) or the write-path error envelope (packages/objectql), report and stop — do not widen (stop on breach; explain in the report)
    Container & model: S/M, model: opus, mode:subagent
    Clause-②: no
    Serial constraints cleared: none — no open PR touches the wizard page or project.object.ts (gh pr list 2026-09-02 11:25); in-flight #14309 (objectql registry + rest/runtime package routes) does not touch examples/

    Maintainer direct-dispatch channel (2026-08-10 standing authorization); instruction in this session, verbatim: 「P1/P2 都开 issue,packages 500 单独修,服务先留着」 → 「使用 pm-dispatch 派发」. Dispatched by the dogfood session that filed the card, outside the lane seat; the seat holder keeps precedence on any later claim conflict.

    Generated by Claude Code

  7. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim amendment (same session, session_62849a16-0144-4728-8942-9f60bb3a73f1, branch claude/issue-14311-wizard-initial-status) — file surface widened to what the triage ruling above already put in scope.

    The triage seat decided the three questions the card raised: (1) showcase wizard content is in scope; (2) the ENGINE half is in scope — checkStateMachine's refusal must be field-located (fields[] naming status, the shape VALIDATION_FAILED already declares) and its fallback message must go through the built-in validation catalog; (3) the product half (a create form deriving allowed initial values from stateMachine) is not filed. My first claim named only examples/app-showcase/** and told the dev to stop at packages/objectql; that contradicted (2).

    Surface now: examples/app-showcase/src/ui/pages/new-project-wizard.page.ts, examples/app-showcase/src/data/objects/project.object.ts, the state-machine write-path check and the built-in validation catalog under packages/objectql/src/** (+ their tests), .changeset/. packages/spec/** stays out of scope — if the field-located shape needs a spec change the dev stops and reports (Clause-②: still no: the refusal stays a refusal; only its envelope gains the location the ledger already declares). Serial constraints: #14309 (in flight, same session) edits packages/objectql/src/registry.ts only — disjoint from the state-machine check; re-checked before dispatch.

    Generated by Claude Code

  8. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    Claim: os-dev seat (dispatched by the PM loop for this card).
    Session: session_62849a16-0144-4728-8942-9f60bb3a73f1
    Branch: claude/issue-14311-wizard-initial-status
    Worktree: objectstack-issue-14311

    Landed as draft PR #14517. Assignee left untouched (PM owns that field).

    Generated by Claude Code

  9. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    os-dev-report

    {
      "issue": 14311,
      "status": "done",
      "branch": "claude/issue-14311-wizard-initial-status",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14517",
      "premise_still_valid": true,
      "summary": "The card's reported defect is real and is fixed, but triage ruling 2's engine prescription was falsified by measurement: checkStateMachine has ALWAYS been field-located (it returns field: rule.field, and rest/error-response.ts ships fields[] verbatim), and its fallback has gone through the built-in catalog since #3957 -- which already ships invalid_initial_state / invalid_transition in zh-CN. The English sentence was NOT a fallback: it was the showcase's own authored message, which by design outranks the catalog, and #14253 had already given authored messages a translation address that the showcase never populated. So the localisation half was showcase content, not engine work. One genuine engine defect remained on that path and is what the objectql change fixes: the emitter carried constraint and value only on the built-in branch, so declaring a message dropped them -- and since ValidationRuleSchema REQUIRES message on every rule, the machine-readable half was reachable only via message: '' . Showcase: status is dropped from the create wizard (the machine admits exactly one initial state, so a narrowed select would be a one-option question; the default: true option supplies that same entry point), the rule message is reworded to be honest for both refusal codes, and all four showcase_project rules go onto the #14253 channel in en + zh-CN. packages/spec untouched; Clause-2 holds. Scope note: translating end_after_start and spent_within_budget goes one rule past the status rule -- same file, same class, and load-bearing because THIS wizard trips both from its budget/schedule step, so fixing only the status rule would move the single English sentence one step later instead of removing it.",
      "tests": "All on final commit 120d1d066 (merge of origin/main), verify-lock wrapper printed 'UNLOCKED (declared) - no usable flock on this host, so the shared verify lock was NEVER taken and NOTHING was serialized' on every run; declared verbatim in the PR body. objectql blast radius (src/validation + engine.test.ts + plugin.integration.test.ts + engine-seed-required-deferral.test.ts): 12 files / 552 passed. Full showcase suite: 27 files / 372 passed. objectql typecheck exit 0 ('check:test-typecheck: OK'); showcase typecheck exit 0; new test file confirmed INSIDE the showcase tsc program via tsc --noEmit --listFiles (1 hit). Whole-repo pnpm lint (eslint . --no-inline-config) exit 0 in 71s -- run in full, NOT narrowed. Gates all exit 0: check:test-source-alias, check:cross-package-test-inputs, check:engine-double-contract, check:where-matcher, check:error-code-casing, check:nul-bytes, check:dispatcher-error-vocabulary. Gate family re-derived after the edits with dispatch-gates.mjs --repo (40 families, script took its own change set). END-TO-END on a real runtime (scratch port 39117, seeded admin, Accept-Language zh-CN): POST without status -> HTTP 201 status=planned; POST status=active -> HTTP 400 {code: VALIDATION_FAILED, fields:[{field:'status', code:'invalid_initial_state', label:'状态', message:'项目的初始状态为“计划中”,此后只能按既定的状态流转变更。', constraint:{allowed:'planned'}, value:'active'}]} -- this exercises the real i18n service resolving the new bundle key, which the unit tests (mocked translate) cannot. ABLATION: fix committed FIRST, then the two constraint/value spread lines deleted; mutation proven on disk by marker count 1->0 AND git hash-object differing from the HEAD blob. Leg A (objectql, source-resolved, no rebuild needed): 2 failed | 184 passed -- red as predicted. Leg B: objectql is NOT aliased to source for the showcase app (only formula and plugin-email are), so it resolves through dist/; dist was REBUILT to carry the mutation before the run: 1 failed | 4 passed. Restore leg proven, not assumed: git checkout HEAD -- path (never a bare git checkout --, which restores from a possibly-polluted index), restored blob hash byte-identical to the HEAD blob, git diff HEAD empty, whole-tree git status --porcelain clean, then dist rebuilt and re-verified with ablation-dist-preflight showing the marker present in 4 EXECUTABLE files. My first preflight marker was a source spelling that survives only into sourcemaps; the tool caught it and the check was redone with a transpilation-surviving marker.",
      "mcp_calls": "0 - no MCP GitHub calls; all GitHub work went through the gh CLI. gh pr create failed with a GraphQL 'rate limit already exceeded' while REST core still had 4708/5000, so the PR, the finding, and both comments were created through REST endpoints instead.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #14518: showcase_account (7 rules incl. two nested conditional branches) and showcase_task (1 rule) declare authored validation messages with no #14253 bundle entry, so they refuse in English on a zh-CN session -- same class as this card, left out of this PR deliberately; examples/app-crm has no _validations block at all. Deduped via REST /search/issues (0 hits) with a control term that returned 59, so the empty result is a real read."
      ]
    }
  10. hotlong commented on Sep 2, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — reviewer of record: PM session session_62849a16-0144-4728-8942-9f60bb3a73f1.

    Checked against GitHub, not the report: draft PR #14517 targets main, first line Fixes #14311, no other closing keyword, changed files stay inside the claimed surface (no governed path, no releases page), changeset declared, every check-run green at the current head. Spot-read: engine half re-measured (checkStateMachine was already field-located and catalog-backed; the real engine defect was the emitter dropping constraint/value under an authored message — fixed with a pin), wizard drops the status step (single initial state), showcase_project rules put on the #14253 translation channel en+zh-CN, e2e on a scratch runtime: 201 planned / 400 active with fields[{field:'status', code:'invalid_initial_state', zh-CN message}]; finding #14518 filed for the remaining eight authored messages. Flipping to ready and arming squash auto-merge; the queue lands it.

    PR: #14517

    Generated by Claude Code

  11. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Sep 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:engineRetired 2026-10-22, alias of domain:server (engine surface); in-flight cards keep it until they landpriority:p2Medium: important, M3

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions