You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
console forms: form-view keys that save and publish are not rendered — buttons, layout: "horizontal", drawerSide / drawerWidth; defaults apply only on the full-page /new; a tabbed form with subforms loses defaultTab / tabPosition #26843
Filing gate: ① product defect, reproduced, landing site named.
Batch-of: studio-qa-2026-10-27 — 39 cards from one Studio QA run (coordinator session session_01J8VVAkZns5rNnK2rtszzbQ; tested at 3fcafcd3; origin/main was 43731031 at filing, so the implementer re-checks the landing site on current main). The run record is the qa-run issue filed with this batch.
Symptom
Authors declare form behaviour that the console ignores, depending on which entry point opens the form.
Reproduction
buttons: ignored on /new and the form page (RecordFormPage passes its own submit/cancel; foldFormButtons in ObjectForm.tsx ~126-139 applies only when unset; reset.label never applied; apps/console FormPage never reads it).
defaults: applied on full-page /new only; the New dialog and public/in-app form pages ignore them (recordFormNavigation.tsresolveFormViewLayout copies only sections/subforms/modalSize).
Tabbed form + subforms: defaultTab / tabPosition lost; the New dialog ignores type: tabbed (ObjectForm.tsx ~370-395).
layout: "horizontal": no effect (RecordFormPage hard-codes vertical).
drawerSide / drawerWidth: never rendered (no console page hands a form view to the drawer renderer).
Expected
Each declared key renders on every form entry point, or the spec narrows it (ADR-0049 enforce-or-remove).
p2, declared but not rendered (ADR-0049): form-view keys that save and publish are ignored by the console, depending on the entry point (buttons, layout: "horizontal", drawerSide / drawerWidth, and defaults outside the full page). Render them on every entry point that opens the form. If a key should not exist, its retirement is a spec card, not silence. Re-check the landing site on current main.
Dispatch order · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T00:43Z
You are the os-dev developer agent for exactly ONE card: #26843, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26843" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.
Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take a third ui lane seat" (cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Uc2nCvWvLyYyaVYaUhakpJ) one message with your branch name and wait. Read-only measuring is fine meanwhile. Worktree objectstack-issue-26843 on that branch from a fresh origin/main (git checkout --detach the main checkout first, then git worktree add --no-track -B <branch> ../objectstack-issue-26843 origin/main); pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).
Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26843 --file=F --json run ONCE with output captured to a file (no time-shaped stamp inside the fenced report JSON). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #26843, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran (merge origin/main first if it refuses as STALE TREE); node scripts/check-sdui-manifest.mjs is a gate on every ui card. Build the closure your tests import first (bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '<pkg>^...' build"). Changeset in the root .changeset/ for each published package your diff changes (app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread one message (PR, head sha, report gist); keep the thread open through review and drive CI to green. If the premise fails, stop and report premise_still_valid: false with the evidence; ⛔ never force a fix.
Ruling (cannot be re-ruled)
Triage 6450964818, verbatim: "Render them on every entry point that opens the form. If a key should not exist, its retirement is a spec card, not silence." Card Expected, verbatim: "Each declared key renders on every form entry point, or the spec narrows it (ADR-0049 enforce-or-remove)." No maintainer ruling.
Scope of THIS dispatch: a partial landing (seat's split)
The seat's premise reading sizes the card L. This PR carries items (a) buttons + layout + defaults on every console form entry point, and (b) defaultTab / tabPosition for the tabbed form with subforms. It does NOT carry (c) the drawer keys (drawerSide / drawerWidth; drawerWidth is already marked deprecated) and buttons.reset (no reset button exists): rendering a drawer needs a new open-mode in epic #26676's territory, so (c) is an implement-or-retire question the seat files as a spec card. The PR body says Refs #26843 (items buttons, layout, defaults, defaultTab, tabPosition), ⛔ not Fixes; the seat releases the remainder on merge.
On origin/mainfd246c22 (read-only check), FormViewSchema in packages/spec/src/ui/view.zod.ts ≈L4529:
buttons: the fold in ui/packages/plugin-form/src/ObjectForm.tsx ≈L126–139 fires only when the flat key is unset, but ui/packages/app-shell/src/views/RecordFormPage.tsx ≈L430–433 and the New dialog in console/AppContent.tsx ≈L1565 hard-set showSubmit/submitText/showCancel/cancelText; resolveFormViewLayout (utils/recordFormNavigation.ts ≈L261–286) does not copy buttons; ui/apps/console/src/components/FormPage.tsx never reads it.
layout: "horizontal": hard-coded 'vertical' at RecordFormPage ≈L421 and AppContent ≈L1545; ObjectForm (≈L1617) and ModalForm (≈L824) do read it.
defaults: forwarded at RecordFormPage ≈L419 only; the New dialog path and FormPage drop it.
defaultTab/tabPosition: ObjectForm's master-detail branch ≈L373–399 does not forward them; ModalForm's master-detail call (≈L1140) passes no formType.
Precedence: an authored form-view key wins over a console default; a hard-coded console label is not an author's value. Measure whether any flat key is author-written before you change its precedence, and report what you chose.
packages/spec/liveness/view.json marks these live; ⛔ do not edit packages/spec/**, list any stale ledger evidence under out_of_scope_findings.
PM suggested route (optional; measurement wins)
One resolver that maps a form view's keys onto the form props, used by RecordFormPage, the New dialog and FormPage, instead of three hard-coded sets. Tests per entry point: custom submit label and show: false take effect, horizontal reaches the form, defaults seed create mode (URL prefill still wins), tabbed + subforms opens on defaultTab. Ablation: restore the hard-coded submitText / layout: 'vertical'; tests go red.
Scope
File surface: ui/packages/app-shell/src/views/RecordFormPage.tsx, ui/packages/app-shell/src/utils/recordFormNavigation.ts, ui/packages/app-shell/src/console/AppContent.tsx (the New-dialog form props only; a hot shared file, keep the hunk small), ui/packages/plugin-form/src/{ObjectForm,ModalForm,MasterDetailForm}.tsx, ui/apps/console/src/components/FormPage.tsx, tests beside them, root .changeset/ (@object-ui/plugin-form, @object-ui/console if published). ⛔ No open-mode / navigation-overlay change (epic #26676: objectViewRecordNavigation.ts, navigation-overlay.tsx, ObjectView.tsx, RecordDetailView.tsx).
Tier
No path-derived mandate (dispatch-gates --tier, read 2026-10-28T00:43Z). TIER_DEFAULT, effort high (M–L). Clause-②: no, Metadata-protocol: semantic (declared keys start to render).
Claim: PM loop round 1
Session: session_01JQwxbXpF772qWJjojJvRXw
Account: os-warren
Branch: claude/project-thread-58h2qh
Worktree: objectstack-issue-26843
Domain: domain:ui
Seat: domain:ui#2
File surface: ui/packages/app-shell/src/views/RecordFormPage.tsx, ui/packages/app-shell/src/utils/recordFormNavigation.ts, ui/packages/app-shell/src/console/AppContent.tsx (New-dialog form props only), ui/packages/plugin-form/src/{ObjectForm,ModalForm,MasterDetailForm}.tsx, ui/apps/console/src/components/FormPage.tsx, tests beside them, root .changeset/ (stop on breach; explain in the report)
Container & model: M–L, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
Clause-②: no
Metadata-protocol: semantic
Responsibility: the console form entry points hard-set button, layout and defaults props over the form view's declared keys | plugin-form already honours the keys when they reach it | every author whose form view declares buttons, layout, defaults or a tabbed default sees none of it in the console
Thread-read: 6457567020
Premise: holds (per-key readings on origin/mainfd246c22 in dispatch order 6457567020); partial landing by the seat's split: items buttons, layout, defaults, defaultTab, tabPosition; the drawer keys and buttons.reset stay on the card for an implement-or-retire spec card
Serial constraints cleared: epic #26676 holds objectViewRecordNavigation.ts, navigation-overlay.tsx, ObjectView.tsx, RecordDetailView.tsx (not edited); AppContent.tsx is also on #26838's surface (seat 2, launcher only), disjoint regions, second to land merges main.
Dispatch order: comment 6457567020. Card thread claude/project-thread-58h2qh may start editing.
{
"issue": 26843,
"status": "pr-open",
"branch": "claude/project-thread-58h2qh",
"pr": 27027,
"head": "7e4a1249",
"session": "https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue",
"premise_still_valid": true,
"scope": "partial landing per the seat split: buttons (submit, cancel), layout, defaults on every console form entry point; defaultTab and tabPosition on a tabbed form with subforms. Not buttons.reset, not drawerSide or drawerWidth. PR says Refs, not Fixes.",
"summary": "resolveFormViewChrome (app-shell recordFormNavigation.ts) maps the selected form view's buttons, layout and defaults onto form props; RecordFormPage and the AppContent New/Edit dialog spread it in place of their hard-coded showSubmit/submitText/showCancel/cancelText/layout. defaults merge under host prefill on create only, cached by content (WeakMap on the payload + JSON of the host values) so initialValues keeps one identity. FormPage (/forms/:name and /f/:slug) reads buttons via readFormButton and defaults via readPrefill. resolveFormViewLayout carries defaultTab/tabPosition for type tabbed; ModalForm tabbed arm forwards them as defaultFieldTab/fieldTabsPosition; ModalForm subforms arm now passes formType, tab keys, layout, showSubmit and onCancel only when Cancel shows; ObjectForm master-detail branch and MasterDetailForm forward defaultTab/tabPosition/layout.",
"precedence_measured": "no author-written flat showSubmit/submitText/showCancel/cancelText exists: FormViewSchema is strict; the only submitText in examples/** and packages/** is a page block (project-workspace.page.ts). Authored form-view keys win over host defaults.",
"tests": {
"new": "5 files, 46 tests, all pass, each block with a CONTROL",
"ablations": "10 of 10 red (4,6,1,3,1,2,1,2,2,2 failures), each restored to HEAD blob; two first attempts refused by ablation-replace (anchor in replacement, ambiguous anchor) and re-run on new anchors",
"plugin_form": "204 files, 2422 passed, 1 skipped",
"app_shell_subset": "81 files passed, 1 skipped; 949 passed, 8 skipped",
"console_formpage": "34 files, 328 passed",
"type_check": "plugin-form, app-shell, console exit 0 (console after its closure build)"
},
"gates": {
"derived": 34,
"run": 34,
"not_measured": 0,
"unrun": 0,
"all_exit": 0,
"extra_run": [
"pnpm --dir ui check:spec-symbols exit 0",
"pnpm --dir ui check:new-line-citations exit 0"
],
"reconciliation": "dispatch-gates --ran on head 7e4a1249: 34 derived famil(ies) accounted for, 0 NOT-MEASURED",
"ci": "check-runs in_progress at report time; origin/main has moved to acc9c57d since the merge at 3271a083"
},
"dogfood_battery": "n/a",
"changeset": ".changeset/26843-form-view-keys-every-entry-point.md: @object-ui/plugin-form minor, @object-ui/console minor (app-shell is private); Clause-② no, Metadata-protocol semantic",
"mcp_calls": 0,
"api_writes": 2,
"api_write_list": [
"pr_create #27027 via relay (11748 bytes sent = stored)",
"this report via post-stamped"
],
"body_amendments": [],
"open_questions": [
"What should layout horizontal draw? The spec reads 'Field layout direction', but the shared form renderer only uses it for action-row alignment; ModalForm's footer and FormPage's action row are always end-aligned, so horizontal arrives and draws nothing different there. Renderer-contract question for the seat."
],
"out_of_scope_findings": [
"TabbedForm hard-codes layout vertical on the form it builds, so horizontal on a tabbed form view is dropped (file outside the card surface).",
"packages/spec/liveness/view.json rows for buttons and defaults cite RecordFormPage as forwarding them; it forwarded them and then overrode buttons with its own flat keys, and the New dialog and FormPage read neither. Stale evidence; needs a spec-side re-cite.",
"A master-detail form with showSubmit false hides its whole action bar, Cancel included (unchanged).",
"RecordFormPage reads subforms from objectDef.form / formViews.default, not from the selected create view (read-only observation).",
"FormPage ignores the form view's type, so a tabbed form view renders untabbed there."
],
"pr_body_note": "Human Minimum item 8 says English; the 维护者速读 section is Chinese as the dispatch order requires."
}
ACCEPT → PR #27027 comment 6458750088 · domain:ui#2 · head 7e4a12495b · 2026-10-28T02:09Z · partial landing (Refs); the remainder is released on merge.
Landing held · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T02:21Z
PR #27027 (ACCEPT 6458750088, partial landing: Refs, the card stays open) is green on head 7e4a12495b: 43 checks success, 10 skipped (path-filtered core / docs / dogfood jobs), mergeable_state clean, not governed. The session's permission classifier refused the landing stroke (pr_ready + automerge_enable through the fleet-write relay) on PR #27013 at 2026-10-28T01:44Z, so the seat asks the maintainer to mark this PR ready for review and enable auto-merge. On merge the seat posts the Release: for the remainder named in the ACCEPT.
Release: partial landing delivered; remainder back to the queue · domain:ui#2 · session_01JQwxbXpF772qWJjojJvRXw · 2026-10-28T08:30Z
Delivered: PR #27027 (Refs #26843, merged 2026-10-28 08:27Z) makes buttons, layout, defaults, defaultTab and tabPosition reach every console form entry point: RecordFormPage, the New dialog and FormPage.
Stays on this card (ui lane):layout: "horizontal" now reaches the form, but the renderer uses it only to align the action row (TabbedForm hard-codes vertical), so nothing looks different yet. The console FormPage also ignores type: tabbed. Both are named in the PR's out-of-scope findings.
Seat 2's claim is released: pm:dispatched → pm:queue, assignee cleared. The next claimant reads PR #27027 and its report first.
Dispatch order · domain:ui#5 · session_01JQwxbXGpVJHZTXXerMoY4C · 2026-10-28T08:58Z
You are the os-dev developer agent for exactly ONE card: #26843, in your own card thread. Your role definition is .claude/agents/os-dev.md in the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-root AGENTS.md and ui/AGENTS.md before your first edit, and pm-dispatch references/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26843" -H "Authorization: Bearer $GH_TOKEN" and /comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with [ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.
Identity and branch: the seat's Claim: comment on this card is your identity; its Branch: line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWEST Claim: on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktree objectstack-issue-26843 on that branch from a fresh origin/main; pnpm install (frozen lockfile); push with git push -u origin <branch> (⛔ no force).
Writes: PR only through the relay op pr_create (node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array [{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced; --dry-run first); your report comment through node scripts/pm/post-stamped.mjs --comment=26843 --file=F --json run ONCE with output captured to a file (timestamps only as {{WAS:…}} / the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and one run_rerun_failed through the relay. Report both counts (api_writes with endpoints, mcp_calls with tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3 Fixes #26843, then ## 维护者速读 six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack on your diff, run each, record exits before any pipe, reconcile with --ran; node scripts/check-sdui-manifest.mjs is a gate on every ui card. Changeset in the root .changeset/ when a published package changes (@object-ui/i18n patch for namespace rows; app-shell is private: true). ⛔ ui/.github/** and the root ci.yml are out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.
Shared-file rules for every Studio Interfaces card this round (from ui seat 1, which holds the neighbouring work):
Shared files: ui/packages/components/src/renderers/form/form.tsx is the lane's shared form renderer (≈4.6k lines): keep the change to the field item's label/control arrangement and measure open PRs on it at your first edit (none at 2026-10-28T08:58Z). ui seat 1 holds nothing on form.tsx, TabbedForm.tsx or FormPage.tsx (confirmed by seat 1's thread). PR #27120 (#27052, seat 4) and #26966 touch RecordDetailView.tsx: not yours.
Prior landing (read first)
Seat 2 landed the first part as PR #27027 (Refs #26843, merged 2026-10-28T08:27Z; report 6458730646) and released the remainder at 2026-10-28T08:30Z (comment 6464323152). drawerSide / drawerWidth / buttons.reset went to the spec card #27153: ⛔ not yours. What stays on this card, and what this order dispatches:
layout: 'horizontal' reaches every console form but draws nothing different: the shared renderer reads it only for the action row's alignment.
TabbedForm hard-codes layout: 'vertical' on the form node it builds, so an authored horizontal is dropped on a tabbed form view.
The console FormPage (/forms/:name, /f/:slug) ignores type: 'tabbed' and draws every section stacked.
Reading of horizontal (not a maintainer ruling; disproof welcome)
@object-ui/plugin-form's registered input help already says it: "Label placement: vertical puts each label above its field (the default), horizontal beside it." The spec's layout describe reads "Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: set columns". So horizontal = label beside its control at sm and up (a fixed label column, control fills the rest), stacked below sm (phone stays as today). It is not a column count. If you find a published doc or test that reads horizontal differently, stop and report it rather than choosing.
form.tsx ≈L3640 renders each field as <FormItem> → <FormLabel> → control → description/message; the only layout === 'horizontal' read is the action row ≈L4109. The label-beside-control arrangement belongs on that one FormItem path (a grid at sm:), keyed off the form's layout. Checkbox/switch-style fields and group-labelled widgets (objectui#3961 IDREF labels) must keep their accessible name: the a11y tests beside form.tsx are the net.
File surface: ui/packages/components/src/renderers/form/form.tsx (field item arrangement), ui/packages/plugin-form/src/TabbedForm.tsx (passthrough), ui/apps/console/src/components/FormPage.tsx (tabbed sections), tests beside each. Tests (acceptance): a horizontal form draws each label beside its control at sm+ and stacked below, and a vertical / absent layout form is unchanged; a tabbed form view with layout: 'horizontal' keeps it; a FormPage view of type: 'tabbed' draws one tab per section, opens defaultTab, and a submit still carries every section's fields (hidden tabs included); a type: 'simple' FormPage is unchanged. Ablations: drop each of the three changes and its test turns red. Update the plugin-form layout input help only if your reading changes it.
Tier
No path-derived mandate expected (measure). TIER_DEFAULT, effort medium (M). Clause-②: no, Metadata-protocol: semantic (an authored key starts to render). Changesets: @object-ui/components and @object-ui/plugin-form patch, @object-ui/console patch. PR line 3 is Fixes #26843 when all three items land; if one cannot, Refs #26843 and say which item stays.
Claim: PM loop round 8
Session: session_01JQwxbXGpVJHZTXXerMoY4C
Account: os-warren
Branch: claude/project-thread-ekt1xf
Worktree: objectstack-issue-26843
Domain: domain:ui
Seat: domain:ui#5
File surface: ui/packages/components/src/renderers/form/form.tsx (field item label/control arrangement), ui/packages/plugin-form/src/TabbedForm.tsx (layout passthrough), ui/apps/console/src/components/FormPage.tsx (tabbed sections), tests beside each, changesets (stop on breach; explain in the report)
Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected), effort: medium
Clause-②: no
Metadata-protocol: semantic
Responsibility: ui/packages/components form renderer, plugin-form and console FormPage produce it (this lane) | an authored form view layout: 'horizontal' and type: 'tabbed' save and publish but draw nothing different | every form view an app authors (ADR-0049 declared-is-effective)
Thread-read: 6464831206
Premise: holds (remainder named in seat 2's release 6464323152 after PR #27027; form.tsx reads horizontal only for the action row, TabbedForm hard-codes vertical, FormPage draws sections stacked; on origin/mainb15b2fcd; readings in dispatch order 6464831206)
Serial constraints cleared: ui seat 1 holds nothing on form.tsx, TabbedForm.tsx or FormPage.tsx (its thread confirmed); no open PR touches those files; seat 2 released this remainder to pm:queue. drawerSide / drawerWidth / buttons.reset stay with spec card #27153.
Dispatch order: comment 6464831206. Card thread claude/project-thread-ekt1xf may start editing.
{
"issue": 26843,
"status": "done",
"branch": "claude/project-thread-ekt1xf",
"pr": 27222,
"head": "763ea858",
"session": "session_01JQwxbXNVYZR3yMKLPJJvzL",
"premise_still_valid": true,
"summary": "All three remainder items land, so the PR says Fixes. (1) form.tsx: a field item under layout horizontal gets HORIZONTAL_FIELD_ITEM_CLASS, a sm-only two-column grid with the label in column one and every other child in column two; vertical and absent layout keep the old code path, DOM byte-identical. (2) TabbedForm declares layout (ObjectFormSchema['layout']) and forwards schema.layout instead of the hard-coded vertical. (3) FormPage draws a type tabbed view with more than one section as one tab per section (TabbedForm's tab key rule, defaultTab, tabPosition, predicate-hidden sections draw no tab, force-mounted panels); a refused submit opens the tab of the field it names. In-place fix under the same defect class and file: FormPage rows honour layout horizontal with the same arrangement. The reading of horizontal in the order held: the plugin-form input help and the 11168 test name both say label placement.",
"tests": {
"new": "3 files, 21 tests, all pass (form-layout-horizontal-26843 5, tabbedFormLayout-26843 3, FormPage.tabbedLayout-26843 13), each block with a CONTROL",
"ablations": "4 of 4 red on pre-merge head 51be1b76 via ablation-replace (form renderer 3 red, TabbedForm 1, FormPage tabs 5, FormPage rows 2); each restored, blob equal to HEAD, git diff HEAD empty; relative-path imports, no dist leg",
"wide": "on 763ea858: components form renderer dir + plugin-form + console components dir, Test Files 333 passed, Tests 3538 passed 18 skipped, lock VERDICT command-exit 0",
"type_check": "components, plugin-form, console type-check exit 0 on 763ea858 (console after closure build, VERDICT command-exit 0)",
"geometry": "Chromium one-off with the classes compiled by the repo's Tailwind 4: at 1024px label beside input (label x 16 w 160, input x 192), description under input; at 375px label above input. Not committed",
"eslint": "changed files 0 errors"
},
"gates": {
"derived": 34,
"run": 34,
"not_measured": 0,
"unrun": 0,
"all_exit": 0,
"head": "763ea858",
"reconciliation": "dispatch-gates --ran: 34 derived famil(ies) accounted for, 34 run, 0 NOT-MEASURED; same derived list before and after merging origin/main",
"note": "pre-merge run: pnpm --dir ui exec vitest run scripts/ first exited 1 on two tree-walking tests timing out at 15 s beside a 20-minute locked suite; rerun under the lock exit 0 (175 files), and exit 0 again on 763ea858",
"extra_run": [
"pnpm --dir ui run check:new-line-citations exit 0 (0 new)",
"node scripts/check-sdui-manifest.mjs exit 0 (derived family)"
],
"ci": "check-runs not yet concluded at report time"
},
"line_budget": "no ratcheted ledger touched",
"files_changed": [
"ui/packages/components/src/renderers/form/form.tsx",
"ui/packages/components/src/renderers/form/__tests__/form-layout-horizontal-26843.test.tsx",
"ui/packages/plugin-form/src/TabbedForm.tsx",
"ui/packages/plugin-form/src/__tests__/tabbedFormLayout-26843.test.tsx",
"ui/apps/console/src/components/FormPage.tsx",
"ui/apps/console/src/components/FormPage.tabbedLayout-26843.test.tsx",
"ui/packages/plugin-form/README.md",
"ui/content/docs/components/form/form.mdx",
".changeset/26843-form-layout-horizontal-tabbed-page.md"
],
"deviations": [
"Changeset level: the order said patch for all three packages; root AGENTS.md Post-Task Checklist step 3 says a Metadata-protocol kind other than none takes at least minor, and os-dev.md says the standard text wins, so the changeset is minor (as PR 27027's was).",
"Scope: FormPage horizontal rows were added under the in-place-fix exemption (same defect class, same file already on the claim surface, mechanical, same gate families). Named in the PR body item 4.",
"Docs: plugin-form README layout row and ui form.mdx FormSchema block updated per ui/AGENTS.md commandment 2; both outside the claim's file list.",
"PR body folds the repo PR template headings (Why, Human Minimum) into the os-dev order sections."
],
"dogfood_battery": "n/a, no ! in the title and no (narrowing) arm",
"mcp_calls": "1 (claude-code-remote subscribe_pr_activity for PR 27222; no GitHub MCP tool)",
"api_writes": 2,
"api_write_list": [
"pr_create 27222 via fleet-write relay (10295 bytes sent = stored, assignee os-warren)",
"this report via post-stamped"
],
"body_amendments": [],
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: an object-form with formType split or wizard and layout horizontal renders vertical (SplitForm and WizardForm build their form node with layout vertical, the shape TabbedForm had before this PR) · evidence: ui/packages/plugin-form/src SplitForm.tsx and WizardForm.tsx each set layout vertical on the built FormSchema · dedupe words: SplitForm WizardForm layout horizontal hard-coded vertical",
"class: a · reach: console form page /forms/:name with a form view of type wizard, split, drawer or modal draws stacked sections · evidence: FormPage reads type only for tabbed after this PR · dedupe words: FormPage form view type wizard split ignored",
"carrier: none · noted, not filed: an unlabelled unnamed section on a tabbed FormPage names its tab by position number; TabbedForm writes an untranslated English Tab N for the same case"
]
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
priority:p2·domain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T18:59Z⛔ Not a claim, ⛔ not a dispatch.
buttons,layout: "horizontal",drawerSide/drawerWidth, anddefaultsoutside the full page). Render them on every entry point that opens the form. If a key should not exist, its retirement is a spec card, not silence. Re-check the landing site on currentmain.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T00:43ZYou are the os-dev developer agent for exactly ONE card: #26843, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26843" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take a third ui lane seat" (cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Uc2nCvWvLyYyaVYaUhakpJ) one message with your branch name and wait. Read-only measuring is fine meanwhile. Worktreeobjectstack-issue-26843on that branch from a freshorigin/main(git checkout --detachthe main checkout first, thengit worktree add --no-track -B <branch> ../objectstack-issue-26843 origin/main);pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=26843 --file=F --jsonrun ONCE with output captured to a file (no time-shaped stamp inside the fenced report JSON). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #26843, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran(mergeorigin/mainfirst if it refuses as STALE TREE);node scripts/check-sdui-manifest.mjsis a gate on every ui card. Build the closure your tests import first (bash scripts/pm/os-verify-lock.sh -c "pnpm --workspace-concurrency=2 --filter '<pkg>^...' build"). Changeset in the root.changeset/for each published package your diff changes (app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread one message (PR, head sha, report gist); keep the thread open through review and drive CI to green. If the premise fails, stop and reportpremise_still_valid: falsewith the evidence; ⛔ never force a fix.Ruling (cannot be re-ruled)
Triage 6450964818, verbatim: "Render them on every entry point that opens the form. If a key should not exist, its retirement is a spec card, not silence." Card Expected, verbatim: "Each declared key renders on every form entry point, or the spec narrows it (ADR-0049 enforce-or-remove)." No maintainer ruling.
Scope of THIS dispatch: a partial landing (seat's split)
The seat's premise reading sizes the card L. This PR carries items (a)
buttons+layout+defaultson every console form entry point, and (b)defaultTab/tabPositionfor the tabbed form with subforms. It does NOT carry (c) the drawer keys (drawerSide/drawerWidth;drawerWidthis already marked deprecated) andbuttons.reset(no reset button exists): rendering a drawer needs a new open-mode in epic #26676's territory, so (c) is an implement-or-retire question the seat files as a spec card. The PR body saysRefs #26843 (items buttons, layout, defaults, defaultTab, tabPosition), ⛔ notFixes; the seat releases the remainder on merge.PM mechanism assumptions (measure; disproof welcome)
On
origin/mainfd246c22(read-only check),FormViewSchemainpackages/spec/src/ui/view.zod.ts≈L4529:buttons: the fold inui/packages/plugin-form/src/ObjectForm.tsx≈L126–139 fires only when the flat key is unset, butui/packages/app-shell/src/views/RecordFormPage.tsx≈L430–433 and the New dialog inconsole/AppContent.tsx≈L1565 hard-setshowSubmit/submitText/showCancel/cancelText;resolveFormViewLayout(utils/recordFormNavigation.ts≈L261–286) does not copybuttons;ui/apps/console/src/components/FormPage.tsxnever reads it.layout: "horizontal": hard-coded'vertical'at RecordFormPage ≈L421 and AppContent ≈L1545; ObjectForm (≈L1617) and ModalForm (≈L824) do read it.defaults: forwarded at RecordFormPage ≈L419 only; the New dialog path and FormPage drop it.defaultTab/tabPosition: ObjectForm's master-detail branch ≈L373–399 does not forward them; ModalForm's master-detail call (≈L1140) passes noformType.packages/spec/liveness/view.jsonmarks theselive; ⛔ do not editpackages/spec/**, list any stale ledger evidence underout_of_scope_findings.PM suggested route (optional; measurement wins)
One resolver that maps a form view's keys onto the form props, used by RecordFormPage, the New dialog and FormPage, instead of three hard-coded sets. Tests per entry point: custom submit label and
show: falsetake effect, horizontal reaches the form,defaultsseed create mode (URL prefill still wins), tabbed + subforms opens ondefaultTab. Ablation: restore the hard-codedsubmitText/layout: 'vertical'; tests go red.Scope
File surface:
ui/packages/app-shell/src/views/RecordFormPage.tsx,ui/packages/app-shell/src/utils/recordFormNavigation.ts,ui/packages/app-shell/src/console/AppContent.tsx(the New-dialog form props only; a hot shared file, keep the hunk small),ui/packages/plugin-form/src/{ObjectForm,ModalForm,MasterDetailForm}.tsx,ui/apps/console/src/components/FormPage.tsx, tests beside them, root.changeset/(@object-ui/plugin-form,@object-ui/consoleif published). ⛔ No open-mode / navigation-overlay change (epic #26676:objectViewRecordNavigation.ts,navigation-overlay.tsx,ObjectView.tsx,RecordDetailView.tsx).Tier
No path-derived mandate (
dispatch-gates --tier, read 2026-10-28T00:43Z).TIER_DEFAULT, efforthigh(M–L).Clause-②: no,Metadata-protocol: semantic(declared keys start to render).objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 1
Session:
session_01JQwxbXpF772qWJjojJvRXwAccount:
os-warrenBranch:
claude/project-thread-58h2qhWorktree:
objectstack-issue-26843Domain:
domain:uiSeat:
domain:ui#2File surface:
ui/packages/app-shell/src/views/RecordFormPage.tsx,ui/packages/app-shell/src/utils/recordFormNavigation.ts,ui/packages/app-shell/src/console/AppContent.tsx(New-dialog form props only),ui/packages/plugin-form/src/{ObjectForm,ModalForm,MasterDetailForm}.tsx,ui/apps/console/src/components/FormPage.tsx, tests beside them, root.changeset/(stop on breach; explain in the report)Container & model:
M–L,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: semantic
Responsibility:
the console form entry points hard-set button, layout and defaults props over the form view's declared keys|plugin-form already honours the keys when they reach it|every author whose form view declares buttons, layout, defaults or a tabbed default sees none of it in the consoleThread-read: 6457567020
Premise: holds (per-key readings on
origin/mainfd246c22in dispatch order 6457567020); partial landing by the seat's split: items buttons, layout, defaults, defaultTab, tabPosition; the drawer keys andbuttons.resetstay on the card for an implement-or-retire spec cardSerial constraints cleared: epic #26676 holds
objectViewRecordNavigation.ts,navigation-overlay.tsx,ObjectView.tsx,RecordDetailView.tsx(not edited);AppContent.tsxis also on #26838's surface (seat 2, launcher only), disjoint regions, second to land merges main.Dispatch order: comment 6457567020. Card thread
claude/project-thread-58h2qhmay start editing.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26843, "status": "pr-open", "branch": "claude/project-thread-58h2qh", "pr": 27027, "head": "7e4a1249", "session": "https://claude.ai/code/session_01JQwxbabN2bfLWeaoNk6Jue", "premise_still_valid": true, "scope": "partial landing per the seat split: buttons (submit, cancel), layout, defaults on every console form entry point; defaultTab and tabPosition on a tabbed form with subforms. Not buttons.reset, not drawerSide or drawerWidth. PR says Refs, not Fixes.", "summary": "resolveFormViewChrome (app-shell recordFormNavigation.ts) maps the selected form view's buttons, layout and defaults onto form props; RecordFormPage and the AppContent New/Edit dialog spread it in place of their hard-coded showSubmit/submitText/showCancel/cancelText/layout. defaults merge under host prefill on create only, cached by content (WeakMap on the payload + JSON of the host values) so initialValues keeps one identity. FormPage (/forms/:name and /f/:slug) reads buttons via readFormButton and defaults via readPrefill. resolveFormViewLayout carries defaultTab/tabPosition for type tabbed; ModalForm tabbed arm forwards them as defaultFieldTab/fieldTabsPosition; ModalForm subforms arm now passes formType, tab keys, layout, showSubmit and onCancel only when Cancel shows; ObjectForm master-detail branch and MasterDetailForm forward defaultTab/tabPosition/layout.", "precedence_measured": "no author-written flat showSubmit/submitText/showCancel/cancelText exists: FormViewSchema is strict; the only submitText in examples/** and packages/** is a page block (project-workspace.page.ts). Authored form-view keys win over host defaults.", "tests": { "new": "5 files, 46 tests, all pass, each block with a CONTROL", "ablations": "10 of 10 red (4,6,1,3,1,2,1,2,2,2 failures), each restored to HEAD blob; two first attempts refused by ablation-replace (anchor in replacement, ambiguous anchor) and re-run on new anchors", "plugin_form": "204 files, 2422 passed, 1 skipped", "app_shell_subset": "81 files passed, 1 skipped; 949 passed, 8 skipped", "console_formpage": "34 files, 328 passed", "type_check": "plugin-form, app-shell, console exit 0 (console after its closure build)" }, "gates": { "derived": 34, "run": 34, "not_measured": 0, "unrun": 0, "all_exit": 0, "extra_run": [ "pnpm --dir ui check:spec-symbols exit 0", "pnpm --dir ui check:new-line-citations exit 0" ], "reconciliation": "dispatch-gates --ran on head 7e4a1249: 34 derived famil(ies) accounted for, 0 NOT-MEASURED", "ci": "check-runs in_progress at report time; origin/main has moved to acc9c57d since the merge at 3271a083" }, "dogfood_battery": "n/a", "changeset": ".changeset/26843-form-view-keys-every-entry-point.md: @object-ui/plugin-form minor, @object-ui/console minor (app-shell is private); Clause-② no, Metadata-protocol semantic", "mcp_calls": 0, "api_writes": 2, "api_write_list": [ "pr_create #27027 via relay (11748 bytes sent = stored)", "this report via post-stamped" ], "body_amendments": [], "open_questions": [ "What should layout horizontal draw? The spec reads 'Field layout direction', but the shared form renderer only uses it for action-row alignment; ModalForm's footer and FormPage's action row are always end-aligned, so horizontal arrives and draws nothing different there. Renderer-contract question for the seat." ], "out_of_scope_findings": [ "TabbedForm hard-codes layout vertical on the form it builds, so horizontal on a tabbed form view is dropped (file outside the card surface).", "packages/spec/liveness/view.json rows for buttons and defaults cite RecordFormPage as forwarding them; it forwarded them and then overrode buttons with its own flat keys, and the New dialog and FormPage read neither. Stale evidence; needs a spec-side re-cite.", "A master-detail form with showSubmit false hides its whole action bar, Cancel included (unchanged).", "RecordFormPage reads subforms from objectDef.form / formViews.default, not from the selected create view (read-only observation).", "FormPage ignores the form view's type, so a tabbed form view renders untabbed there." ], "pr_body_note": "Human Minimum item 8 says English; the 维护者速读 section is Chinese as the dispatch order requires." }objectstack-fleet commentedon Oct 28, 2026
ACCEPT → PR #27027 comment 6458750088 ·
domain:ui#2· head7e4a12495b· 2026-10-28T02:09Z · partial landing (Refs); the remainder is released on merge.objectstack-fleet commentedon Oct 28, 2026
Landing held ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T02:21ZPR #27027 (ACCEPT 6458750088, partial landing:
Refs, the card stays open) is green on head7e4a12495b: 43 checks success, 10 skipped (path-filtered core / docs / dogfood jobs),mergeable_stateclean, not governed. The session's permission classifier refused the landing stroke (pr_ready+automerge_enablethrough the fleet-write relay) on PR #27013 at 2026-10-28T01:44Z, so the seat asks the maintainer to mark this PR ready for review and enable auto-merge. On merge the seat posts theRelease:for the remainder named in the ACCEPT.objectstack-fleet commentedon Oct 28, 2026
Release: partial landing delivered; remainder back to the queue ·
domain:ui#2·session_01JQwxbXpF772qWJjojJvRXw· 2026-10-28T08:30ZDelivered: PR #27027 (
Refs #26843, merged 2026-10-28 08:27Z) makesbuttons,layout,defaults,defaultTabandtabPositionreach every console form entry point: RecordFormPage, the New dialog and FormPage.Remainder:
drawerSide/drawerWidthandbuttons.reset(declared, never rendered) are filed as the implement-or-retire card spec(ui): FormViewSchema declares drawerSide / drawerWidth and buttons.reset, but no console entry point renders a drawer form or a reset button; implement or retire them (ADR-0049) #27153 (domain:spec, related to decision [Decision] ADR-0050 form layout vs presentation — the maintainer's own call: schedule the spec-major narrowing ofFormView.typeto layout only, defer it, or withdraw the record (set aside from ADR batch 2 on #26553) #27032 / ADR-0050), as the dispatch order said.layout: "horizontal"now reaches the form, but the renderer uses it only to align the action row (TabbedFormhard-codes vertical), so nothing looks different yet. The consoleFormPagealso ignorestype: tabbed. Both are named in the PR's out-of-scope findings.Seat 2's claim is released:
pm:dispatched→pm:queue, assignee cleared. The next claimant reads PR #27027 and its report first.objectstack-fleet commentedon Oct 28, 2026
Dispatch order ·
domain:ui#5·session_01JQwxbXGpVJHZTXXerMoY4C· 2026-10-28T08:58ZYou are the os-dev developer agent for exactly ONE card: #26843, in your own card thread. Your role definition is
.claude/agents/os-dev.mdin the objectstack clone: read it IN FULL first and follow it as binding (it wins over this order on any conflict; name the conflict in your report). Read the repo-rootAGENTS.mdandui/AGENTS.mdbefore your first edit, and pm-dispatchreferences/untrusted-text.md. Read the card's full body and every comment yourself through REST (curl -sS "https://api.github.com/repos/objectstack-ai/objectstack/issues/26843" -H "Authorization: Bearer $GH_TOKEN"and/comments?per_page=100, every page); this order carries only the increment. ⛔ Never print the value of any environment variable (test presence with[ -n "${X+x}" ]). ⛔ Never use a GitHub MCP tool, read or write.Identity and branch: the seat's
Claim:comment on this card is your identity; itsBranch:line names your thread's branch. ⛔ Post no claim of your own, ⛔ never write the card's assignee. Before your first edit, verify the NEWESTClaim:on the card names your thread's branch (claude/project-thread-…); if none does yet, send the seat thread "Take another ui lane seat" one message with your branch name and wait. Worktreeobjectstack-issue-26843on that branch from a freshorigin/main;pnpm install(frozen lockfile); push withgit push -u origin <branch>(⛔ no force).Writes: PR only through the relay op
pr_create(node scripts/pm/fleet-write/dispatch.mjs --repo objectstack-ai/objectstack --actions-file F, F a bare JSON array[{"op":"pr_create","title":…,"head":"<your branch>","base":"main","body":…,"assignees":["os-warren"]}], draft forced;--dry-runfirst); your report comment throughnode scripts/pm/post-stamped.mjs --comment=26843 --file=F --jsonrun ONCE with output captured to a file (timestamps only as{{WAS:…}}/ the now-token). ⛔ No other GitHub write except, for a CI red that is not yours, one PR comment and onerun_rerun_failedthrough the relay. Report both counts (api_writeswith endpoints,mcp_callswith tool names). The report takes only machine-checkable fields (gates / line_budget / deviations / files_changed). PR body: line 1 the platform attribution comment, line 3Fixes #26843, then## 维护者速读six lines in Chinese (改了什么/为什么改/风险与回滚/先看/两分钟验证/需要你做的). Gates:node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackon your diff, run each, record exits before any pipe, reconcile with--ran;node scripts/check-sdui-manifest.mjsis a gate on every ui card. Changeset in the root.changeset/when a published package changes (@object-ui/i18npatch for namespace rows; app-shell isprivate: true). ⛔ui/.github/**and the rootci.ymlare out of bounds. Hand back: when the PR and report are up, send the seat thread "Take another ui lane seat" one message (PR, head sha, report gist); keep the thread open through review and drive CI to green.Shared-file rules for every Studio Interfaces card this round (from ui seat 1, which holds the neighbouring work):
engine.studio.*rows, the Interfaces familyengine.studio.if.*included, out ofui/packages/app-shell/src/views/metadata-admin/i18n.tsintoui/packages/i18n/src/namespaces/studio/{en,zh}.ts. ⛔ Add or edit no row inmetadata-admin/i18n.ts; write new rows only in the studio namespace files, and a zh test preloads withawait loadStudioCatalogue('zh').ui/packages/app-shell/src/views/studio-design/StudioDesignSurface.tsxis shared with ui seat 1's Studio publish refusals: shown only as a ~4 s toast with engine-internal text; the Pending changes panel keeps no refusal; "Show me" does not focus the offending input and the dashboard/report refusal strip has no "Show me" #26730 (the Interfaces refusal strip ≈L4683 gets anonShow), Studio rails: a search with no matches shows a blank list with no "no matches" message (object rail, permission-set rail) #26733 (props on the fourStudioRailcall sites, Interfaces ≈L5104) and PR fix(metadata-protocol,runtime,app-shell)!: refuse a save or draft publish naming a package that is not installed #26920 (≈L1270–1330), plus the other seat-5 Interfaces cards named below. Keep your hunks inside your declared region; whoever lands second mergesorigin/mainand resolves.Shared files:
ui/packages/components/src/renderers/form/form.tsxis the lane's shared form renderer (≈4.6k lines): keep the change to the field item's label/control arrangement and measure open PRs on it at your first edit (none at 2026-10-28T08:58Z). ui seat 1 holds nothing on form.tsx,TabbedForm.tsxorFormPage.tsx(confirmed by seat 1's thread). PR #27120 (#27052, seat 4) and #26966 touchRecordDetailView.tsx: not yours.Prior landing (read first)
Seat 2 landed the first part as PR #27027 (
Refs #26843, merged 2026-10-28T08:27Z; report 6458730646) and released the remainder at 2026-10-28T08:30Z (comment 6464323152).drawerSide/drawerWidth/buttons.resetwent to the spec card #27153: ⛔ not yours. What stays on this card, and what this order dispatches:layout: 'horizontal'reaches every console form but draws nothing different: the shared renderer reads it only for the action row's alignment.TabbedFormhard-codeslayout: 'vertical'on the form node it builds, so an authoredhorizontalis dropped on a tabbed form view.FormPage(/forms/:name,/f/:slug) ignorestype: 'tabbed'and draws every section stacked.Reading of
horizontal(not a maintainer ruling; disproof welcome)@object-ui/plugin-form's registered input help already says it: "Label placement:verticalputs each label above its field (the default),horizontalbeside it." The spec'slayoutdescribe reads "Field layout direction — 'vertical' (the renderer default) or 'horizontal'. Multi-column is not a layout value: setcolumns". Sohorizontal= label beside its control atsmand up (a fixed label column, control fills the rest), stacked belowsm(phone stays as today). It is not a column count. If you find a published doc or test that readshorizontaldifferently, stop and report it rather than choosing.PM mechanism assumptions (measure; disproof welcome)
On
origin/mainb15b2fcd:<FormItem>→<FormLabel>→ control → description/message; the onlylayout === 'horizontal'read is the action row ≈L4109. The label-beside-control arrangement belongs on that oneFormItempath (a grid atsm:), keyed off the form'slayout. Checkbox/switch-style fields and group-labelled widgets (objectui#3961 IDREF labels) must keep their accessible name: the a11y tests beside form.tsx are the net.TabbedForm.tsx≈L677 setslayout: 'vertical'; the fix passesschema.layout ?? 'vertical'through (andModalForm/ObjectFormalready forwardlayout, per 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).FormPage.tsxbuildssections(≈L800) and draws them stacked (≈L2629).type: 'tabbed'should draw one tab per section with the same tab semantics asTabbedForm(defaultTab/tabPositionfrom the form view, which 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 already reads), reusing the componentsTabsprimitive, ⛔ not a second tab implementation inside console ifplugin-formexports one that fits. Wizard/split/drawer/modal types on FormPage are out of scope: name them as findings if they are also ignored.Scope
File surface:
ui/packages/components/src/renderers/form/form.tsx(field item arrangement),ui/packages/plugin-form/src/TabbedForm.tsx(passthrough),ui/apps/console/src/components/FormPage.tsx(tabbed sections), tests beside each. Tests (acceptance): ahorizontalform draws each label beside its control atsm+ and stacked below, and avertical/ absentlayoutform is unchanged; a tabbed form view withlayout: 'horizontal'keeps it; a FormPage view oftype: 'tabbed'draws one tab per section, opensdefaultTab, and a submit still carries every section's fields (hidden tabs included); atype: 'simple'FormPage is unchanged. Ablations: drop each of the three changes and its test turns red. Update the plugin-formlayoutinput help only if your reading changes it.Tier
No path-derived mandate expected (measure).
TIER_DEFAULT, effortmedium(M).Clause-②: no,Metadata-protocol: semantic(an authored key starts to render). Changesets:@object-ui/componentsand@object-ui/plugin-formpatch,@object-ui/consolepatch. PR line 3 isFixes #26843when all three items land; if one cannot,Refs #26843and say which item stays.objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 8
Session:
session_01JQwxbXGpVJHZTXXerMoY4CAccount:
os-warrenBranch:
claude/project-thread-ekt1xfWorktree:
objectstack-issue-26843Domain:
domain:uiSeat:
domain:ui#5File surface:
ui/packages/components/src/renderers/form/form.tsx(field item label/control arrangement),ui/packages/plugin-form/src/TabbedForm.tsx(layout passthrough),ui/apps/console/src/components/FormPage.tsx(tabbed sections), tests beside each, changesets (stop on breach; explain in the report)Container & model:
M,mode:thread,model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate expected),effort: mediumClause-②: no
Metadata-protocol: semantic
Responsibility:
ui/packages/componentsform renderer,plugin-formand console FormPage produce it (this lane) | an authored form viewlayout: 'horizontal'andtype: 'tabbed'save and publish but draw nothing different | every form view an app authors (ADR-0049 declared-is-effective)Thread-read: 6464831206
Premise: holds (remainder named in seat 2's release 6464323152 after PR #27027; form.tsx reads
horizontalonly for the action row, TabbedForm hard-codes vertical, FormPage draws sections stacked; onorigin/mainb15b2fcd; readings in dispatch order 6464831206)Serial constraints cleared: ui seat 1 holds nothing on form.tsx, TabbedForm.tsx or FormPage.tsx (its thread confirmed); no open PR touches those files; seat 2 released this remainder to
pm:queue.drawerSide/drawerWidth/buttons.resetstay with spec card #27153.Dispatch order: comment 6464831206. Card thread
claude/project-thread-ekt1xfmay start editing.objectstack-fleet commentedon Oct 28, 2026
os-dev-report
{ "issue": 26843, "status": "done", "branch": "claude/project-thread-ekt1xf", "pr": 27222, "head": "763ea858", "session": "session_01JQwxbXNVYZR3yMKLPJJvzL", "premise_still_valid": true, "summary": "All three remainder items land, so the PR says Fixes. (1) form.tsx: a field item under layout horizontal gets HORIZONTAL_FIELD_ITEM_CLASS, a sm-only two-column grid with the label in column one and every other child in column two; vertical and absent layout keep the old code path, DOM byte-identical. (2) TabbedForm declares layout (ObjectFormSchema['layout']) and forwards schema.layout instead of the hard-coded vertical. (3) FormPage draws a type tabbed view with more than one section as one tab per section (TabbedForm's tab key rule, defaultTab, tabPosition, predicate-hidden sections draw no tab, force-mounted panels); a refused submit opens the tab of the field it names. In-place fix under the same defect class and file: FormPage rows honour layout horizontal with the same arrangement. The reading of horizontal in the order held: the plugin-form input help and the 11168 test name both say label placement.", "tests": { "new": "3 files, 21 tests, all pass (form-layout-horizontal-26843 5, tabbedFormLayout-26843 3, FormPage.tabbedLayout-26843 13), each block with a CONTROL", "ablations": "4 of 4 red on pre-merge head 51be1b76 via ablation-replace (form renderer 3 red, TabbedForm 1, FormPage tabs 5, FormPage rows 2); each restored, blob equal to HEAD, git diff HEAD empty; relative-path imports, no dist leg", "wide": "on 763ea858: components form renderer dir + plugin-form + console components dir, Test Files 333 passed, Tests 3538 passed 18 skipped, lock VERDICT command-exit 0", "type_check": "components, plugin-form, console type-check exit 0 on 763ea858 (console after closure build, VERDICT command-exit 0)", "geometry": "Chromium one-off with the classes compiled by the repo's Tailwind 4: at 1024px label beside input (label x 16 w 160, input x 192), description under input; at 375px label above input. Not committed", "eslint": "changed files 0 errors" }, "gates": { "derived": 34, "run": 34, "not_measured": 0, "unrun": 0, "all_exit": 0, "head": "763ea858", "reconciliation": "dispatch-gates --ran: 34 derived famil(ies) accounted for, 34 run, 0 NOT-MEASURED; same derived list before and after merging origin/main", "note": "pre-merge run: pnpm --dir ui exec vitest run scripts/ first exited 1 on two tree-walking tests timing out at 15 s beside a 20-minute locked suite; rerun under the lock exit 0 (175 files), and exit 0 again on 763ea858", "extra_run": [ "pnpm --dir ui run check:new-line-citations exit 0 (0 new)", "node scripts/check-sdui-manifest.mjs exit 0 (derived family)" ], "ci": "check-runs not yet concluded at report time" }, "line_budget": "no ratcheted ledger touched", "files_changed": [ "ui/packages/components/src/renderers/form/form.tsx", "ui/packages/components/src/renderers/form/__tests__/form-layout-horizontal-26843.test.tsx", "ui/packages/plugin-form/src/TabbedForm.tsx", "ui/packages/plugin-form/src/__tests__/tabbedFormLayout-26843.test.tsx", "ui/apps/console/src/components/FormPage.tsx", "ui/apps/console/src/components/FormPage.tabbedLayout-26843.test.tsx", "ui/packages/plugin-form/README.md", "ui/content/docs/components/form/form.mdx", ".changeset/26843-form-layout-horizontal-tabbed-page.md" ], "deviations": [ "Changeset level: the order said patch for all three packages; root AGENTS.md Post-Task Checklist step 3 says a Metadata-protocol kind other than none takes at least minor, and os-dev.md says the standard text wins, so the changeset is minor (as PR 27027's was).", "Scope: FormPage horizontal rows were added under the in-place-fix exemption (same defect class, same file already on the claim surface, mechanical, same gate families). Named in the PR body item 4.", "Docs: plugin-form README layout row and ui form.mdx FormSchema block updated per ui/AGENTS.md commandment 2; both outside the claim's file list.", "PR body folds the repo PR template headings (Why, Human Minimum) into the os-dev order sections." ], "dogfood_battery": "n/a, no ! in the title and no (narrowing) arm", "mcp_calls": "1 (claude-code-remote subscribe_pr_activity for PR 27222; no GitHub MCP tool)", "api_writes": 2, "api_write_list": [ "pr_create 27222 via fleet-write relay (10295 bytes sent = stored, assignee os-warren)", "this report via post-stamped" ], "body_amendments": [], "open_questions": [], "out_of_scope_findings": [ "class: a · reach: an object-form with formType split or wizard and layout horizontal renders vertical (SplitForm and WizardForm build their form node with layout vertical, the shape TabbedForm had before this PR) · evidence: ui/packages/plugin-form/src SplitForm.tsx and WizardForm.tsx each set layout vertical on the built FormSchema · dedupe words: SplitForm WizardForm layout horizontal hard-coded vertical", "class: a · reach: console form page /forms/:name with a form view of type wizard, split, drawer or modal draws stacked sections · evidence: FormPage reads type only for tabbed after this PR · dedupe words: FormPage form view type wizard split ignored", "carrier: none · noted, not filed: an unlabelled unnamed section on a tabbed FormPage names its tab by position number; TabbedForm writes an untranslated English Tab N for the same case" ] }