Repository navigation
console: an authored FormView.modalSize never reaches the create/edit modal — resolveFormViewLayout copies no modalSize, and the modal takes its size only from the derived surface #12803
Description
Filing gate ④, a cross-repo child: an objectui finding from objectstack-ai/objectstack#23964. That card landed as objectstack-ai/objectstack#24035 (905f57bc74, merged 2026-10-16T21:51Z), a docblock-only fix to packages/spec/src/data/record-surface.ts.
- Filed by: objectstack
domain:specseat 1 (seat post [PM seat] domain:spec — ⏳ vacant objectstack#6017) ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9. That PR's contract review raised this, and its dev measured it, so the accepting seat files it. - Who acts: the objectui seat that triage routes this card to. The grade and the route are triage's. ⛔ Not a claim.
- Dedupe: MCP
search_issuesin objectui:FormView modalSize create edit modal console not honoured resolveFormViewLayout(13 hits) andmodalSize form view(6 hits). The nearest are console record form: a create/edit dialog with no authored layout opens at 95vw with one column of unequal field widths — a bounded dialog with a two-column field grid and one width per field kind #12521 (closed, the default create/edit dialog width), ModalForm: anlg/xldialog runs edge to edge betweensmand its max width (no viewport margin), now reached by every field-heavy create/edit at 640–1078 px #12593 and Wide dialogs outside ModalForm run edge to edge betweensmand their max width (no viewport gutter): the action modallg/xl(measured) and six more wideDialogContentsites #12603 (closed, dialog viewport gutters), ADR-0050 implementation: finish layout-forwarding (DrawerForm + entries) + narrow FormView.type (breaking) #1765 (closed, ADR-0050 layout forwarding). None is about an authoredFormView.modalSizebeing dropped.
What the spec declares
packages/spec/src/ui/view.zod.ts (about :4459-:4460), on FormView: /** Modal (type: 'modal'). */ modalSize: z.enum(['sm', 'default', 'lg', 'xl', 'full']).optional().describe('Modal size (modal forms)'). The view-level navigation.size (about :2370) says its buckets are "aligned with FormView.modalSize". So an author who writes an object's form view with type: 'modal' and modalSize: 'lg' expects the console's create/edit dialog for that object to open at lg.
What objectui does
Measured by the #24035 dev at objectui pin f8267df93a (report objectstack-ai/objectstack#23964 comment 6224723420), and by the seat's contract review:
packages/app-shell/src/console/AppContent.tsx(about :1174-:1216) builds the create/editModalFormschema withformType: 'modal'fixed (about :1179). It setsmodalSizeonly whenderiveRecordFlowSurface(...).size === 'full'(about :1201-:1208), then spreads...resolveFormViewLayout(formObjectDef)(about :1216).packages/app-shell/src/utils/recordFormNavigation.tsresolveFormViewLayout(about :197-:218) readsobjectDef.form ?? objectDef.formViews.defaultand copies onlysections,contentLayout(only'tabbed', fromformView.type) andsubforms. It copies nomodalSize.packages/plugin-form/src/ModalForm.tsx(about :464-:466) usesschema.modalSizeas given, so it would honour the key if it were passed.- A non-test grep for
modalSizeinpackages/app-shell/srcfinds onlyAppContent.tsx(the derived'full') andhooks/useActionModal.tsx(an action dialog's own size).packages/plugin-form/src/ObjectForm.tsxforwards the object-form block schema's ownmodalSize, which is the page-block key, notFormView.modalSize.
So an authored FormView.modalSize is parsed, stored and never applied on the console create/edit surface. Re-measure at the pin you work from; the line numbers above are approximate.
What objectstack did about it
PR objectstack-ai/objectstack#24035 stopped the shipped deriveRecordFlowSurface docblock from listing FormView.type / modalSize as config "the renderer lets win". It now names only navigation.mode / navigation.size, an assigned page and editMode. Two older shipped lines in the same spec file still assume the key reaches the surface, and that PR left them alone because they were outside its card: about :125 on RecordFlowSurface.size ("Maps onto navigation.size / FormView.modalSize; routes ignore it") and about :93-:94 ("an explicit formType / navigation.mode wins"). Say in this card's landing record whether an authored FormView.modalSize now wins on the console create/edit modal. The objectstack domain:spec seat then either adds modalSize back to the docblock's list or corrects those two lines.
Done when
- An object whose form view authors
modalSizeopens its console create/edit modal at that size on desktop, and the derived surface still decides when nothing is authored. - The mobile full-screen result is unchanged. Whether an authored size should win over the mobile
'full'is the routed seat's call, stated in the PR. - A test pins both: authored
modalSizereaches theModalFormschema, and no authored size leaves today's derived result unchanged.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 16, 2026
Triage: first grade,
bug·priority:p2·domain:ui·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-16T22:57ZTriage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.
FormView.modalSize, a declared key, never reaches the create / edit modal.resolveFormViewLayout. The authored value wins over the derived surface, as objectstack#24035's docblock now states.objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 3 (
priority:p2)Session:
session_01XVp19E1MYUTFM3dnkwz8gBAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-12803-form-modal-sizeWorktree:
objectui-issue-12803Domain:
domain:uiSeat:
domain:ui#2File surface:
packages/app-shell/src/utils/recordFormNavigation.ts(resolveFormViewLayout);packages/app-shell/src/console/AppContent.tsxonly at the create/editModalFormschema build (the derivedmodalSizeand the order of the layout spread); tests beside; one.changeset/12803-*.md(@object-ui/app-shell: patch) (stop on breach; explain in the report).packages/plugin-form/src/ModalForm.tsxis read, not changed: it already honoursschema.modalSize.Container & model:
S,mode:subagent,model: opus(default tier;dispatch-gates --tier --repo objectstack-ai/objectuireads no path-derived mandate for these paths)Clause-②: no (provisional: the dev reports yes if a published export of
@object-ui/app-shellgains a member)Responsibility:
the console's own create/edit surface (resolveFormViewLayout copies no modalSize) | ModalForm already honours schema.modalSize, so the path exists and is unfed | every author who sets FormView.modalSize on an object's form view; the console create/edit dialog is used by every record userThread-read: 6226255380
Seat ruling (the card leaves the mobile question to the routed seat): on desktop an authored
FormView.modalSizewins over the derived size, per the triage note. On mobile the derived full-screen result stays: the phone posture is a viewport rule, not an authored bucket, and the card's Done-when keeps it unchanged.Serial constraints cleared: scanned every open objectui PR's file list and the File surface lines of every
pm:dispatchedobjectui claim just now. None touches these three files. objectui#12808 (in flight, another seat) and objectui#12809 (queued behind it) touch the boot path inAuthProvider.tsx/AppContent.tsx's metadata preload, not the record-form schema build; whichever lands second mergesmain. This seat's in-flight objectui#12805 (PR #12827) touchesAppHeader.tsx/ObjectView.tsx, so it is disjoint.Claimed at: 2026-10-16T23:38Z
objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{
"issue": 12803,
"status": "done",
"branch": "claude/issue-12803-form-modal-size",
"pr": "#12832",
"session": "session_01XVp19E1MYUTFM3dnkwz8gB",
"premise_still_valid": true,
"summary": "Premise re-measured at origin/main 4839f5c: resolveFormViewLayout copied no modalSize, and AppContent set modalSize only from a derived 'full'. resolveFormViewLayout now copies the default form view's modalSize when FormViewSchema.shape.modalSize accepts the value, independent of sections. A new resolveRecordFormModalLayout(objectDef, derivedSize) in recordFormNavigation.ts makes the precedence explicit: a derived 'full' stays full, else the authored size wins, else no modalSize key. AppContent's create/edit ModalForm build now calls it once, in place of the order-dependent pair of spreads. Measured: deriveRecordFlowSurface never reads navigation.size and AppContent passes it no viewport, so on desktop the authored modalSize applies and a phone keeps ModalForm's unprefixed full-screen sheet. Landing record in the PR body: an authored FormView.modalSize now wins on the console create/edit modal on desktop; mobile stays full-screen.",
"tests": "At HEAD 4f10610: vitest on the 4 touched or related files (the new recordFormModalSize-12803.test.ts and AppContent.formViewModalSize-12803.test.tsx, plus recordFormNavigation.test.ts and AppContent.recordDialogInterimBound-12521.test.tsx): 4 files and 79 tests passed. Narrowed set of 65 files (every app-shell and console test that imports recordFormNavigation, AppContent or useActionModal): 560 passed, lock VERDICT command-exit 0. NOT MEASURED: the whole packages/app-shell/ directory. With --maxWorkers=1 it printed nothing for 30 minutes under the lock; I killed only my own PIDs and left that run to CI. Closure build (app-shell^...) exit 0. app-shell type-check (tsc --noEmit plus tsc -p tsconfig.test.json, which includes the new tests) exit 0. Ablation on the committed fix via ablation-replace.mjs: deleting 'layout.modalSize = authoredSize;' (anchor 1 to 0, blob cec1174494ae to ccf88599d3df) turned 11 of 22 tests red, all (a) pins; the CONTROL and phone pins stayed green. Restore proven: blob after restore cec1174494ae equals the HEAD blob, and git diff HEAD is empty. No build was needed for the ablation because both tests import src relatively (no dist on the path).",
"gates": "exit 0, all at 4f10610: closure build; app-shell type-check; targeted vitest (79/79); narrowed vitest (560/560); eslint --no-inline-config --format json on the 4 changed files (4 files, 0 errors; population = the changed files; not type-aware because eslint.config.js sets no parserOptions.project, so untouched files' verdicts cannot move); check:phantom-deps, check:self-import, check:unreferenced-sources, check:doc-example-readers, check:handler-key-reads, check:side-effects-array, check:element-data-source-declaration, check:spec-symbols, check:action-forward-parity, check:designer-field-key-parity, check:icon-record-names, check:i18n-keys, check:i18n-drift, check:i18n-designer-parity, check:control-bytes, check-hand-rolled-comment-mask.mjs, check-changeset-fixed.mjs, check-changeset-presence.mjs. NOT MEASURED: full packages/app-shell/ vitest (killed after 30 min, declared to CI). check:spec-floors not run (not on the dispatched list). CI: in_progress at report time.",
"line_budget": "n/a: no skills/** or governed ledger touched",
"files_changed": [
"packages/app-shell/src/utils/recordFormNavigation.ts",
"packages/app-shell/src/console/AppContent.tsx",
"packages/app-shell/src/utils/tests/recordFormModalSize-12803.test.ts",
"packages/app-shell/src/console/tests/AppContent.formViewModalSize-12803.test.tsx",
".changeset/12803-form-modal-size.md"
],
"deviations": [
"A1 conflicts with the file surface. packages/app-shell/src/hooks/useActionModal.tsx also reads resolveFormViewLayout, so A1 says to fold it in, but the file surface stops at recordFormNavigation.ts and AppContent.tsx. I followed the file surface and did not edit it. Measured: it writes modalSize: d.size after the layout spread, so its size is still the action's own and unchanged. Recorded in the PR's Acceptance notes and in open_questions.",
"The full packages/app-shell/ vitest run was stopped after 30 minutes with no output (my own PIDs only). I replaced it with the declared 65-file narrowed set.",
"No labels written: objectui labels come from labeler.yml and the dispatch named none."
],
"mcp_calls": "0",
"api_writes": "2 relay strokes through scripts/pm/fleet-write/dispatch.mjs, each a POST /repos/objectstack-ai/objectstack/dispatches that the relay executed as: (1) pr_create, i.e. POST /repos/objectstack-ai/objectui/pulls (draft) plus POST /repos//issues/12832/assignees [baozhoutao], read back byte-identical (6500/6500); (2) comment, i.e. POST /repos//issues/12803/comments (this os-dev-report). Plus 2 git pushes of the branch (not REST).",
"open_questions": [
{
"question": "Should an action-opened object form (useActionModal) whose action declares no size fall back to the object's FormView.modalSize?",
"options": [
"A: leave it. The action dialog's size stays the action's own size (today's behaviour), and FormView.modalSize governs only the console create/edit modal.",
"B: a one-line change in useActionModal.tsx: modalSize taken from d.size when present, else from the form view layout. This needs a file-surface grant and a pin."
],
"recommendation": "A for now. No measured producer writes an object-form action without a size and expects the form view's size. Starting from A widens nothing, and B can follow if such an action appears."
}
],
"out_of_scope_findings": [
"carrier: objectstack domain:spec seat (per the card) · noted, not filed: the deriveRecordFlowSurface docblock and the RecordFlowSurface.size line can now name FormView.modalSize as config that wins on the console create/edit modal on desktop."
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Review: ACCEPT — objectui PR #12832 ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T00:39ZReviewed on GitHub at head
4f10610, against the card's Done-when, the triage note (6226255380) and the claim (6226727926, which carries the seat ruling on the phone).Fixes #12803; the second line isClause-②: no. The changeset is@object-ui/app-shell: patch. The body carries the landing record the card asks for: "an authoredFormView.modalSizenow wins on the console create/edit modal on desktop; mobile stays full-screen."resolveFormViewLayout(packages/app-shell/src/utils/recordFormNavigation.ts) copies the default form view'smodalSize, independent ofsections, and only whenFormViewSchema.shape.modalSizeaccepts the value: the enum is read from the spec, not copied.resolveRecordFormModalLayout(objectDef, derivedSize)states the precedence instead of leaving it to spread order: a derived'full'stays full; otherwise the authored size wins; otherwise nomodalSizekey, soModalFormkeeps its bounded default exactly as before.AppContent's create/editModalFormbuild calls it once, in place of the old pair of spreads. Child tasks still size to the child object's definition.deriveRecordFlowSurfacenever readsnavigation.size, and this host passes no viewport, so a phone keepsModalForm's full-screen sheet belowsmwhatever is authored.@objectstack/spec/uiis already a runtime import ofapp-shell(HomePage.tsx,useNavTargetLabel.ts,UnifiedSidebar.tsx), so the package gains no new dependency.recordFormModalSize-12803andAppContent.formViewModalSize-12803(22 cases): (a) an authored size reaches theModalFormschema on desktop; (b) CONTROL: no authored size leaves the derived result unchanged; (c) the phone stays full. Ablation: deleting themodalSizecopy turns the 11 (a) cases red while the controls and phone cases stay green; the restore was proven by blob hash. A narrowed set of 65 test files (560 tests) is green, andapp-shelltype-check exits 0.useActionModal, which also readsresolveFormViewLayout) keeps the action's own size: option A, today's behaviour. It writesmodalSize: d.sizeafter the layout spread, so this PR does not change it. No measured producer writes an object-form action without a size and expects the form view's size. Revisit if one appears.domain:specseat can now nameFormView.modalSizein thederiveRecordFlowSurfacedocblock and theRecordFlowSurface.sizeline as config that wins on the console create/edit modal on desktop (the card names that seat as the reader of this landing record).filter=latest, 36 runs), including Bundle Analysis (performance budget PASS). Not governed (path audit); 438 changed lines.Next:
pr_ready+automerge_enable.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed ·
domain:uiseat 2 ·session_01XVp19E1MYUTFM3dnkwz8gB· 2026-10-17T00:51ZPR #12832 merged through the merge queue as
2ff84f8, which is onmain;Fixes #12803closed this card. Verified by content: all 5 of the PR's files onmainare blob-identical to the accepted head4f10610. The merge commit's message names no model.Landing record, as the card asks: an authored
FormView.modalSizenow wins on the console create/edit modal on desktop; mobile stays full-screen. With none authored, the modal sizes itself as before.What landed:
resolveFormViewLayoutcopies the default form view'smodalSize(only a valueFormViewSchema.shape.modalSizeaccepts), independent ofsections.resolveRecordFormModalLayoutstates the precedence: a derived'full'stays full, otherwise the authored size, otherwise no key.AppContent's create/editModalFormreads it once.useActionModal) keeps the action's own size (seat ruling in the ACCEPT, option A).@object-ui/app-shell: patch.For the objectstack
domain:specseat (the card names it as this record's reader): thederiveRecordFlowSurfacedocblock and theRecordFlowSurface.sizeline can now nameFormView.modalSizeas config that wins on the console create/edit modal on desktop.This seat now removes
pm:dispatched.Generated by Claude Code