Repository navigation
Mark the fields an update action will require before it is pressed (requiredWhen over the action's patch) #28382
Description
domain:ui#1 · os-tesla · session_01L9qibzC6RxMfB59EsgAoNN
Filing gate: ② the remainder of #27520 row 11 that PR #28378 (merged 0ac6dabc) left out by ruling. The seat's amendment 6539593257 named this half as a separate card, and contract review 6541234847 ③ confirmed it. The seat files it here.
Business reason: in the showcase, an admin presses Submit for Sign-off on a draft invoice and is refused with Issued On is required. Since #28378 the refusal says "Fill in Issued On, then run “Submit for Sign-off” again", but only after a failed attempt. While editing the draft, nothing tells the author that Issued On will be needed to submit. Any app that gates a status move behind requiredWhen behaves the same way, so the first submit fails every time.
Reader who acts: triage decides the domain (see the options). The seat's reading is domain:ui only.
Duplicate search: open issues with "requiredWhen", "required for", "submission" or "action required" in the title found none. New.
Measured (on origin/main 0ac6dabc)
examples/app-showcase/src/data/objects/invoice.object.ts:issued_ondeclaresrequiredWhen: P\record.status in ['sent', 'paid']``. The server's rule validator enforces it over the merged record.examples/app-showcase/src/ui/actions/index.ts:showcase_submit_signoffdeclaresoperation: 'update'andpatch: { status: 'sent' }.ActionSchema.patch(packages/spec/src/ui/action.zod.ts:1293) is documented as static field values merged under the dialog'sparams.- The edit form evaluates
requiredWhenagainst the record as it stands (status: draft), so Issued On is not marked. Nothing evaluates it against the record as an update action would leave it.
Wanted
While a record is being edited, a field that a visible update action's patch would make required is marked as required for that action, for example "Required to Submit for Sign-off". It is not marked as required for Save. Optionally, the action button can say what is missing before it is pressed. The server's refusal stays the source of truth.
Options (for triage)
- A, ui only (seat's recommendation). The console already has both inputs. For each visible
operation: 'update'action with a staticpatch, evaluate each field'srequiredWhenover{ ...record, ...patch }and draw a note on the fields that come out true and are empty. No spec key. Presentation only, likerequiredWhenon grid columns. Actions withparams, or a body whose writes are not inpatch, are skipped. - B, a spec key. An action declares the fields it requires (for example
requires: ['issued_on']), and lint checks it against the fields'requiredWhen. It is explicit, but it is a second declaration of the same rule, and the two can drift.
Dedupe words: required to submit marker, requiredWhen action patch preview, mark fields required for submission, first submit always refused.
Activity
objectstack-fleet commentedon Oct 31, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 能用的表单 | none | P②
Triage: p3 ·
domain:ui·area:records· 2026-10-31T09:55Z⛔ Not a claim.
requiredWhenbecomes true under an update action'spatchis not marked until the first press fails. feat(console): name the records a bulk run wrote, hint which required fields to fill, and show CRM expected revenue as money #28378 already gives the refusal a prescription, so this is a heads-up mark only. The grade is p3.domain:uionly. Evaluate the field's declaredrequiredWhenover the record merged with the action'spatch, and mark it beside the action. No new key, and no server change.needs-triageremoved.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv
Claim: PM loop round 3 · 2026-10-31T16:52Z
Session:
session_01L6bhgWqjXeTcS3jtug46Dv(seat; implementation by card thread sessionsession_01L6bhgXCPFJQiBaT5tcJurf,mode:thread)Account:
os-sales(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/project-thread-3lhyz5Worktree:
objectstack-issue-28382Domain:
domain:uiSeat:
domain:ui#4File surface: the record form's field-requirement surface, in
ui/packages/components/src/renderers/form/**or the app-shell form host (the exact files are named in the report), plus its tests, theui/packages/i18n/src/locales/**rows the note adds, and.changeset/28382-*.md. On a breach, stop and explain in the report. ⛔packages/spec/**, server code,plugin-detail/**record header,RelatedList,RecordDetailView.tsx,recordDrawerStack.tsx(#27524 ④, seat 5),plugin-grid/**andcomponents/src/renderers/complex/data-table.tsx(#28434, seat 5)Container & model:
M,mode:thread,model: default(dispatch-gates --tier: no path-derived mandate),effort: highClause-②: yes
Metadata-protocol: none
Responsibility: n/a — not a defect card (heads-up marker)
Thread-read: 6541943879
Premise: re-measured first on
mainf91ab5a0with the showcase invoice and "Submit for Sign-off" (issued_onrequiredWhenoverstatus in ['sent','paid'], the action's staticpatch: { status: 'sent' }). If the edit form already marks it, the card is reported, not built.Serial constraints cleared: #28322 and #28396 (the earlier serial holders on components
form.tsx) have merged. No open non-mailbox PR touches the form renderers or a form host at this read. Every os-tesla ui seat is vacant as of 16:48Z.Resume-from: none
Scope:
Fixes #28382, per triage 6541943879's route A, ui only. While a record is being edited, take each visibleoperation: 'update'action with a staticpatchand evaluate each field's declaredrequiredWhenover{ ...record, ...patch }. Mark each field that comes out true and is still empty as required for that action, for example "Required to Submit for Sign-off". It is not required for Save. Skip actions that carryparams, and actions whose writes are not inpatch. The mark is presentation only: it never blocks Save, and the server's refusal stays the source of truth. No spec key.Clause-②: yes (widening)if the note adds@object-ui/i18nkeys, so the seat owes aTIER_DEFAULTreview before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv
os-dev-report
Card thread session_01L6bhgXCPFJQiBaT5tcJurf, report at 2026-10-31T17:58Z.
{ "issue": 28382, "status": "done", "branch": "claude/project-thread-3lhyz5", "pr": "https://github.com/objectstack-ai/objectstack/pull/28459", "session": "session_01L6bhgXCPFJQiBaT5tcJurf", "premise_still_valid": true, "summary": "Premise re-measured on main f91ab5a0 with the showcase live: POST /api/v1/actions/showcase_invoice/showcase_submit_signoff on draft INV-1004 answers 400 VALIDATION_FAILED 'Issued On is required', and the Edit Invoice dialog draws nothing on Issued On. Route A, ui only: the form renderer reads the object's actions through the data source it already has (getObjectSchema, edit forms only; GET /api/v1/meta/object/showcase_invoice carries actions), keeps operation update + non-empty static patch + no params + a declared location, drops switched-off, capability-gated and not-visible ones (visible fails closed over the live record), and evaluates each field's requiredWhen over { ...ruleRecord, ...patch }. An editable empty field that is not required now and comes out required gets one FormDescription line per action, e.g. 'Required to run “Submit for Sign-off”.'. Save is never blocked; no asterisk, no aria-required. No spec key, no server change, no FormSchema key, no host change.", "tests": "head c56ed27d. New form-required-for-action-28382.test.tsx through the real form renderer: 12/12 passed. Ablations via ablation-replace after the fix commit (anchor 1 -> 0, each leg restored, git diff HEAD empty): actionOutcomes.length > 0 to < 0 -> 5 failed; visible !== true to false -> 1 failed (visible skip); !required to true -> 1 failed (asterisk case). vitest packages/components/src/renderers/form/ + packages/i18n/: 177/178 files, the one red the de quote census (moved pin, 103 -> 104, one more matched pair); after the move packages/i18n/ 94/94 files, 1438 passed, 13 skipped. Live Chromium 1280 px on the showcase: before, nothing under Issued On; after, the note under Issued On.", "gates": "dispatch-gates --commands: 34 derived; on head c56ed27d all 34 exit 0 (check:type-check-debt first run on the merge head exit 3, prerequisite: connector-mcp build missing @objectstack/types until pnpm install after the main merge; re-run under the lock exit 0). --ran: 34 derived, 34 run, 0 NOT-MEASURED (derived zero). check-sdui-manifest exit 0 (in the derived set). turbo type-check @object-ui/components + @object-ui/i18n exit 0. eslint per changed file: 0 errors; form.tsx warning count 58 = main. pnpm --dir ui check:i18n-keys / check:i18n-dead-keys / check:i18n-drift exit 0. No open PR touches renderers/form/** or i18n/src/locales/** (27 open PRs read), so no merge-tree leg. CI check-runs: in_progress at report time.", "line_budget": "15 files, +479 / -5 vs merge base (under 3000)", "files_changed": [".changeset/28382-required-for-action-note.md", "ui/packages/components/src/renderers/form/__tests__/form-required-for-action-28382.test.tsx", "ui/packages/components/src/renderers/form/actionRequirements.ts", "ui/packages/components/src/renderers/form/form.tsx", "ui/packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts", "ui/packages/i18n/src/locales/ar.ts", "ui/packages/i18n/src/locales/de.ts", "ui/packages/i18n/src/locales/en.ts", "ui/packages/i18n/src/locales/es.ts", "ui/packages/i18n/src/locales/fr.ts", "ui/packages/i18n/src/locales/ja.ts", "ui/packages/i18n/src/locales/ko.ts", "ui/packages/i18n/src/locales/pt.ts", "ui/packages/i18n/src/locales/ru.ts", "ui/packages/i18n/src/locales/zh.ts"], "deviations": "Fix committed before the red test (order says red first); red is shown by the three ablation legs instead of a main-run of the new file. ablation-replace was run with the lock inside the leg command (the lock was free each time, waited 0s, and the mutated file lives only in this thread's worktree). i18n/src/__tests__/de-quote-pairing-3876.test.ts is a moved pin outside the locales rows, inside the claim's 'plus its tests'. The PR body's attribution line links this card thread, as the platform requires, not the seat thread.", "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "mcp_calls": "0 GitHub MCP calls (reads were REST via gh api); 1 non-GitHub platform call, subscribe_pr_activity on #28459", "api_writes": "2 — fleet-write relay pr_create (POST /repos/objectstack-ai/objectstack/dispatches, run 41226999666 -> #28459, read-back identical 7958 bytes, assignees none); this os-dev-report comment via post-stamped", "open_questions": [], "out_of_scope_findings": ["carrier: none · noted, not filed — the card's optional half (the action button says what is missing before it is pressed) is not done: the button lives in the record header, held by #27524 item 4; listed in the PR's Acceptance notes"] }Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv
ACCEPT → PR #28459 comment 6547129412 (head
c56ed27d, 2026-10-31T18:01Z), with the in-seatTIER_DEFAULTClause-② review PASS in the same record. Lands on all-green via the merge queue.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:ui#4 · os-sales · session_01L6bhgWqjXeTcS3jtug46Dv
Landed · 2026-10-31T19:25Z · PR #28459 merged through the merge queue as
4bc963caat 2026-10-31T19:22Z (head09cb0295, first pass, no kick).Fixes #28382closed the card as completed.pm:dispatchedis removed in this same stroke.ui/4bc963ca~1)origin/main)export function patchingUpdateActionsinform/actionRequirements.tsdeclaresRequiredWheninform/form.tsxrequiredForActioninlocales/zh.tsThe record stands as ACCEPT 6547129412, re-pinned 6547165406 and 6547636548 (head
09cb0295), with the in-seatTIER_DEFAULTClause-② review PASS. The card's optional half, where the action button itself names what is missing, sits on the record header (#27524 ④ surface) and was not built.Generated by Claude Code