Repository navigation
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
Conversation
…t, defaults and tab keys (wip) Claude-Session: https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue Co-authored-by: Claude <noreply@anthropic.com>
…keys on every form entry point Claude-Session: https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue Co-authored-by: Claude <noreply@anthropic.com>
维护者速读
席位核对ACCEPT ·
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 40654628276 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Requested by Warren · project thread
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.mdxfordefaultTab/tabPosition, the spec's property help forbuttons/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/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 authoredshowwins over the host'strue; an authoredlabel(anI18nLabel, resolved against the UI language) over the host's label.buttons.resetis 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; theformLinkpre-link or the opener's seeded values in the dialog), which win per field. Before, any prefill droppeddefaultswholesale, 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 newinitialValues), with no dependency on a memoised identity (ui/AGENTS.md chore: version packages #10).Precedence, as measured: no flat
showSubmit/submitText/showCancel/cancelTextkey is author-written.FormViewSchemais strict and refuses them, and a census overexamples/**andpackages/**finds the onesubmitTexton a page block (project-workspace.page.ts, anobject-master-detail-formnode), 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:readFormButtonfor the submit / cancel label and visibility (the public route still draws no Cancel), andreadPrefilltakes the form view'sdefaultson a create, over the object's field defaults and under a?prefill_param.The tab keys:
resolveFormViewLayoutcopiesdefaultTab/tabPositionbesidecontentLayout: 'tabbed', andModalForm's tabbed arm hands them to the form renderer (defaultFieldTab/fieldTabsPosition).ModalForm's subforms arm now passesformType(tabbed when the content layout is), the tab keys,layoutandshowSubmittoMasterDetailForm, and handsonCancelonly when Cancel is shown.ObjectForm's master-detail branch forwardsdefaultTab/tabPosition/layout, andonCancelonly whenshowCancelis notfalse;MasterDetailFormpasses the three keys to its headerObjectForm.Not in this PR (the seat's split):
buttons.resetand 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 refusedlayout: 'grid'andtabPositionvalues, 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 toObjectForm; 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 toModalForm; aformLinkpre-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 ondefaultTabwith a vertical strip fortabPosition: 'left'; the modal tabbed arm and its subforms arm likewise; master-detail Cancel and Save followshowCancel/showSubmit.ui/apps/console/src/components/FormPage.formViewChrome-26843.test.tsx: authored labels and hidden buttons on the internal and public routes,defaultson a create, aprefill_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-endagainstsm: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. Whathorizontalshould 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.TabbedFormhard-codeslayout: 'vertical'on the form it builds, sohorizontalon a tabbed form view is dropped there. The file is outside this card's surface; it is listed in the report.showSubmit: falsehides its whole action bar, Cancel included, as before.packages/spec/liveness/view.jsonrows forbuttonsanddefaultsciteRecordFormPageas forwarding them; it forwarded them, but then set its own flat keys overbuttons, 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 mergingorigin/mainat3271a083), all underscripts/pm/os-verify-lock.sh:Affected-package tests and type-checks
pnpm --dir ui exec vitest run packages/plugin-form/: Test Files 204 passed, Tests 2422 passed / 1 skipped.RecordFormPage*,AppContent*andutilstest): Test Files 81 passed / 1 skipped, Tests 949 passed / 8 skipped.FormPage*test inui/apps/console: Test Files 34 passed, Tests 328 passed.type-checkfor@object-ui/plugin-form,@object-ui/app-shelland@object-ui/console: exit 0 (the console one after building its closure,--filter '@object-ui/console^...').Ablations (each restored, blob equal to HEAD, `git diff HEAD` empty)
Driven through
scripts/ablation-replace.mjs; every mutation landed on disk and turned its test red:defaultFieldTab: 1 failed.formType: 3 failed.defaultTab/tabPosition: 1 failed.defaultTab: 2 failed.onCancelregardless: 1 failed.buttons: 2 failed.defaults: 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.
Derived gates
node scripts/pm/dispatch-gates.mjs --ranon head7e4a1249: 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 0node scripts/check-adr-0087-registration.mjs --self-test: exit 0node scripts/check-changeset-no-major.mjs --base origin/main: exit 0node scripts/check-closing-keyword-parity.mjs: exit 0node scripts/check-closing-keyword-parity.mjs --self-test: exit 0node scripts/check-comment-mask-corpus.mjs: exit 0node scripts/check-empty-changeset.mjs --base origin/main: exit 0node scripts/check-empty-changeset.mjs --self-test: exit 0node scripts/check-sdui-manifest.mjs: exit 0node scripts/check-sdui-manifest.mjs --self-test: exit 0node scripts/pm/release-rehearsal-clone.mjs --self-test: exit 0node scripts/release-pending-publish.mjs --self-test: exit 0pnpm --dir ui exec vitest run scripts/: exit 0pnpm --filter @objectstack/spec run check:duration-unit-keys: exit 0pnpm check:changeset-gate-self-tests: exit 0pnpm check:closed-ceremony-text: exit 0pnpm check:cross-package-test-inputs: exit 0pnpm check:dispatcher-error-vocabulary: exit 0pnpm check:driver-memory-census: exit 0pnpm check:engine-double-contract: exit 0pnpm check:gitlink-declared: exit 0pnpm check:nul-bytes: exit 0pnpm check:pm-changeset-deadline-census: exit 0pnpm check:published-files: exit 0pnpm check:query-options-erasure: exit 0pnpm check:refd-timer-probe: exit 0pnpm check:skill-identifier-liveness: exit 0pnpm check:test-source-alias: exit 0pnpm check:tier-file-adoption: exit 0pnpm check:type-check-coverage: exit 0pnpm check:type-check-debt: exit 0pnpm check:undeclared-dep-imports: exit 0pnpm check:watch-hint-literal: exit 0pnpm check:where-matcher: exit 0pnpm --dir ui check:spec-symbols: exit 0pnpm --dir ui check:new-line-citations: exit 0Human Minimum
packages/specholds no runtime logic.pnpm lintpasses (CI).main, one pull request.packages/spectouched: not touched.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue