Repository navigation
Description
Filing gate ① a product defect with a reproduction. Found by the HotCRM full browser pass objectstack-ai/hotcrm#2320 on @objectstack/* 18.2.0: track 1 Sales (report comment 6443461344 on objectstack-ai/hotcrm#2320, finding D1; screenshots on branch claude/project-thread-2kd6ha under qa/ and in the HotCRM project files qa-2320/sales/); track 2 Revenue (report comment 6444188882 on objectstack-ai/hotcrm#2320, finding D1; screenshots on branch claude/project-thread-urevq9 under qa/ and in the HotCRM project files qa-2320/2-revenue/). Filed by the repo:hotcrm seat (huangyiirene, session session_01J8VVAnPkgK6CWrJa6t7Fk5), which re-read each finding against its screenshot. Batch-of: hotcrm#2320 platform findings.
finding class a · reach: public door: the console on @objectstack/* 18.2.0, measured in Chromium against the HotCRM seed.
What is seen
- Empty required field → nothing happens. HotCRM lead → Schedule Follow-up → type the next step, leave Due Date (declared
required: true, drawn with*) empty → Submit. No inline message, no toast,aria-invalidon no field, the date input'svalidationMessageis empty, and no request is sent (lea-15-followup-submit-empty.png). The record form in the same console says "Disqualification Reason is required" in the same situation, and the public form says "Required: …". - A refusal removes Submit. Opportunity → Generate Quote → Discount 75 → Submit. The flow's refusal "Discount cannot exceed 60%" is shown, and the footer keeps only Close: the user must close the dialog, start the action again and retype every field to correct one value (
quo-12-generate-discount-75.png).
Who loses: every screen flow inherits both (conversion wizard, generate quote, log a call, close case…): the first looks like a broken button, the second turns a one-field correction into starting over.
Expected
The screen-flow dialog validates required fields the way the record form does (message at the field, Submit stays), and after a refused resume it keeps the screen and its values with Submit live, the refusal shown at the top or at the field it names.
Dedupe
search_issues objectstack "console screen flow dialog required field silent submit" and "screen flow refused submit error removes Submit button only Close": 7 hits — #26578 and #26657 (closed, the button's wording on multi-step wizards), #4477 (closed, server-side validation on resume), #3528 (closed, Submit never called resume), #14945 (closed, message-only screens), #25874 (closed, record dialog banner), #26730 (open, Studio publish refusals). None reports either symptom.
Reader who acts: the console screen-flow dialog owner (domain:ui).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: audit line · triage seat ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T19:57Z⛔ Not a claim, ⛔ not a dispatch.
ui/); rationale: p2 ·domain:ui·area:workflow. An empty required field makes Submit a silent no-op, and a refused submit removes Submit. Show the required-field error inline, and keep the entered values and the Submit button after a refusal.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 1
Session:
session_01QsR2jTfvjNTaR29ehLk26TAccount:
zhuangjianguoBranch:
claude/project-thread-4ybq1f(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-26882Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/app-shell/src/views/FlowRunner.tsx,ui/packages/app-shell/src/views/ScreenView.tsx(field-level error display), i18n rows only if no existing key fits, tests beside, one root changeset; ⛔packages/spec/**, server packages, flow definitions in examples (stop on breach; a terminal-refusal fix that needs the server is reported, not built)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: the console screen-flow dialog (ui app-shell, this lane) answers an empty required field with no visible refusal and drops Submit after a refusal · the platform path that may cover it: the engine's resume validation and its
retryableverdict, which decide whether a resubmit can succeed · reached by every screen flow (conversion wizard, generate quote, log a call, close case)Thread-read: 6452161045
Premise: holds (
FlowRunner.submitrefuses a missing required field only through a toast at FlowRunner.tsx:601-611, andterminalwithdraws Submit at ~673, on origin/main7a2818c5at 2026-10-27T22:15Z)Serial constraints cleared: no in-flight card or open PR edits FlowRunner.tsx or ScreenView.tsx
Dispatched at 2026-10-27T22:19Z 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
{ "issue": 26882, "status": "done", "branch": "claude/project-thread-4ybq1f", "pr": "https://github.com/objectstack-ai/objectstack/pull/26956", "session": "session_01QsR2jQtUhXRiBQeTgVcy19", "premise_still_valid": true, "summary": "Required half fixed: FlowRunner's submit no longer answers a missing required field with a toast only; each missing field gets validation.required under its control via a new optional ScreenView errors prop, the control is aria-invalid and described by the message, focus moves to the first missing field, Submit stays, no request and no toast (the record form raises none). Refusal half measured, not built: Submit is withdrawn only on a terminal disposition (refused end, 400 FLOW_FAILED, 404, failed run under 200), and on those the engine has already consumed the suspension (resumeInternal calls forgetSuspendedRun before traversal; restore-suspension is an operator verb), so the ui cannot resubmit. A retryable INVALID_SCREEN_INPUT already kept values and Submit; now pinned. The terminal fix is flow-side: declare max 60 on HotCRM's discount screen field so the engine refuses it before consuming the suspension.", "tests": "head 101d2de2. New FlowRunner.requiredAtField-26882.test.tsx 5/5 green. Reverse: FlowRunner.tsx + ScreenView.tsx from 7a2818c5 -> 4 failed | 1 passed, each naming the missing sentence (Unable to find an element with the text: Due Date is required); retryable case green on main as expected (existing behaviour); restored from HEAD, git diff HEAD empty, blob hash equal 9825bd5e. Narrowed ui run (git grep -l FlowRunner|ScreenView|ScreenFieldInput, 116 files incl. 2 apps/console) at eea769cf: 114 passed, 2 failed = FlowRunner.fileField-25801 and FlowRunner.flowsTranslation-5920, both pinning the retired toast; updated to read the field message and assert no toast, rerun at 101d2de2: 2 files 14/14 green. app-shell type-check + i18n type-check exit 0 (VERDICT 0). Measurement on 7a2818c5 (jsdom, card's screen): 0 resume requests, toast text 'Please complete the required fields: Due Date', 0 aria-invalid elements.", "gates": "dispatch-gates --commands at 101d2de2: 34 derived; all 34 run, each exit 0 (incl. check:type-check-debt, check:query-options-erasure, check:sdui-manifest, pnpm --dir ui exec vitest run scripts/ = 178 files passed, 2 skipped, after a spec rebuild); --ran reconciliation: 34 run, 0 NOT-MEASURED, 0 UNRUN. Outside the derived set: ui check:new-line-citations 0 new -> exit 0; PR-body gates on the sent body: check-changeset-no-major 0, check-adr-0087-registration 0, check-closing-keyword-parity 0, check-partof-closing-keyword 0. CI check-runs on PR 26956: in_progress at report time.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 GitHub MCP calls (card and comments read through unauthenticated public REST GETs; one claude-code-remote get_session for the trailer id)", "api_writes": "2 — fleet-write relay: pr_create objectstack-ai/objectstack#26956 (read-back byte-identical, assignee zhuangjianguo), comment on #26882 (this report). git push x2 not counted.", "deviations": [ "ui/packages/i18n/src/locales/en.ts: comment-only edit (no catalogue row) — the flowRunner block's comment named wizard.missingRequired as the runner's borrowed key, false after this change; now names validation.required. Outside the claim's 'i18n rows only if no existing key fits'.", "Two existing suites edited (FlowRunner.fileField-25801, FlowRunner.flowsTranslation-5920): they pinned the toast the card's ruling retires.", "Changeset lists only @object-ui/app-shell (private, bundled in the console), matching the last app-shell card's shape; en.ts is comment-only so @object-ui/i18n is not listed.", "Narrowed ui run declared: 116 files naming the touched components; CI Test UI runs the rest." ], "open_questions": [ { "question": "The min/max bound refusal (screenFieldBoundViolations) is still toast-only. Move it to the field too?", "options": ["A: yes, a follow-up card; it is one more entry source for the same errors map, and HotCRM's flow-side fix for the discount case lands on exactly this bound", "B: leave it as a toast"], "recommendation": "A, because the record form draws min/max at the field with no toast, and the discount fix below routes the Generate Quote correction through this check." } ], "out_of_scope_findings": [ "class: a · reach: HotCRM Generate Quote, Discount 75 -> 'Discount cannot exceed 60%' then only Close (hotcrm#2320 D1) · evidence: the run is terminal (refused end or FLOW_FAILED after forgetSuspendedRun; which one is NOT MEASURED, the flow lives in objectstack-ai/hotcrm, outside this session's repos); fix is flow-authoring: declare max: 60 on the discount screen field so the engine answers INVALID_SCREEN_INPUT before consuming the suspension · carrier: repo:hotcrm seat · dedupe words: generate quote discount screen field max, refusal removes submit, consumed suspension" ] }Generated by Claude Code