Repository navigation
fix(ui): a form view's layout horizontal puts labels beside fields, and the form page draws tabbed views as tabs - #27222
Merged
Conversation
…age tabbed sections Claude-Session: https://claude.ai/code/session_01JQwxbXNVYZR3yMKLPJJvzL Co-authored-by: Claude <noreply@anthropic.com>
…e form page's tabs Claude-Session: https://claude.ai/code/session_01JQwxbXNVYZR3yMKLPJJvzL Co-authored-by: Claude <noreply@anthropic.com>
Contributor
Author
席位核对ACCEPT · seat 席位意见:表单视图写
|
This was referenced Oct 28, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Warren · project thread
Fixes #26843
Clause-②: no
Metadata-protocol: semantic
维护者速读
layout: 'horizontal'时,字段标签在 sm 及以上宽度放到输入框左侧(手机上仍是上下排);分页表单不再把它改回 vertical;/forms/:name与/f/:slug表单页把type: 'tabbed'画成每节一个页签。ui/apps/console/src/components/FormPage.tsx(分页渲染与提交时跳到出错页签)、ui/packages/components/src/renderers/form/form.tsx(HORIZONTAL_FIELD_ITEM_CLASS)、ui/packages/plugin-form/src/TabbedForm.tsx。pnpm --dir ui exec vitest run apps/console/src/components/FormPage.tabbedLayout-26843.test.tsx,预期 13 个测试全绿。What changes
layout: 'horizontal'draws something. The shared form renderer (@object-ui/componentsform) usedlayoutonly to align its action row. A horizontal field item is now a two-column grid fromsmup: a fixed label column (minmax(0,10rem)) and the control column, label on the control's first text baseline. Every child except thelabel(the control, its description, its message) is placed in the second column, so a label-less field and the help text line up under the control. Every class the arrangement adds issm:-prefixed, so belowsmthe item is the vertical one.verticaland an absentlayouttake the old code path unchanged: same classes, same DOM. The label element, itsfor/id, and the control'saria-*attributes are not touched.TabbedFormpasseslayoutthrough. It built its form node withlayout: 'vertical'whatever the form view said; it now forwardsschema.layout(declared onTabbedFormSchemaasObjectFormSchema['layout'], the keyObjectFormalready hands over in its spread).type: 'tabbed'.FormPage(/forms/:name,/f/:slug) drew every section stacked. A tabbed form view with more than one section now draws one tab per section with the app's tabbed-form semantics: the tab key is the section'sname, else its heading, else its position (TabbedForm's rule),defaultTabopens first,tabPositionplaces the strip (top / bottom / left / right), a section hidden by itsvisibleWhendraws no tab, and the panels are force-mounted and CSS-hidden when closed. A submit carries every section's fields from the page's onevaluesmap, closed tabs included; a submit refused for a missing or faulted field opens the tab that holds it. A one-section tabbed view draws stacked, asTabbedFormdraws no strip for one tab.layout: 'horizontal'too.FormPagerenders its own rows (not the shared renderer) and never readlayout, so after item 1 the form page would have been the one entry point still ignoring it. Its rows take the same arrangement (HORIZONTAL_ROW_CLASS). Same file as item 3, same gate families.Docs: the
layoutrow inui/packages/plugin-form/README.mdand theFormSchemablock inui/content/docs/components/form/form.mdxnow say what each value draws. Theobject-formregistration's input help already read "horizontalbeside it" and is unchanged.Changeset:
.changeset/26843-form-layout-horizontal-tabbed-page.md,minorfor@object-ui/components,@object-ui/plugin-formand@object-ui/console.Why
The card's rule is declared equals effective (ADR-0049):
layout: 'horizontal'andtype: 'tabbed'validate, save and publish, and the console drew them exactly as if they were absent. PR #27027 made the keys reach every console form entry point; this PR makes them draw.Human Minimum
packages/specholds no runtime logic. (packages/specnot touched.)pnpm lintpasses (ESLint on the changed files: 0 errors; the repo-wide run is CI's).main, one pull request.packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Tests
ui/packages/components/src/renderers/form/__tests__/form-layout-horizontal-26843.test.tsx(5): the grid classes and the label as first child on a horizontal item; every class horizontal adds issm:-prefixed and none is removed (stacked belowsm); a label-less field keeps its control in column two; the labels still name their controls (text, textarea, checkbox); CONTROL: absentlayoutrenders byte-identical tovertical(ids normalised), and horizontal differs.ui/packages/plugin-form/src/__tests__/tabbedFormLayout-26843.test.tsx(3): a tabbedObjectFormwithlayout: 'horizontal'draws both tabs' fields horizontal; CONTROLS for nolayoutandvertical.ui/apps/console/src/components/FormPage.tabbedLayout-26843.test.tsx(13): one tab per section named by its heading with every panel mounted;defaultTab; tab click;tabPosition: leftgives a vertical strip; a submit carriestitle,notesand a never-opened tab'sdefaultsvalue; a refused submit opens the tab of the missing required field; CONTROLS: one-section tabbed andtype: 'simple'draw stacked with no tab strip; horizontal rows (classes, label first, label names the control), kept on every tab; CONTROL: nolayoutandverticaldraw no grid row; the two tab-rule helpers.Acceptance notes
smbreakpoint, as the dispatch reading asked, not on a container query: a form with no container ancestor would never engage a container variant. Inside a narrow dialog at a wide viewport the label column is still 10rem.SplitFormandWizardForm(plugin-form) also hard-codelayout: 'vertical'on the form node they build, so a split or wizard form view dropshorizontalthe same wayTabbedFormdid. Outside this card's file surface; reported to the seat.typevalueswizard,split,drawerandmodalstill draw stacked sections (unchanged; outside this card, reported to the seat).namenor heading labels its tab with its position number (the app'sTabbedFormwrites an EnglishTab N); no catalogue key exists for either.Verification
All readings on head
763ea858(branch tip after mergingorigin/main) unless noted.New tests and wider suites
pnpm --dir ui exec vitest run --maxWorkers=2 packages/components/src/renderers/form/ packages/plugin-form/ apps/console/src/components/under the verify lock: Test Files 333 passed (333), Tests 3538 passed, 18 skipped; VERDICT command-exit 0.pnpm --filter @object-ui/components run type-check,@object-ui/plugin-form,@object-ui/consoleeach exit 0 (console after its closure build--filter '@object-ui/console^...', VERDICT command-exit 0).any/ exhaustive-deps lines).pnpm --dir ui run check:new-line-citations: 0 new citations.Ablations (each restored, blob equal to HEAD, git diff HEAD empty)
Run with
node scripts/ablation-replace.mjson the pre-merge head51be1b76; each anchor hit once and the mutation was verified on disk. The three test files import their subject by relative path, so no dist leg.? cn(colSpanClass, HORIZONTAL_FIELD_ITEM_CLASS)replaced by? colSpanClass || undefinedturns 3 tests red (grid classes, sm-prefix, label-less field).layout: schema.layout ?? 'vertical',replaced bylayout: 'vertical',turns 1 test red (every field on every tab horizontal).tabbedViewforced false turns 5 tests red (strip, defaultTab, tab click, tabPosition, refused submit opens the tab). The submit-carries-every-field test stays green on stacked sections, as a guard should.horizontalRowsforced false turns 2 tests red.Geometry in Chromium
The item and row classes compiled with the repository's Tailwind 4 (
compilefromtailwindcss, which emitted thenot(label)child rule) and drawn in the preinstalled Chromium. At 1024px: label x 16 width 160, input x 192, label vertically inside the input's box, description under the input at x 192. At 375px: label above the input, both at x 16. One-off measurement, not committed.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderives 34 families on this change set (same list before and after the merge). Each ran on763ea858with its exit code captured before any pipe: 34 of 34 exit 0.dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED.node scripts/check-sdui-manifest.mjsis among them (exit 0). On the pre-merge head theui/scriptsvitest family first exited 1 on two tree-walking tests that timed out at 15 s while a 20-minute locked suite shared the box; rerun under the lock it passed (175 files), and it passed again on763ea858.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQwxbXNVYZR3yMKLPJJvzL