Skip to content

fix(ui): a wizard form view keeps layout horizontal, and the form page draws it as steps - #27267

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-rxvyfl
Oct 28, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-rxvyfl

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Warren · project thread
Fixes #27224

Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么:分步表单(type: 'wizard')写 layout: 'horizontal' 时每一步的字段标签都放到输入框左侧;/forms/:name 与 /f/:slug 表单页把分步表单画成逐步填写(步骤条、下一步/上一步、最后一步提交)。
  • 为什么改:这两项都能保存发布,但分步表单把 layout 固定成 vertical,表单页把所有分节堆在一页,作者以为生效了其实没有。
  • 风险与回滚:只改 WizardForm 的一处 layout 传值和表单页的 wizard 分支;simple、tabbed 与不写 layout 的表单行为不变(有对照测试);回滚即 revert 本 PR。
  • 先看:ui/apps/console/src/components/FormPage.tsx(renderWizardSteps、handleWizardNext、提交被拒时跳到出错步骤)、ui/packages/plugin-form/src/WizardForm.tsx(layout: schema.layout ?? 'vertical')。
  • 两分钟验证:pnpm --dir ui exec vitest run apps/console/src/components/FormPage.wizardSteps-27224.test.tsx,预期 11 个测试全绿。
  • 需要你做的:无,席位落地。

What changes

  1. A wizard form keeps layout: 'horizontal'. ObjectForm hands the authored key to WizardForm in its {...schema} spread, and the step form node WizardForm built said layout: 'vertical' whatever the form view said. It now passes schema.layout ?? 'vertical', the shape TabbedForm got in PR fix(ui): a form view's layout horizontal puts labels beside fields, and the form page draws tabbed views as tabs #27222, and declares layout on WizardFormSchema as ObjectFormSchema['layout']. Every field on every step draws its label beside the control from sm up, through the form renderer's existing horizontal arrangement. The auto-stepper path (mobile.stepper) spreads the same schema, so it keeps the key too.

  2. The form page draws type: 'wizard' as steps. /forms/:name and the public /f/:slug read type only for tabbed, so a wizard view drew every section stacked on one page. A wizard view with more than one section now draws, with the semantics of the app's wizard form and the spec's FormView wizard contract (D4 step gate, D5 derived progress):

    • a step indicator, one step per section in array order, labelled by the section heading (else the wizard.stepFallback text), hidden by showStepIndicator: false;
    • Next checks the current step's fields with the same rules the final submit applies (findSubmitRefusals over that one section) and opens the next step only when none is refused; a refused step is marked in the indicator and the refusal message is the page's existing one;
    • Back is always free; a later step opens from the indicator once completed, or at once under allowSkip (canOpenWizardStep, the rule WizardForm's canOpenStep applies);
    • Submit appears on the last step only and carries every step's fields (the page's one values map); a submit refused for a missing field, or a faulted visibleWhen, opens the step holding the first field it names and marks every step a refused field sits on;
    • Enter in a field before the last step is that step's Next; a continue submit behaviour starts the wizard over from step one;
    • every step stays mounted and only the current one is drawn (the hidden attribute), so a control on another step keeps its widget state, an upload in flight included;
    • a step with no fields shows the wizard.emptyStep text, as the app's wizard does;
    • a wizard view with a single section draws it stacked, as a single-section tabbed view does.

    The refusal reporting that handleSubmit inlined is lifted into reportRefusals so Next and the final submit put the same messages on the same channel. Every string the wizard draws is an existing catalogue key (wizard.progressLabel, wizard.stepFallback, wizard.back, wizard.emptyStep, common.next, form.stepOf), so no locale pack changes.

FORM_SPEC_TYPES is left as it is; its split/drawer/modal entries belong to #27159.

Why

Both keys are authorable and saved, and both drew as if absent: a wizard form view's layout: 'horizontal' and the form page's type: 'wizard'. This is the wizard arm of the defect PR #27222 closed for simple and tabbed forms (triage 6468472559: the filer's Expected stands).

Tests

  • ui/packages/plugin-form/src/__tests__/wizardFormLayout-27224.test.tsx (3): horizontal on step one and, after Next, on step two; CONTROLS: no layout, and vertical, draw vertical.
  • ui/apps/console/src/components/FormPage.wizardSteps-27224.test.tsx (11): steps drawn, only the current one shown, Next and no Back or Submit on step one; Next and Back, completed steps reopen from the indicator and an uncompleted one does not; Next refuses an outstanding required field and writes nothing; Enter is Next; the submit carries every step's fields; allowSkip opens any step and a refused submit opens the step holding the field; showStepIndicator: false; horizontal rows on every step; canOpenWizardStep unit cases. CONTROLS: a single-section wizard draws stacked with Submit and no indicator; a simple view draws every section stacked with Submit and no steps.

Ablations, each through scripts/ablation-replace.mjs on the committed head 15589ba4, restored to the HEAD blob with git diff HEAD empty:

  • WizardForm.tsx back to layout: 'vertical': the horizontal test reds (1 failed, 2 passed: both controls stay green).
  • FormPage.tsx with wizardView forced to false: all 8 wizard tests red, the 3 controls (single section, simple view, canOpenWizardStep) stay green.

Acceptance notes

  • The page's step gate checks what the page can check: required, requiredWhen, readonlyWhen and a faulted visibleWhen, through the rules its final submit already applies. Like the app's wizard, it is a UI admission rule, never authorization; the server re-validates the record on submit.
  • nextText / prevText are not FormView keys (they live on the object-form component props), so the page draws the catalogue's Next and Back.

Verification

Measured on objectstack-ai/objectstack at 15589ba4 (merge base 295699d5). origin/main has since moved to 3beb2240; none of its commits touch ui/packages/plugin-form, FormPage.tsx or the form renderer, so the branch is not merged with it.

Builds, suites and type-checks
  • Closure pnpm --workspace-concurrency=2 --filter '@object-ui/console^...' build under the lock: VERDICT command-exit 0 (11m36s).
  • pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-form/ apps/console/src/components/FormPage: Test Files 243 passed (243), Tests 2792 passed, 1 skipped; VERDICT command-exit 0.
  • pnpm --filter @object-ui/plugin-form run type-check && pnpm --filter @object-ui/console run type-check: VERDICT command-exit 0.
  • pnpm check:new-line-citations (from ui/): 0 new citations.
Derived gates: 34 derived, 34 run, all exit 0 (dispatch-gates --ran: 0 NOT-MEASURED)
  • 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 --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 exec vitest run scripts/ :: exit 0

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (not touched)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint is CI's run.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: not touched.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JQwxbZ31aDwuL2615F85BS

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tooling labels Oct 28, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · head 15589ba4aa · 2026-10-28T13:54Z

席位意见:向导表单不再丢掉横排布局,表单页按节分步,下一步的检查沿用最终提交的规则,被拒的提交会跳到出错那一步;简单表单和标签页表单的行为不变。

  1. Diff read: 5 files, +642 / −29.
    • WizardForm.tsx: layout: schema.layout ?? 'vertical', typed as ObjectFormSchema['layout']. This is the TabbedForm shape from fix(ui): a form view's layout horizontal puts labels beside fields, and the form page draws tabbed views as tabs #27222.
    • FormPage.tsx: a wizardView arm, used only when there is more than one section. A single-section wizard stays stacked, like a single-section tabbed view.
    • The refusal reporting is extracted into reportRefusals with no behaviour change on the simple and tabbed arms. The tabbed jump to the refused field's tab is kept.
    • Next runs findSubmitRefusals over the current section only, which is the same rule the final submit applies. A submit before the last step is that step's Next.
    • A refused final submit marks every blocked step and opens the first refused field's step.
    • canOpenWizardStep mirrors WizardForm's rule: up to the current step, a later step once it is completed or under allowSkip.
    • "Save and new" resets the step and the completed set.
    • Every step stays mounted and only the current one is drawn, so per-control widget state, an in-flight upload included, survives a step change.
  2. Checked against the seat's own reading: section-level visibleWhen is not filtered on the wizard arm, unlike the tabbed and stacked arms. That is correct: since the Design the first-class wizard view schema shape — the ruled spec half of #13342 (proposal returns to the decision box before any implementation) #13622 ruling, the spec refuses visibleWhen on a wizard step at parse (WizardForm.tsx ≈L104–111), and WizardForm does not honour it either, so the page matches the app's wizard. The i18n keys used (form.stepOf, wizard.stepFallback, wizard.emptyStep, wizard.progressLabel) already exist in all 10 packs. FORM_SPEC_TYPES is untouched; it belongs to ui(plugin-form): ObjectForm drops the split / drawer / modal branches and SplitForm retires; showcase task views re-spelled (ADR-0050 cutover, renderer half of #27155) #27159.
  3. Tests and proof (report 6470846214): 14 new tests, covering the horizontal and default layout pins on WizardForm plus steps, Next gate, Back, indicator reopen rule, submit carries every step and a refused submit opens its step on FormPage, with a simple CONTROL. Two ablations went red as expected. The plugin-form and FormPage suites are green across 243 files, both type-checks are green, and 34/34 derived gates exit 0.
  4. Accepted deviations:
    • The changeset also names @object-ui/console as a patch. Its package.json declares no private and ships dist, and earlier changesets name it too.
    • The local install used npm_config_nodedir after a node-gyp header download aborted. This is environment only and changes nothing in the diff.
  5. Shape: Fixes #27224, Clause-②: no, Metadata-protocol: none. Not governed: no skills/**, spec or CI path is touched.
  6. Landing clause: check-runs are still in progress at this record (52 runs, none failed so far). The seat readies and arms now. Auto-merge enforces the required checks, and the seat tracks the PR to MERGED.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 28, 2026 13:55
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 28, 2026 13:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 0d45c0e Oct 28, 2026
56 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-rxvyfl branch October 28, 2026 14:18
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/l tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

console forms: a type: 'wizard' form view drops layout: 'horizontal' (WizardForm hard-codes vertical) and the form page draws it as stacked sections

2 participants