fix(app-shell): a served form item becomes the create/edit form only when it is the default; no first-named promotion (objectui#11539) - #11549
Merged
objectstack-fleet[bot] merged 4 commits intoOct 3, 2026
Conversation
…default `applyViewItem` no longer makes the first form item to arrive the object's create/edit `.form` when no item carries `isDefault`. `ViewSchema.form` is a container's default form view and `formViews` are additional named form views, so a container with no `form` leaves `.form` unset, and a named form is reached only by its name. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
Served-row fixtures for the three shapes the ruling names: `showcase_inquiry` (named forms only) yields no `.form` and the create/edit layout an object with no form view gets; `crm_lead` and `showcase_contact` keep their default in either arrival order. The create modal renders the object's own fields for a named-only object. Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
…ssue-11539-no-first-form-promotion Claude-Session: https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2 Co-authored-by: Claude <noreply@anthropic.com>
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
objectstack-fleet
Bot
deleted the
claude/issue-11539-no-first-form-promotion
branch
October 3, 2026 14:17
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.
Fixes #11539
Clause-②: no
What changes
applyViewIteminpackages/app-shell/src/providers/MetadataProvider.tsxnow sets an object's create/edit.formonly from a served form item that carriesisDefault. The|| !bucket.formleg, which made the first form item to arrive the.formwhenever no item was default, is gone. The function's docblock states the rule.bucket.primary(the list family) is untouched.Why
@objectstack/specViewSchema:formis a container's "Default form view",formViewsare "Additional named form views". objectstack-ai/objectstack#21535 (on objectstackmainas41b13331) serves exactly that: the container's ownformarrives asOBJECT.formwithisDefault, and a container with noformserves its named forms with noisDefaultkey. With the old merge rule the console still promoted the first named form, soshowcase_inquiry(whose only form is the public, anonymous contact-us form) rendered that form as its create and edit form.The no-default-form path (named and pinned, not changed)
When
.formis unset, the read shared byresolveFormViewLayout,RecordFormPageandScreenView(.form, thenformViews.default) finds nothing. No served row lands underformViews.default: merged keys are canonicalOBJECT.KEYnames. So:AppContent) and action-opened forms (useActionModal):resolveFormViewLayoutreturns{}.ModalFormgets no sections and lays out the object's own fields (itsfieldGroupsfallback, else the flat field list). This is the answer for an object that declares no form view at all, pinned by the existing testfalls back to the full schema when the object declares no form view.RecordFormPage):formDefresolves to{}, so the layout issimplewith no sections.ModalForm.tsxis not touched. Batch sibling objectui#11542 changes how it renders a view's sections, and the new render pin here uses only the no-sections path.Pins
MetadataProvider.merge.test.ts, new describe.form is the served default form only (objectui#11539), with fixtures shaped as served rows (name, object, viewKind, label, config, order, scope, isDefault?) in the order the expansion emits them:showcase_inquiryshape (named forms only): no.form; the named form stays informViewsunder its name; the create/edit layout equals the layout for an object with no form view.crm_lead: the default arrives AFTER the named Web-to-Lead form and still takes.form.showcase_contact: keeps.formin both arrival orders.formbecomes.form, not the named form beside it.createModalHonorsFormView.test.tsx: the realModalForm, fed the layout resolved from a merged named-only object, renders every object field and not the named form's section.Measured (PM hypotheses)
|| !bucket.formleg inapplyViewItemat base6f5719e1c.formViewsentry.RecordFormPage,ScreenViewandresolveFormViewLayout(used byAppContentanduseActionModal) read.form, then the keyformViews.defaultby name. A grep ofpackages/*/srcandapps/*/srcforformViewsreaders and first-entry reads found no other site.bucket.primaryis set only fromisDefault; it has no first-wins leg.Ablation (one-shot, mutations via objectstack
scripts/ablation-replace.mjs, restored to HEAD blob6a7b9077with an emptygit diff HEADeach time)view.isDefault || !bucket.form): 3 failed, 18 passed of 21. The red tests are the twoshowcase_inquirypins and the modal render pin.bucket.form = entryunconditionally): 4 failed of 18 (merge file). The red tests are the twoshowcase_inquirypins,showcase_contactboth orders, and the named-after-default pin.if (!bucket.form)): 5 failed of 18 (merge file). The red tests are the containerformpin, the twoshowcase_inquirypins,crm_leaddefault-after, andshowcase_contact.Local verification at
8828bfapnpm exec turbo run build --filter='@object-ui/app-shell^...' --concurrency=2: 28 of 28 tasks successful, exit 0.pnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0.--listFilesOnlyconfirms that both edited test files are in the test tsconfig.pnpm exec vitest run --maxWorkers=2over 162 files, 1633 tests passed, exit 0. The set is the app-shell and console tests that name the provider module,mergeViewsIntoObjects,resolveFormViewLayout,recordFormNavigationorattachInlineSubforms, plus every app-shell and console test that writes a form-family view (viewKind: 'form'orformViews). The changed line runs only for a form-family view, so a test that supplies none cannot observe it. Blind spot: a test that feeds form views from a fixture file that is not named in its source. No app-shell test imports fromexamples/. A fullpackages/app-shell/run (974 files) was started and stopped unfinished after 18 minutes on the shared box. The full suite is declared to CI'spnpm testshards.pnpm check:control-bytes,check:new-line-citations(0 new),check:changeset-claims,check:pending-changeset-literals,check:spec-symbols,check:esm-specifiers,check:self-import,check:unreferenced-sources,node scripts/check-changeset-presence.mjs,check-changeset-no-major.mjs,check-test-path-roots.mjs,check-vi-mock-override-shape.mjs: all exit 0.eslint .config): 0 errors.MetadataProvider.tsxhas 52 warnings before and after.Acceptance notes
@objectstack/spec17.5.0expandViewContainer. Measured: for a named-only container it still stampsisDefault: trueon the first named form (showcase_inquiry.contact). That path keeps the promotion until objectui's normal spec bump carries fix(spec): a view container'sformis its default form — never collapsed into a named form, and no named form is promoted objectstack#21535, which the card scopes out.list. That is spec-side and outside this ruling. The console's ownbucket.primaryhas no such leg.formViews.defaultread.RecordFormPage,ScreenViewandresolveFormViewLayoutfall back to aformViewskey literally nameddefaulton the object definition. It is a lookup by name, not first-wins, and no served row lands under that key. Observation only; it is not filed.addRecord.formViewhas no reader in the console. The spec'sAddRecordConfigSchema.formViewis "Named form view to use when mode is form or modal", butListViewreads onlyaddRecord.enabledand.position, and callsonAddRecordwith no arguments. A word-grep forformViewoverpackages/*/srcandapps/*/srcfinds only the@object-ui/typesdeclaration.showcase_contactwritesaddRecord: { mode: 'form', formView: 'create' }. This was found by reading the source only, with no runtime reading, and is reported to the seat rather than filed. Named forms otherwise stay reachable by name through the console's/forms/NAMEroute.Session:
https://claude.ai/code/session_01FjqrwXPfSMkSfkKYDSRkN2Generated by Claude Code