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
Screen flow wizard: every step's button reads 「提交」 and every step toasts 「已保存 — 下一步」, so a user cannot tell which click submits #26578
Filing class: ① user-visible defect — reach: public door, measured in a browser on the 18.1.0 Console (objectstack dev --ui) with objectstack-ai/hotclm main, and the code read on objectstack main1c632311. Reader: the UI lane, ui/packages/app-shell/src/views/FlowRunner.tsx.
Business reason
A multi-step screen flow is how an app builds a wizard. hotclm's 发起合同 wizard has five flat steps. Its last step asks 「立即提交」 — whether to send the contract to legal or keep it as a draft. Every earlier step's button already says 「提交」, so a requester reasonably believes the contract was submitted on step 1, and the last step's choice reads as noise.
Measured
Step 1 「合同信息」 (a flat screen, 12 fields), step 3 「首个版本」, step 5 「提交合同」: the footer is 取消 / 提交 on each. The label is fixed: FlowRunner.tsx:740 renders t('common.submit') for every non-terminal flat screen, and the screen config has no key for it.
After each step a success toast 「已保存 — 下一步」 appears (FlowRunner.tsx:530, flowRunner.nextStep), even after a step that saved nothing (step 1 only collects variables; the record is created two steps later).
No step indicator: the dialog does not say 「第 2 步,共 5 步」, so the user does not know how far they are. The object-form wizard has stepFallback / progressLabel keys; the flat-screen path draws neither.
Expected
A non-final screen's primary button reads 下一步 (or a label the screen declares); only the last screen reads 提交 or the flow's own wording.
No success toast between steps, or one only when a step actually wrote a record.
A step counter when the run knows it has more screens.
Dedupe: objectstack issues (2,000 most recent, all states) and the archived objectui issues grepped for nextStep, Saved — next, Save & Continue, step indicator, screen flow + submit — objectui#12088 (Submit stays on screen for a long form) and #12234 / #12246 (terminal toast copy) are neighbours; none covers the per-step label or toast.
Filed from the hotclm UI/UX review (2026-10-27); maintainer: 「平台的问题应该提交issue到平台」.
p2: every wizard step's button reads 「提交」 and toasts 「已保存 — 下一步」. FlowRunner.tsx:740 should say "next" on a step that is not the last and "submit" only on the last, and the toast should follow. If an author-declared step button label is wanted, that is a protocol addition, and it comes back to triage first.
Claim: PM loop round 1
Session: session_01QsR2jTfvjNTaR29ehLk26T
Account: zhuangjianguo
Branch: claude/project-thread-xuv7w9 (platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)
Worktree: objectstack-issue-26578
Domain: domain:ui
Seat: domain:ui#4
File surface: ui/packages/app-shell/src/views/FlowRunner.tsx, its tests, one root changeset; ⛔ ScreenView.tsx field widgets (#26579) (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate; reason: the maintainer's standing rule 「只认领不需要 fable 的卡」, 2026-10-27T10:39Z), effort: high
Clause-②: no
Metadata-protocol: none
Responsibility: FlowRunner.tsx (app-shell, this lane) hard-codes common.submit and the between-steps toast · no platform path covers it · reached by every user of a multi-step screen flow (hotclm 发起合同 wizard)
Thread-read: 6437749475
Premise: holds (action, card-reference and work-item surfaces re-read on origin/main 8c0306f5 at 2026-10-27T10:50Z)
Serial constraints cleared: none: no open PR or in-flight claim on FlowRunner.tsx (scan 2026-10-27T10:50Z)
Dispatched at 2026-10-27T10:55Z to the card thread on that branch; the order lives in the thread's opening message.
{
"report": "os-dev-report",
"issue": 26578,
"card": "objectstack-ai/objectstack#26578",
"claim": "6440134516",
"ruling_ref": "6437749475",
"status": "needs_decision",
"branch": "claude/project-thread-xuv7w9",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26656",
"head_sha": "a746e5660f927158534c1d6a74c80e72bc8a2979",
"commits": [
"014de59df96093d3ab3b212f6eff8b3e43048b75 fix(app-shell): a screen-flow wizard step toasts \"Saved — next step\" only when it saved a record",
"a746e5660f927158534c1d6a74c80e72bc8a2979 test(app-shell): the objectui#12234 multi-step pin reads no toast between flat screens"
],
"session": "session_01QsR2jQzKBjxZXgyVnom2V3",
"premise_still_valid": true,
"summary": "Toast half landed: FlowRunner's resumeWith takes savedRecord; on a paused answer the flowRunner.nextStep toast fires only for an object-form step (it persisted its record, and onSuccess silences ObjectForm's own confirmation). A flat step advances with no toast; terminal, refused and failed paths unchanged. Button half NOT built: measured that the paused ScreenSpec (packages/spec/src/contracts/automation-service.ts) carries nodeId, title, description, fields, kind, objectName, mode, recordId, defaults, idVariable and no final-step or step-count member, and the screen executor (service-automation builtin/screen-nodes.ts) emits exactly those; a flow can branch after any screen, so the runner cannot know the last screen before the server answers. The only client signal is after the fact (a resume answering paused proves the previous step was not last). Hence no Next label and no step counter; PR body says Part of #26578.",
"tests": "head a746e566. ui root under os-verify-lock: vitest run --maxWorkers=2 packages/app-shell/src/views/__tests__/FlowRunner -> Test Files 15 passed (15), Tests 94 passed (94), VERDICT command-exit 0. New file FlowRunner.betweenStepsToast-26578.test.tsx (3 pins); FlowRunner.actionSuccessMessage-12234.test.tsx multi-step pin updated from [['已保存 — 下一步'],[ACTION]] to [[ACTION]]. Ablation via scripts/ablation-replace.mjs: anchor 'if (savedRecord) toast.success(' -> 'if (savedRecord || true) toast.success(' (anchor 1->0, blob 1ed20f953095->244560514f7f): Tests 2 failed | 10 passed (flat-step pin and 12234 pin red), restored blob == HEAD, git diff HEAD empty. No dist build needed: tests import ../FlowRunner from src.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 34 commands, each run with exit code captured before any pipe; all 34 exit 0. --ran at a746e566: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit + tsc -p tsconfig.test.json, the latter covers __tests__): VERDICT command-exit 0. ui check:new-line-citations: 0 new. PR-body gates simulated via GITHUB_EVENT_PATH before pr_create: closing-keyword-parity, changeset-no-major, adr-0087-registration, empty-changeset all exit 0. CI check-runs on #26656: in_progress at report time.",
"line_budget": "+190 / -12 over 4 files (changeset 20, FlowRunner.tsx +30/-7, 12234 test +7/-5, new test 133); under the 3000 human-merge threshold",
"files_changed": [
".changeset/26578-flow-wizard-between-steps-toast.md",
"ui/packages/app-shell/src/views/FlowRunner.tsx",
"ui/packages/app-shell/src/views/__tests__/FlowRunner.actionSuccessMessage-12234.test.tsx",
"ui/packages/app-shell/src/views/__tests__/FlowRunner.betweenStepsToast-26578.test.tsx"
],
"deviations": [
"Card half-closed: PR body says Part of #26578, not a closing keyword, because the Next/Submit label needs a wire signal that does not exist (see open_questions).",
"File surface: one file beyond FlowRunner's own tests touched, FlowRunner.actionSuccessMessage-12234.test.tsx, whose multi-step pin asserted the old between-steps toast; it is a FlowRunner test, inside the claimed surface.",
"No locale-pack key added; Clause-② no and Metadata-protocol none copied verbatim from the claim into the PR body and changeset.",
"First ablation attempt used an undeclared identifier in the replacement (threw instead of toasting) and was discarded as invalid; the reported ablation is the second.",
"Gate loop started at 014de59d and finished after a746e566 landed (a test-only commit); --ran was reconciled at a746e566 and derived the same 34 commands."
],
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "0 — MCP GitHub calls: none (reads by plain REST curl; mcp__claude-code-remote__get_session ×1 for the session id, not GitHub)",
"api_writes": "2 relay strokes — fleet-write pr_create (run 40497397032: POST pulls, POST issues/26656/assignees), and this os-dev-report comment via post-stamped (POST issues/26578/comments); git push ×2 is not a REST write",
"open_questions": [
{
"question": "How should a non-final wizard step say Next, given the runner cannot know which screen is last?",
"options": [
"A: the engine computes, at suspend, whether any screen node is reachable downstream of this one and serves it as a new optional wire member on ScreenSpec (contracts, not an authorable key); the runner shows common.next while one may follow and common.submit otherwise. Contract addition in packages/spec/src/contracts plus service-automation and FlowRunner; owes a contract-tier review.",
"B: an author-declared per-screen button label key (protocol addition; triage ruled it goes back to triage first).",
"C: a neutral label on every step (e.g. Continue) with no signal; makes the final step read Continue, which the card's own business case (last step decides submit to legal) argues against.",
"D: leave Submit on every step; the toast half alone is the fix."
],
"recommendation": "A. Business need: measured on the card (hotclm five-step contract wizard), the label is the headline complaint. Long-term: the engine owns the graph, so it is the only honest producer; a client-side graph walk would duplicate engine semantics. AI-error resistance: a served fact needs no authoring, so no author can mis-declare it, unlike B. Startup focus: one optional wire member with one producer and one consumer, no new authorable surface. Needs a spec-lane card and the contract-tier review, which this seat does not take under the no-Fable rule."
}
],
"out_of_scope_findings": [
"carrier: none · noted, not filed — an object-form step's button reads 'Save & Continue' on every step including the last; same last-step signal gap as open question A; recorded in this report only."
],
"thread": "stays open for review and patch rounds; worktree removed after this report (a patch round re-creates it from the pushed branch)"
}
Split: items 1 and 3 of this card (the step button label and the step counter) now live on #26657. The os-dev report (comment 6440951100) measured that the paused ScreenSpec carries no final-step or step-count member. Closing them therefore needs an engine + packages/spec wire addition, which goes to triage, as triage's own grade said it would. This card keeps item 2, the between-steps toast, delivered by PR #26656 (Part of). When that PR merges, the seat closes this card as completed, pointing at #26657.
Seat close · domain:ui#4 · session_01QsR2jTfvjNTaR29ehLk26T
PR #26656 merged. It delivers this card's item 2: the between-steps toast now fires only after a step that wrote a record. Items 1 and 3 (the step button label and the step counter) continue on #26657, per the split note 6441002688. Closing as completed.
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:ui·area:workflow·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T08:59Z⛔ Not a claim, ⛔ not a dispatch.
FlowRunner.tsx:740should say "next" on a step that is not the last and "submit" only on the last, and the toast should follow. If an author-declared step button label is wanted, that is a protocol addition, and it comes back to triage first.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01QsR2jTfvjNTaR29ehLk26TAccount:
zhuangjianguoBranch:
claude/project-thread-xuv7w9(platform-assigned card-thread branch; a card thread can push only its assigned branch, and the name carries no issue number, so this claim is the identity record)Worktree:
objectstack-issue-26578Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/app-shell/src/views/FlowRunner.tsx, its tests, one root changeset; ⛔ScreenView.tsxfield widgets (#26579) (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT(dispatch-gates --tier: no path-derived mandate; reason: the maintainer's standing rule 「只认领不需要 fable 的卡」, 2026-10-27T10:39Z),effort: highClause-②: no
Metadata-protocol: none
Responsibility:
FlowRunner.tsx(app-shell, this lane) hard-codescommon.submitand the between-steps toast · no platform path covers it · reached by every user of a multi-step screen flow (hotclm 发起合同 wizard)Thread-read: 6437749475
Premise: holds (action, card-reference and work-item surfaces re-read on origin/main
8c0306f5at 2026-10-27T10:50Z)Serial constraints cleared: none: no open PR or in-flight claim on
FlowRunner.tsx(scan 2026-10-27T10:50Z)Dispatched at 2026-10-27T10:55Z to the card thread on that branch; the order lives in the thread's opening message.
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
os-dev-report
Posted: 2026-10-27T11:37Z
{ "report": "os-dev-report", "issue": 26578, "card": "objectstack-ai/objectstack#26578", "claim": "6440134516", "ruling_ref": "6437749475", "status": "needs_decision", "branch": "claude/project-thread-xuv7w9", "pr": "https://github.com/objectstack-ai/objectstack/pull/26656", "head_sha": "a746e5660f927158534c1d6a74c80e72bc8a2979", "commits": [ "014de59df96093d3ab3b212f6eff8b3e43048b75 fix(app-shell): a screen-flow wizard step toasts \"Saved — next step\" only when it saved a record", "a746e5660f927158534c1d6a74c80e72bc8a2979 test(app-shell): the objectui#12234 multi-step pin reads no toast between flat screens" ], "session": "session_01QsR2jQzKBjxZXgyVnom2V3", "premise_still_valid": true, "summary": "Toast half landed: FlowRunner's resumeWith takes savedRecord; on a paused answer the flowRunner.nextStep toast fires only for an object-form step (it persisted its record, and onSuccess silences ObjectForm's own confirmation). A flat step advances with no toast; terminal, refused and failed paths unchanged. Button half NOT built: measured that the paused ScreenSpec (packages/spec/src/contracts/automation-service.ts) carries nodeId, title, description, fields, kind, objectName, mode, recordId, defaults, idVariable and no final-step or step-count member, and the screen executor (service-automation builtin/screen-nodes.ts) emits exactly those; a flow can branch after any screen, so the runner cannot know the last screen before the server answers. The only client signal is after the fact (a resume answering paused proves the previous step was not last). Hence no Next label and no step counter; PR body says Part of #26578.", "tests": "head a746e566. ui root under os-verify-lock: vitest run --maxWorkers=2 packages/app-shell/src/views/__tests__/FlowRunner -> Test Files 15 passed (15), Tests 94 passed (94), VERDICT command-exit 0. New file FlowRunner.betweenStepsToast-26578.test.tsx (3 pins); FlowRunner.actionSuccessMessage-12234.test.tsx multi-step pin updated from [['已保存 — 下一步'],[ACTION]] to [[ACTION]]. Ablation via scripts/ablation-replace.mjs: anchor 'if (savedRecord) toast.success(' -> 'if (savedRecord || true) toast.success(' (anchor 1->0, blob 1ed20f953095->244560514f7f): Tests 2 failed | 10 passed (flat-step pin and 12234 pin red), restored blob == HEAD, git diff HEAD empty. No dist build needed: tests import ../FlowRunner from src.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack: 34 commands, each run with exit code captured before any pipe; all 34 exit 0. --ran at a746e566: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. pnpm --filter @object-ui/app-shell type-check (tsc --noEmit + tsc -p tsconfig.test.json, the latter covers __tests__): VERDICT command-exit 0. ui check:new-line-citations: 0 new. PR-body gates simulated via GITHUB_EVENT_PATH before pr_create: closing-keyword-parity, changeset-no-major, adr-0087-registration, empty-changeset all exit 0. CI check-runs on #26656: in_progress at report time.", "line_budget": "+190 / -12 over 4 files (changeset 20, FlowRunner.tsx +30/-7, 12234 test +7/-5, new test 133); under the 3000 human-merge threshold", "files_changed": [ ".changeset/26578-flow-wizard-between-steps-toast.md", "ui/packages/app-shell/src/views/FlowRunner.tsx", "ui/packages/app-shell/src/views/__tests__/FlowRunner.actionSuccessMessage-12234.test.tsx", "ui/packages/app-shell/src/views/__tests__/FlowRunner.betweenStepsToast-26578.test.tsx" ], "deviations": [ "Card half-closed: PR body says Part of #26578, not a closing keyword, because the Next/Submit label needs a wire signal that does not exist (see open_questions).", "File surface: one file beyond FlowRunner's own tests touched, FlowRunner.actionSuccessMessage-12234.test.tsx, whose multi-step pin asserted the old between-steps toast; it is a FlowRunner test, inside the claimed surface.", "No locale-pack key added; Clause-② no and Metadata-protocol none copied verbatim from the claim into the PR body and changeset.", "First ablation attempt used an undeclared identifier in the replacement (threw instead of toasting) and was discarded as invalid; the reported ablation is the second.", "Gate loop started at 014de59d and finished after a746e566 landed (a test-only commit); --ran was reconciled at a746e566 and derived the same 34 commands." ], "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0 — MCP GitHub calls: none (reads by plain REST curl; mcp__claude-code-remote__get_session ×1 for the session id, not GitHub)", "api_writes": "2 relay strokes — fleet-write pr_create (run 40497397032: POST pulls, POST issues/26656/assignees), and this os-dev-report comment via post-stamped (POST issues/26578/comments); git push ×2 is not a REST write", "open_questions": [ { "question": "How should a non-final wizard step say Next, given the runner cannot know which screen is last?", "options": [ "A: the engine computes, at suspend, whether any screen node is reachable downstream of this one and serves it as a new optional wire member on ScreenSpec (contracts, not an authorable key); the runner shows common.next while one may follow and common.submit otherwise. Contract addition in packages/spec/src/contracts plus service-automation and FlowRunner; owes a contract-tier review.", "B: an author-declared per-screen button label key (protocol addition; triage ruled it goes back to triage first).", "C: a neutral label on every step (e.g. Continue) with no signal; makes the final step read Continue, which the card's own business case (last step decides submit to legal) argues against.", "D: leave Submit on every step; the toast half alone is the fix." ], "recommendation": "A. Business need: measured on the card (hotclm five-step contract wizard), the label is the headline complaint. Long-term: the engine owns the graph, so it is the only honest producer; a client-side graph walk would duplicate engine semantics. AI-error resistance: a served fact needs no authoring, so no author can mis-declare it, unlike B. Startup focus: one optional wire member with one producer and one consumer, no new authorable surface. Needs a spec-lane card and the contract-tier review, which this seat does not take under the no-Fable rule." } ], "out_of_scope_findings": [ "carrier: none · noted, not filed — an object-form step's button reads 'Save & Continue' on every step including the last; same last-step signal gap as open question A; recorded in this report only." ], "thread": "stays open for review and patch rounds; worktree removed after this report (a patch round re-creates it from the pushed branch)" }Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Seat note ·
domain:ui#4·session_01QsR2jTfvjNTaR29ehLk26T· 2026-10-27T11:41ZSplit: items 1 and 3 of this card (the step button label and the step counter) now live on #26657. The os-dev report (comment 6440951100) measured that the paused
ScreenSpeccarries no final-step or step-count member. Closing them therefore needs an engine +packages/specwire addition, which goes to triage, as triage's own grade said it would. This card keeps item 2, the between-steps toast, delivered by PR #26656 (Part of). When that PR merges, the seat closes this card as completed, pointing at #26657.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Seat close ·
domain:ui#4·session_01QsR2jTfvjNTaR29ehLk26TPR #26656 merged. It delivers this card's item 2: the between-steps toast now fires only after a step that wrote a record. Items 1 and 3 (the step button label and the step counter) continue on #26657, per the split note 6441002688. Closing as completed.
Generated by Claude Code