Repository navigation
Commit 4f48d2b
feat(ui): an edit form's state-machine select offers only the moves the server accepts (#26033)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi955CwQcSiWwJCH5htb8Ncqt)_
Part of #25933 — this PR is the edit form half; the board half (drop
gate on the Kanban columns) follows in a second PR from the same branch.
Clause-②: yes
Metadata-protocol: none
## What changes
An object whose field is governed by an error-severity `state_machine`
validation (the showcase task's `status`, for one) used to offer every
option in the record edit form. The server refused the invalid move only
after submit, with "Invalid task status transition." and no list of the
allowed next states. The edit form now offers the persisted value plus
the states the machine declares as its next moves, and leaves the rest
out.
- **`@object-ui/core` — new export `stateMachineNextValues(objectSchema,
fieldName, currentValue)`** in
`validation/validators/state-machine-next-values.ts`, published through
the validators barrel. It was the grid's private helper
(`plugin-grid/src/inline-edit-options.ts`); it now narrows exactly where
the server's rule validator refuses: `severity` `error` (the spec
default), `active` not `false`, `events` covering `update` (the default
covers it). An undeclared from-state stays unconstrained, and several
applicable rules on one field intersect. It reads the declared
transition map for choice controls; it does not import, mirror or wire
the deprecated object-level rule engine
(`validation-engine-stays-unwired.test.ts` still green).
- **`@object-ui/plugin-form` — the narrowing runs in `gateFormFields`**,
the one field-gate step every form layout draws its resolved fields
through. It takes an optional `previousValues` (the persisted record)
and narrows a single-value select on both carriers: the entry's own
`options` (the form renderer's built-in select) and `field.options` (the
registered select widget). All six layouts pass their persisted record:
the simple form, `ModalForm`, `DrawerForm`, `TabbedForm`, `SplitForm`
and `WizardForm`. The from-state is the stored row, never the live
value. A create form is unconstrained, and the current value always
stays offered.
- **`@object-ui/plugin-grid`** re-exports the helper from core, so
`ObjectGrid.tsx` is untouched.
### Visible change in the grid's inline editor (intended)
The grid used to narrow on ANY `state_machine` rule for the field. It
now narrows only on a rule the server enforces on update. A `severity:
'warning'` / `'info'` machine (for example the showcase
`project_health_progression`), an `active: false` rule, or an `events:
['insert']` rule no longer narrows the inline editor: the server never
refuses those moves, so every option is offered again.
### Why `gateFormFields` and not `ObjectForm.tsx` alone
The console's record edit dialog is `ModalForm`, rendered directly by
`app-shell` `AppContent.tsx` (`formType: 'modal'`, `mode: 'edit'`), not
`ObjectForm`'s default arm; `ObjectForm` also routes `drawer` / `tabbed`
/ `split` / `wizard` to their own files. Narrowing only the default arm
would have left the reported dialog unchanged. The seat approved the
wider file surface (Claim amendment on the card).
## Tests
Run at head `80690c5b` from `ui/` under the verify lock:
-
`packages/core/src/validation/__tests__/state-machine-next-values.test.ts`
(new, 12): default severity narrows; warning / info / inactive /
insert-only do not; undeclared and empty from-states are unconstrained;
several rules intersect; published from the package root.
-
`packages/core/src/validation/__tests__/validation-engine-stays-unwired.test.ts`
— still green.
- `packages/plugin-grid/src/inline-edit-options.test.ts` — existing pins
green, plus the warning-machine case.
- `packages/plugin-form/src/stateMachineOptions.test.ts` (new, 11): both
carriers narrowed, create unconstrained, unloaded record unconstrained,
warning keeps every option, string options, multi-value untouched, and
through `gateFormFields`.
- `packages/plugin-form/src/__tests__/stateMachineSelect-25933.test.tsx`
(new, 5, rendered): the console edit dialog on a `backlog` task offers
`backlog`, `todo`; an `in_review` task offers `in_progress`,
`in_review`, `done`; the simple edit form on a `done` task offers
`in_progress`, `done`; CONTROLS: a create dialog and a warning-severity
machine offer all five.
Result: the five files above, 41 + 5 tests, all pass. The full
`@object-ui/core` and `@object-ui/plugin-form` suites (`npx vitest run
--maxWorkers=2 packages/core/ packages/plugin-form/`, 413 files, 6484
passed, 28 skipped) ran at `8e28b0ff`; their one red,
`column-identity.ratchet.test.ts` reading `f?.name || f.multiple ||
f.field?.multiple` as a column-identity dual read, was fixed by
splitting the guard and re-run green with the four pinned files. `pnpm
--filter @object-ui/core --filter @object-ui/plugin-form --filter
@object-ui/plugin-grid run type-check`: exit 0. The
`@object-ui/plugin-grid` suite was narrowed to
`inline-edit-options.test.ts` (its only change is the re-export); the
full grid suite is left to CI `Test Core`.
**Ablation** (fix committed first; `scripts/ablation-replace.mjs`,
mutation proven on disk, restored to the HEAD blob): handing
`narrowStateMachineOptions` `previousValues: undefined` inside
`gateFormFields` turned the 4 narrowing pins red (the `gateFormFields`
unit pin and the three rendered narrowing pins) while the 12 others,
controls included, stayed green.
**Gates.** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 46 commands at `573a23e7`; 45 exited
0 there. `node scripts/check-sdui-manifest.mjs` was red there because
the record predated the last source commit; after the re-record it exits
0 at the final head `80690c5b`, whose only further change is
`scripts/sdui-manifest.record.json`. `--ran` reconciliation: 46 derived,
46 run, 0 NOT-MEASURED (a derived zero: every row carries its exit
code). Repo-wide scans (`pnpm lint`, the full `check:*` farm) are CI's.
## Acceptance notes
- The wizard narrows from `persistedRecord`, the record as first read;
it does not advance after a mid-flow save. The wizard only writes at its
final commit, so this matches its own `previousValues`.
- Only top-level `state_machine` rules narrow. A `state_machine` nested
inside a `conditional` rule's `then` / `otherwise` is not read; that
leaves the control permissive, never stricter than the server.
- The refusal message after submit is unchanged; it still does not name
the allowed next states. With the select narrowed the user should no
longer reach it from the form.
Session: `session_015Kzn5VKHAGKe251waHoUqj` (card thread, `domain:ui#2`)
---
_Generated by [Claude
Code](https://claude.ai/code/session_015Kzn5VKHAGKe251waHoUqj)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ffecf03 commit 4f48d2b
19 files changed
Lines changed: 705 additions & 56 deletions
File tree
- .changeset
- scripts/sdui-manifest.sources
- ui/packages
- core/src/validation
- __tests__
- validators
- plugin-form/src
- __tests__
- plugin-grid/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
247 | 247 | | |
248 | 248 | | |
249 | 249 | | |
| 250 | + | |
250 | 251 | | |
251 | 252 | | |
252 | 253 | | |
| |||
262 | 263 | | |
263 | 264 | | |
264 | 265 | | |
| 266 | + | |
| 267 | + | |
265 | 268 | | |
266 | | - | |
| 269 | + | |
267 | 270 | | |
268 | 271 | | |
269 | | - | |
270 | 272 | | |
271 | 273 | | |
272 | 274 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
323 | 323 | | |
324 | 324 | | |
325 | 325 | | |
| 326 | + | |
326 | 327 | | |
327 | 328 | | |
328 | 329 | | |
| |||
336 | 337 | | |
337 | 338 | | |
338 | 339 | | |
| 340 | + | |
| 341 | + | |
339 | 342 | | |
340 | | - | |
| 343 | + | |
341 | 344 | | |
342 | 345 | | |
343 | | - | |
344 | 346 | | |
345 | 347 | | |
346 | 348 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
670 | 670 | | |
671 | 671 | | |
672 | 672 | | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
673 | 683 | | |
674 | 684 | | |
675 | 685 | | |
| |||
681 | 691 | | |
682 | 692 | | |
683 | 693 | | |
| 694 | + | |
684 | 695 | | |
685 | | - | |
| 696 | + | |
686 | 697 | | |
687 | 698 | | |
688 | 699 | | |
| |||
701 | 712 | | |
702 | 713 | | |
703 | 714 | | |
704 | | - | |
705 | 715 | | |
706 | 716 | | |
707 | 717 | | |
| |||
1524 | 1534 | | |
1525 | 1535 | | |
1526 | 1536 | | |
1527 | | - | |
1528 | | - | |
1529 | | - | |
1530 | | - | |
1531 | | - | |
1532 | | - | |
1533 | | - | |
1534 | | - | |
1535 | | - | |
1536 | 1537 | | |
1537 | 1538 | | |
1538 | 1539 | | |
| |||
0 commit comments