Repository navigation
Commit 6af79c1
fix(console): every console form entry point renders the form view's buttons, layout and defaults, and a tabbed form with subforms opens on its defaultTab (#27027)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ83Xfyz6um6gNWtmJ2QTcNH3)_
Refs #26843 (items buttons, layout, defaults, defaultTab, tabPosition)
Clause-②: no
Metadata-protocol: semantic
Skills-docs: none — the skills and docs already teach these keys as
rendered (`content/docs/protocol/objectui/layout-dsl.mdx` for
`defaultTab` / `tabPosition`, the spec's property help for `buttons` /
`defaults` / `layout`); this diff makes the console do what they say.
## 维护者速读
- 改了什么:控制台三个表单入口(整页新建/编辑、新建/编辑弹窗、表单页 `/forms/:name` 与公开表单
`/f/:slug`)现在都按表单视图里声明的 `buttons`、`layout`、`defaults` 渲染;带子表的分页签表单按
`defaultTab` / `tabPosition` 打开。
- 为什么改:这些键能保存、能发布,但各入口用自己写死的 `showSubmit: true`、按钮文案和 `layout:
'vertical'` 盖掉了作者的值,作者看不到任何效果(ADR-0049 声明即生效)。
- 风险与回滚:只改 UI 渲染层,不动 spec 和服务端;表单视图没写这些键时行为不变(每组测试都有对照用例)。回滚即 revert 本
PR。
-
先看:`ui/packages/app-shell/src/utils/recordFormNavigation.ts`(`resolveFormViewChrome`
的优先级与 `defaults`
合并缓存)、`ui/packages/app-shell/src/console/AppContent.tsx`(新建弹窗那一处)、`ui/packages/plugin-form/src/ModalForm.tsx`(子表分支补
`formType`)。
- 两分钟验证:`pnpm --dir ui exec vitest run
packages/app-shell/src/utils/__tests__/formViewChrome-26843.test.ts
packages/plugin-form/src/__tests__/formViewTabKeysAndButtons-26843.test.tsx`,预期全部通过。
- 需要你做的:无,席位落地。
## What changes
One resolver, `resolveFormViewChrome`
(`ui/packages/app-shell/src/utils/recordFormNavigation.ts`), maps the
selected form view's keys onto the form props, and the full-page record
form and the New / Edit dialog both spread it in place of their
hard-coded sets:
- `buttons.submit` / `buttons.cancel`: an authored `show` wins over the
host's `true`; an authored `label` (an `I18nLabel`, resolved against the
UI language) over the host's label. `buttons.reset` is not read: no host
draws a reset button.
- `layout`: the authored value when the spec's enum allows it, else
`'vertical'`.
- `defaults`, on a create only: merged under the values the host already
carries (URL prefill on the page; the `formLink` pre-link or the
opener's seeded values in the dialog), which win per field. Before, any
prefill dropped `defaults` wholesale, and the dialog never read them.
The merged seed is cached on the metadata payload and on the host values
by content, so it keeps its identity across renders (the forms re-seed
on a new `initialValues`), with no dependency on a memoised identity
(ui/AGENTS.md #10).
Precedence, as measured: no flat `showSubmit` / `submitText` /
`showCancel` / `cancelText` key is author-written. `FormViewSchema` is
strict and refuses them, and a census over `examples/**` and
`packages/**` finds the one `submitText` on a page block
(`project-workspace.page.ts`, an `object-master-detail-form` node), none
on a form view. So the hosts' flat values are console defaults, and the
authored form-view keys win.
The form page (`ui/apps/console/src/components/FormPage.tsx`) is its own
renderer and reads the same keys locally: `readFormButton` for the
submit / cancel label and visibility (the public route still draws no
Cancel), and `readPrefill` takes the form view's `defaults` on a create,
over the object's field defaults and under a `?prefill_` param.
The tab keys:
- `resolveFormViewLayout` copies `defaultTab` / `tabPosition` beside
`contentLayout: 'tabbed'`, and `ModalForm`'s tabbed arm hands them to
the form renderer (`defaultFieldTab` / `fieldTabsPosition`).
- `ModalForm`'s subforms arm now passes `formType` (tabbed when the
content layout is), the tab keys, `layout` and `showSubmit` to
`MasterDetailForm`, and hands `onCancel` only when Cancel is shown.
- `ObjectForm`'s master-detail branch forwards `defaultTab` /
`tabPosition` / `layout`, and `onCancel` only when `showCancel` is not
`false`; `MasterDetailForm` passes the three keys to its header
`ObjectForm`.
Not in this PR (the seat's split): `buttons.reset` and the drawer keys
(`drawerSide` / `drawerWidth`), which stay on #26843 for an
implement-or-retire spec card.
## Tests
New, each block with a CONTROL where the key is absent:
-
`ui/packages/app-shell/src/utils/__tests__/formViewChrome-26843.test.ts`:
the resolver's precedence, the per-locale label, the refused `layout:
'grid'` and `tabPosition` values, the per-field merge and its identity,
edit reads no defaults.
-
`ui/packages/app-shell/src/views/RecordFormPage.formViewChrome-26843.test.tsx`:
the page hands the authored chrome to `ObjectForm`; a URL prefill wins
per field.
-
`ui/packages/app-shell/src/console/__tests__/AppContent.formViewChrome-26843.test.tsx`:
the dialog hands the authored chrome and tab keys to `ModalForm`; a
`formLink` pre-link wins per field and the seed has one identity across
renders; the real dialog draws the authored label and no Cancel.
-
`ui/packages/plugin-form/src/__tests__/formViewTabKeysAndButtons-26843.test.tsx`:
real renders. A tabbed form with subforms opens on `defaultTab` with a
vertical strip for `tabPosition: 'left'`; the modal tabbed arm and its
subforms arm likewise; master-detail Cancel and Save follow `showCancel`
/ `showSubmit`.
-
`ui/apps/console/src/components/FormPage.formViewChrome-26843.test.tsx`:
authored labels and hidden buttons on the internal and public routes,
`defaults` on a create, a `prefill_` param wins, an edit reads the
record.
## Acceptance notes
- `layout: 'horizontal'` now reaches every form the console opens, but
the shared form renderer's only reading of it is the action row's
alignment (`sm:justify-end` against `sm:justify-start`). The dialog
draws its buttons in its own footer, which is always end-aligned, and
the form page's action row is always end-aligned too. So in those two
places the key arrives and nothing is drawn differently. What
`horizontal` should draw (labels beside inputs, as the spec's "Field
layout direction" reads) is a renderer-contract question for the seat;
it is listed in the report.
- `TabbedForm` hard-codes `layout: 'vertical'` on the form it builds, so
`horizontal` on a tabbed form view is dropped there. The file is outside
this card's surface; it is listed in the report.
- A master-detail form with `showSubmit: false` hides its whole action
bar, Cancel included, as before.
- `packages/spec/liveness/view.json` rows for `buttons` and `defaults`
cite `RecordFormPage` as forwarding them; it forwarded them, but then
set its own flat keys over `buttons`, and the New dialog and the form
page read neither. The ledger is not edited here (`packages/spec/**` is
out of bounds); the stale evidence is listed in the report.
## Verification
Head `7e4a1249` (after merging `origin/main` at `3271a083`), all under
`scripts/pm/os-verify-lock.sh`:
<details><summary>Affected-package tests and type-checks</summary>
- `pnpm --dir ui exec vitest run packages/plugin-form/`: Test Files 204
passed, Tests 2422 passed / 1 skipped.
- app-shell (every `RecordFormPage*`, `AppContent*` and `utils` test):
Test Files 81 passed / 1 skipped, Tests 949 passed / 8 skipped.
- every `FormPage*` test in `ui/apps/console`: Test Files 34 passed,
Tests 328 passed.
- `type-check` for `@object-ui/plugin-form`, `@object-ui/app-shell` and
`@object-ui/console`: exit 0 (the console one after building its
closure, `--filter '@object-ui/console^...'`).
</details>
<details><summary>Ablations (each restored, blob equal to HEAD, `git
diff HEAD` empty)</summary>
Driven through `scripts/ablation-replace.mjs`; every mutation landed on
disk and turned its test red:
- RecordFormPage hard-codes its chrome again: 4 failed.
- the New dialog hard-codes its chrome again: 6 failed.
- ModalForm's tabbed arm drops `defaultFieldTab`: 1 failed.
- ModalForm's subforms arm drops `formType`: 3 failed.
- ObjectForm's master-detail branch drops `defaultTab` / `tabPosition`:
1 failed.
- MasterDetailForm's header drops `defaultTab`: 2 failed.
- ObjectForm's master-detail branch hands `onCancel` regardless: 1
failed.
- FormPage's submit label ignores `buttons`: 2 failed.
- FormPage's load ignores `defaults`: 2 failed.
- the resolver rebuilds the merged seed per call: 2 failed.
Two first attempts were no-ops the tool refused (a replacement
containing its anchor, an ambiguous anchor); both were re-run on a
different anchor, and the counts above are the re-runs.
</details>
<details><summary>Derived gates</summary>
`node scripts/pm/dispatch-gates.mjs --ran` on head `7e4a1249`: 34
derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. Every one exited 0; the last
two lines are run in addition to the derived 34.
- `node scripts/check-adr-0087-registration.mjs --base origin/main`:
exit 0
- `node scripts/check-adr-0087-registration.mjs --self-test`: exit 0
- `node scripts/check-changeset-no-major.mjs --base origin/main`: exit 0
- `node scripts/check-closing-keyword-parity.mjs`: exit 0
- `node scripts/check-closing-keyword-parity.mjs --self-test`: exit 0
- `node scripts/check-comment-mask-corpus.mjs`: exit 0
- `node scripts/check-empty-changeset.mjs --base origin/main`: exit 0
- `node scripts/check-empty-changeset.mjs --self-test`: exit 0
- `node scripts/check-sdui-manifest.mjs`: exit 0
- `node scripts/check-sdui-manifest.mjs --self-test`: exit 0
- `node scripts/pm/release-rehearsal-clone.mjs --self-test`: exit 0
- `node scripts/release-pending-publish.mjs --self-test`: exit 0
- `pnpm --dir ui exec vitest run scripts/`: exit 0
- `pnpm --filter @objectstack/spec run check:duration-unit-keys`: exit 0
- `pnpm check:changeset-gate-self-tests`: exit 0
- `pnpm check:closed-ceremony-text`: exit 0
- `pnpm check:cross-package-test-inputs`: exit 0
- `pnpm check:dispatcher-error-vocabulary`: exit 0
- `pnpm check:driver-memory-census`: exit 0
- `pnpm check:engine-double-contract`: exit 0
- `pnpm check:gitlink-declared`: exit 0
- `pnpm check:nul-bytes`: exit 0
- `pnpm check:pm-changeset-deadline-census`: exit 0
- `pnpm check:published-files`: exit 0
- `pnpm check:query-options-erasure`: exit 0
- `pnpm check:refd-timer-probe`: exit 0
- `pnpm check:skill-identifier-liveness`: exit 0
- `pnpm check:test-source-alias`: exit 0
- `pnpm check:tier-file-adoption`: exit 0
- `pnpm check:type-check-coverage`: exit 0
- `pnpm check:type-check-debt`: exit 0
- `pnpm check:undeclared-dep-imports`: exit 0
- `pnpm check:watch-hint-literal`: exit 0
- `pnpm check:where-matcher`: exit 0
- `pnpm --dir ui check:spec-symbols`: exit 0
- `pnpm --dir ui check:new-line-citations`: exit 0
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes (CI).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: not touched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent f558e14 commit 6af79c1
13 files changed
Lines changed: 1101 additions & 34 deletions
File tree
- .changeset
- ui
- apps/console/src/components
- packages
- app-shell/src
- console
- __tests__
- utils
- __tests__
- views
- plugin-form/src
- __tests__
| 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 | + | |
| 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 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1254 | 1254 | | |
1255 | 1255 | | |
1256 | 1256 | | |
| 1257 | + | |
| 1258 | + | |
| 1259 | + | |
| 1260 | + | |
| 1261 | + | |
| 1262 | + | |
1257 | 1263 | | |
1258 | 1264 | | |
1259 | 1265 | | |
| |||
1331 | 1337 | | |
1332 | 1338 | | |
1333 | 1339 | | |
| 1340 | + | |
1334 | 1341 | | |
1335 | 1342 | | |
1336 | 1343 | | |
| |||
1343 | 1350 | | |
1344 | 1351 | | |
1345 | 1352 | | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
1346 | 1357 | | |
1347 | 1358 | | |
1348 | 1359 | | |
| |||
1355 | 1366 | | |
1356 | 1367 | | |
1357 | 1368 | | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
1358 | 1383 | | |
1359 | 1384 | | |
1360 | 1385 | | |
| |||
2261 | 2286 | | |
2262 | 2287 | | |
2263 | 2288 | | |
2264 | | - | |
| 2289 | + | |
2265 | 2290 | | |
2266 | 2291 | | |
2267 | 2292 | | |
| |||
2284 | 2309 | | |
2285 | 2310 | | |
2286 | 2311 | | |
| 2312 | + | |
| 2313 | + | |
| 2314 | + | |
| 2315 | + | |
| 2316 | + | |
| 2317 | + | |
| 2318 | + | |
| 2319 | + | |
| 2320 | + | |
2287 | 2321 | | |
2288 | 2322 | | |
2289 | 2323 | | |
| |||
2460 | 2494 | | |
2461 | 2495 | | |
2462 | 2496 | | |
2463 | | - | |
| 2497 | + | |
2464 | 2498 | | |
2465 | 2499 | | |
2466 | 2500 | | |
| |||
2706 | 2740 | | |
2707 | 2741 | | |
2708 | 2742 | | |
2709 | | - | |
| 2743 | + | |
2710 | 2744 | | |
2711 | 2745 | | |
2712 | 2746 | | |
2713 | 2747 | | |
2714 | 2748 | | |
2715 | 2749 | | |
2716 | | - | |
| 2750 | + | |
2717 | 2751 | | |
2718 | 2752 | | |
2719 | 2753 | | |
2720 | 2754 | | |
2721 | 2755 | | |
2722 | 2756 | | |
2723 | 2757 | | |
| 2758 | + | |
2724 | 2759 | | |
2725 | 2760 | | |
2726 | 2761 | | |
| |||
2730 | 2765 | | |
2731 | 2766 | | |
2732 | 2767 | | |
2733 | | - | |
| 2768 | + | |
2734 | 2769 | | |
| 2770 | + | |
2735 | 2771 | | |
2736 | 2772 | | |
2737 | 2773 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
34 | 34 | | |
35 | 35 | | |
36 | 36 | | |
37 | | - | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
322 | 322 | | |
323 | 323 | | |
324 | 324 | | |
325 | | - | |
| 325 | + | |
326 | 326 | | |
327 | 327 | | |
328 | 328 | | |
| |||
1500 | 1500 | | |
1501 | 1501 | | |
1502 | 1502 | | |
1503 | | - | |
1504 | | - | |
1505 | | - | |
1506 | 1503 | | |
1507 | 1504 | | |
1508 | 1505 | | |
| |||
1542 | 1539 | | |
1543 | 1540 | | |
1544 | 1541 | | |
1545 | | - | |
1546 | 1542 | | |
1547 | 1543 | | |
1548 | 1544 | | |
| |||
1563 | 1559 | | |
1564 | 1560 | | |
1565 | 1561 | | |
1566 | | - | |
1567 | | - | |
1568 | | - | |
1569 | | - | |
1570 | | - | |
1571 | | - | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
| 1576 | + | |
| 1577 | + | |
| 1578 | + | |
| 1579 | + | |
| 1580 | + | |
| 1581 | + | |
1572 | 1582 | | |
1573 | 1583 | | |
1574 | 1584 | | |
| |||
0 commit comments