Skip to content

fix(app-shell): refuse an empty required screen-flow field at the field, as the record form does - #26956

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-4ybq1f
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-4ybq1f

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #26882

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:流程弹窗(screen flow)里必填字段留空点「提交」,以前只弹一个转瞬即逝的提示,弹窗里什么都没有,看起来像按钮坏了。现在每个漏填的字段下方直接显示「某某 is required」,输入框标红(aria-invalid),光标跳到第一个漏填的字段,「提交」按钮保留,不发请求、不弹 toast,与记录表单一致。
  • 为什么改:HotCRM 全量浏览器走查(hotcrm#2320,D1)报告「Schedule Follow-up 的 Due Date 留空提交毫无反应」。实测:请求没有发出,唯一的反馈是 toast,字段上没有任何标记。
  • 风险与回滚:只改控制台流程弹窗的前端呈现,不动服务端、spec 与流程定义。回滚即 revert 本 PR。卡里的第二半(引擎拒绝后「提交」消失)经实测是运行已结束的正确处置,前端无法重提,修法在流程作者侧,见 Acceptance notes。
  • 先看:ui/packages/app-shell/src/views/FlowRunner.tsx 的 submit(必填检查改为字段级);ScreenView.tsx 的 errors 属性与字段下方的消息;新测试 FlowRunner.requiredAtField-26882.test.tsx。
  • 两分钟验证:pnpm --dir ui exec vitest run packages/app-shell/src/views/__tests__/FlowRunner.requiredAtField-26882.test.tsx,预期 5 个用例全绿。
  • 需要你做的:无,席位落地。

What changes

  • An empty required field is refused at the field. FlowRunner's submit used to answer a missing required field with one toast.error (wizard.missingRequired) and nothing in the dialog. It now records a per-field message (validation.required, "Due Date is required", in the display locale and naming the localized label), moves focus to the first missing field in screen order, and returns without a request. No toast: the record form raises none for a client-side refusal either, because each refused field carries its own message. Submit stays. Editing a field retires its own message.
  • ScreenView draws the message. A new optional errors prop (field name to message) draws the message under the control the way the record form's FormMessage does, and marks the control: aria-invalid on the plain input, textarea and select trigger, and the published error slot on the platform widgets (date and date-time pickers, lookup, upload, checkbox), which set aria-invalid on their own control. The control's aria-describedby names the message after any help text. The Studio preview passes no errors and renders as before.
  • A retryable engine refusal is unchanged and now pinned for values. INVALID_SCREEN_INPUT keeps the screen, the typed values and Submit, with the sentence in the banner; the resubmit goes to the same run.
  • Two existing suites that pinned the old toast (FlowRunner.fileField-25801, FlowRunner.flowsTranslation-5920) now read the message drawn at the field and assert no toast. The en locale comment that named wizard.missingRequired as the runner's borrowed key now names validation.required; no catalogue row changes.

Tests

  • New FlowRunner.requiredAtField-26882.test.tsx (5 cases): an empty required date (message under it, aria-invalid, described by it, focused, Submit enabled, no request, no toast); several missing fields each with a message and focus on the first; answering a field retires its message and the next Submit resumes; the message in zh names the label; a retryable INVALID_SCREEN_INPUT keeps both typed values and Submit, and the resubmit reaches the same run.
  • Reverse verification: with FlowRunner.tsx and ScreenView.tsx checked out from 7a2818c5, 4 of the 5 fail, each naming the missing sentence (Unable to find an element with the text: Due Date is required); the retryable case passes there too, as expected, since that behaviour already existed. Restored from HEAD, proved by an empty git diff HEAD and equal blob hashes.

Acceptance notes

  • Why QA saw "nothing happens" (measured on 7a2818c5, jsdom, the card's screen shape): Submit with Due Date empty made 0 resume requests, raised the toast "Please complete the required fields: Due Date", and left 0 elements with aria-invalid. So the untouched date was empty to the check (the PM's alternative, a non-empty untouched date, does not hold); the refusal's only carrier was a transient toast outside the dialog.
  • The vanished Submit is the terminal disposition, and the ui cannot change it. Submit is withdrawn only when the run is over: a refused end, or a resume answered 400 FLOW_FAILED / 404 / a failed run under 200. On those, the engine has already consumed the suspension: resumeInternal calls forgetSuspendedRun before it traverses the downstream nodes (an order ruled for exactly-once, per the comment above stepCountAtPause in service-automation's run-resume.ts), and the only door that re-arms it, POST /automation/FLOW/runs/RUN/restore-suspension, is an operator verb. Which of the two terminal paths HotCRM's Generate Quote takes for "Discount cannot exceed 60%" is NOT MEASURED: the HotCRM flow is outside this repository. Either way the fix is on the flow's side: declaring the bound on the screen field (max: 60 on the discount field) makes the engine refuse it as INVALID_SCREEN_INPUT before it consumes the suspension (retryable: values and Submit kept, this PR's last test), and the dialog refuses it before any request.
  • Out of scope, noted: the min / max bound refusal is still a toast only (screenFieldBoundViolations). Moving it to the field is the same mechanism (an entry in errors); it is left as an open question to the seat rather than changed here.

Verification

Head of the measured tree: see the final report on the card.

Submit with a required field left empty raised one toast and nothing in
the dialog. Each missing field now shows its message under the control,
the control is marked aria-invalid and described by it, and focus moves
to the first missing field; Submit stays and no resume request is made.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQtUhXRiBQeTgVcy19
…nner suites

The file-field and flows-translation suites pinned the old missing-required
toast; they now read the message drawn under the field, and assert no toast.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jQtUhXRiBQeTgVcy19
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface labels Oct 27, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · seat domain:ui#4 · session_01QsR2jTfvjNTaR29ehLk26T · 2026-10-27T23:19Z · head 101d2de2ad66113db132feb46df70c84b311bf11

  1. Shape: draft, base main, Fixes objectstack-ai/objectstack#26882. Clause-②: no and Metadata-protocol: none are in the body and the changeset, and they match the claim (6455022963). Seven files, inside the claimed surface: FlowRunner.tsx (+57/−6), ScreenView.tsx (+62/−6), one new test file, two updated pins, one @object-ui/app-shell patch changeset, and a comment-only edit in i18n/src/locales/en.ts. No key or value changes, so TranslationKeys is untouched. No packages/spec/**, no server package, no flow definition.
  2. Premise, measured: the seat read the diff.
    • submit refuses an empty required field at the field: validation.required under the control, aria-invalid plus aria-describedby through ScreenView's new errors prop, focus on the first missing field, Submit kept, no toast, no request.
    • Editing a field clears its own message, and a new screen or run clears them all.
    • The thread measured QA's "nothing happens" on 7a2818c5: the toast was the refusal's only carrier, with 0 aria-invalid elements.
    • The new file is 5/5, and reversing the fix reddens 4.
  3. Gates, pinned to the head: check-runs?filter=latest 43 success, 9 skipped, 0 failing or pending · check-governed-merges --pr 26956: NOT governed, +340/−19. mergeable_state: clean. origin/main b1b153a3 has no field-level screen refusal (screenFieldErrorId absent), so nothing here is already landed.
  4. Accepted deviations:
  5. Out of scope: the thread's open question (refuse min/max at the field too, rec A) is recorded here for a later card. It is not filed while this seat winds down. No skip, quarantine or raised timeout. Skills-docs: is not owed.
  6. Prose face, checked against the diff: the changeset and the FlowRunner and ScreenView header notes describe what the code does, including what it deliberately leaves to the engine. They cite Screen flow dialog: an empty required field makes Submit a silent no-op, and a refused submit removes Submit so the user must close and retype everything #26882.

Landing: no contract review is owed (no published schema, no governed text, Clause-②: no). The seat marks the PR ready and arms auto-merge. On MERGED, Fixes objectstack-ai/objectstack#26882 closes the card.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 23:20
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 27, 2026 23:20
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit 3217fc5 Oct 28, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-4ybq1f branch October 28, 2026 00:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling Fix lands in tooling, not a product package — enters pm:queue only with Unblocks: or a named surface

Projects

None yet

2 participants