Skip to content

fix(plugin-form,types): the default form draws a self-describing inline section entry, and ObjectFormSection.fields gains the form view's { field } arm (objectui#11615) - #11622

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11615-simple-inline-section-entry
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/issue-11615-simple-inline-section-entry

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #11615

Clause-②: yes

What this does

The default (simple) object-form now draws a self-describing inline section entry the same way the tabbed, wizard, split, drawer and modal forms already did. ObjectFormSection.fields now declares the form view's { field, … } entry. This carries out triage's first grade as ruled (comment 5980751840, Option 1).

What it accepts now, and the type gain (Clause-② widening)

  • @object-ui/types. ObjectFormSection.fields is (string | SpecFormFieldInput | FormField)[].
    • The new arm is @objectstack/spec's FormFieldInput, by reference. It is the authoring face of FormFieldSchema, the entry that the spec's FormSectionSchema.fields takes beside a bare name.
    • The zod mirror is unchanged: a section's fields entry is still z.any() there.
  • Breaking for TypeScript readers of ObjectFormSection.fields (still minor, as the changeset states).
    • Code that narrowed an entry with typeof entry === 'string' ? entry : entry.name no longer compiles, because the form view's { field } entry has no name. Before the gain, that read returned undefined on such an entry without a type error.
    • The remedy is to name each entry by its own arm through sectionEntryName. Patch round 1 (31a9d9a) publishes it from the @object-ui/plugin-form entry, beside resolveSectionGroupReferences, whose result it reads. That is a further Clause-② widening, named in the changeset.
    • The one in-repo reader, an app-shell test, is fixed that way.
  • @object-ui/plugin-form types. The five layout section configs now take NonNullable of ObjectFormSection['fields'], by reference: FormSectionConfig (tabbed), WizardStepConfig, SplitFormSectionConfig, DrawerFormSectionConfig and ModalFormSectionConfig.
    • This change is forced. ObjectForm passes an authored section's fields to these configs unchanged, so without it the ruled type gain does not compile: tsc reported TS2322 at each of the five places ObjectForm passes them.
    • Each of these layouts already drew the { field } entry through buildSectionFields. The only alternative was a cast, which would hide what these layouts draw.
  • @object-ui/plugin-form runtime, simple form only. Three changes:
    1. Drawing. buildSectionFields with a field pool now draws any entry that isInlineFieldDef accepts, whatever the pool holds, and draws it as written. The branch with no pool gives the same result. isInlineFieldDef accepts an object whose field is not a string and whose name is a string.
    2. Submit. The no-data-source submit exception now uses the shared hasInlineFieldSource. Before, it used hasInlineFields, which is true only for a non-empty customFields.
    3. Opening values. With no data source, a form whose sections are all inline now falls back to an inline-only schema. That form now opens on initialValues / initialData, as the five other layouts do.

What stays refused or warned

  • A bare name and a { field } entry still resolve against the field pool on simple (the objectui#9884 intersection of fields and sections). If the pool lacks the name, the entry is dropped, and a warning is logged once when the object declares the field.
    • The warning now skips inline entries, because those are drawn. Warning about them would report a loss that did not happen.
  • An inline entry with no name is malformed. A form with a field pool still does not draw it.
  • With no submitHandler and no data source, the submit still refuses with DataSource is required for form submission (inline mode not configured) unless every section entry is inline. One name among inline entries still refuses, and this is now pinned on all six layouts.
  • The object-form element gate is unchanged. See Acceptance notes.

Behaviour change for an existing schema. On simple, take an inline entry whose name the object declares but top-level fields leaves out. Before, it was dropped, with the intersection warning. Now it is drawn as its own definition, with no warning, as on the other five layouts. With a data source, its value is written as usual. As on every form, a value for a field the object does not declare is stripped from the write.

Zone 2 item 2: the submit refusal, measured

Measured with a throwaway probe (not committed) and the ablations below. The form is simple with all-inline sections, no customFields, no data source and initialValues: { ref: 'SEED' }:

tree fields drawn ref opens as submit
base 9db9ff3 none (no control) onError with the refusal
drawing change, old submit check (ablation A3) ref — onError with the refusal, after collecting a value
drawing + shared submit check, no inline-only fallback (ablation A4) ref '' onSuccess
this PR ref 'SEED' onSuccess with the collected values
tabbed / drawer on base, the same form (probe) ref 'SEED' onSuccess with the collected values

Decision: simple now counts all-inline sections as an inline field source, using hasInlineFieldSource. The code and the family precedent decide this:

  • The old comment in handleSubmit justified the simple-only exception with one premise: "sections only SELECT pooled fields, so a sections-only form with no adapter resolves zero fields". This card removes that premise. With the drawing change alone, the form draws and collects its field and then refuses, while the five other layouts accept the same form. That would be a second contract that only simple follows.
  • The README section "What a form submits to" already declares that two-limb rule as uniform across all six layouts, simple included.
  • The object-form element gate already uses hasInlineFieldSource for requiresObject, for every form type.
  • The refusal is still loud wherever a form really has nowhere to write. The second limb accepts only sections in which every entry is inline. One name or { field } entry means the form needed metadata it could not get, and it refuses.

The boundary case, before and after. submitTargetRefusal.test.tsx changes as follows:

  • Before: formType simple: BOUNDARY — sections of inline fields are NOT its field source pinned the refusal of a form that drew nothing.
  • After: that row is replaced by a six-layout row, the inline-sections collector opens on initialValues and submits what it holds. simple also joins the six-layout row BOUNDARY — one bare field name among inline ones still refuses. The inline-sections row sections of inline runtime fields — the README's own shape — still work now runs all six layouts.

Zone 2 item 4: "carries its own type" is not the right test for "self-contained"

  • With no pool, a section entry counts as an inline field whenever its field is not a string. That path does not check type.
  • The spec's inline arm requires name and leaves type optional ("the renderer's default input when omitted"). That arm is objectFormRuntimeField in objectstack's component.zod.ts, landed by feat(spec)!: object-form customFields takes a closed runtime form field, and both forms' sections a page-block section shape (#21464, S-forms) objectstack#21742.
  • This package already had a predicate for "self-describing": isInlineFieldDef in submitTarget.ts, which the submit-target rule reads. This PR exports it and reuses it, so one predicate answers both "needs no adapter" and "needs no pool".
  • A { name, label } entry with no type draws the default input on all six layouts. This was measured, and the new pin covers it.

Testing for type would have kept skipping a spec-legal typeless inline entry on simple while the other five layouts draw it. That is the divergence this card closes, and it would also have added a second predicate.

Zone 2 item 3: no maintainer ruling forbids drawing

File surface: what the claim did not list, and why

The claim covers sectionFields.ts, ObjectForm.tsx, submitTargetRefusal.test.tsx and the tests beside it, ObjectFormSection.fields and the changeset. The PR also touches these files:

  • submitTarget.ts: exports isInlineFieldDef (Zone 2 item 4: use the same predicate).
  • TabbedForm.tsx, WizardForm.tsx, SplitForm.tsx, DrawerForm.tsx, ModalForm.tsx: one type line each, needed for the ruled type gain to compile (see above).
  • index.tsx: the object-master-detail-form fields registration description said that any section member fields does not list is dropped. This change makes that false for inline members, so the sentence is corrected in the same PR.
  • packages/types/src/zod/objectql.zod.ts: a comment that names the declared entry type.
  • scripts/check-spec-symbol-derivation.mjs: removes ObjectFormSection from CLAIM_DEBT. Once fields referenced a spec type, check:spec-symbols failed on that stale entry and asks for the removal. --claim-ledger prints the same block.
  • packages/plugin-form/README.md and content/docs/plugins/plugin-form.mdx: a short "What a section's fields entries draw" section, required by AGENTS.md §5 Add automated testing infrastructure and CI/CD workflows #2.
  • Patch round 1 (31a9d9a):
    • packages/app-shell/src/views/metadata-admin/SchemaForm.groupSectionReachability-8725.test.tsx: the one downstream reader the type gain broke. It now names entries with sectionEntryName.
    • index.tsx: publishes sectionEntryName, and corrects the object-form element gate's docblock (comment only).
    • The README and the mdx page: how to read an entry.
  • New tests: packages/plugin-form/src/__tests__/inlineSectionEntry-11615.test.tsx and packages/types/src/__tests__/object-form-section-field-entry-11615.test.ts.

Verification

Patch round 1, on head 31a9d9a:

  • Downstream type-check sweep. turbo run type-check --filter='...@object-ui/types' covers @object-ui/types and its 41 dependents that have a type-check script.
    • It was reproduced on a49b506 first: 76 of 77 tasks passed, and the single error was the app-shell test's TS2339.
    • On 31a9d9a: 77 of 77 tasks passed, exit 0.
  • app-shell: the 25 SchemaForm*.test files: Test Files 25 passed, Tests 301 passed.
  • check:readme-exports, check:doc-snippets and check:doc-examples were measured green this round.

The readings below were taken on head a49b506, the branch merged with origin/main c096f03, and are unchanged by patch round 1. Round 1 re-ran the plugin-form and types suites green on 31a9d9a.

  • plugin-form tests (pnpm exec vitest run packages/plugin-form/): Test Files 164 passed (164), Tests 1906 passed and 1 skipped (1907).
  • types tests (pnpm exec vitest run packages/types/): Test Files 357 passed (357), Tests 9500 passed (9500).
  • Tests for the edited gate script. The 12 tests under scripts/__tests__ that name check-spec-symbol-derivation: Test Files 12 passed (12), Tests 343 passed (343).
  • Type checks: pnpm --filter @object-ui/types run type-check and pnpm --filter @object-ui/plugin-form run type-check both exit 0, after turbo run build --filter=@object-ui/plugin-form^... built the dependencies. --listFilesOnly confirms that both new test files are in their package's test program.
  • Lint: @object-ui/plugin-form and @object-ui/types report 0 errors.
  • Root gates, all exit 0: check-changeset-presence, check-changeset-no-major, check:changeset-claims, check:pending-changeset-literals, check:spec-symbols, check:doc-types, check:doc-fences, check:doc-example-ids, check:new-line-citations (0 new), check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:element-data-source-declaration, check:unreferenced-sources, check:prompt-keys, check:installed-pin-claims and check:skills-paths. check-governed-queue-guard --test over the 19 paths reports NOT GOVERNED.
  • NOT MEASURED, left to CI: check:sdui-registration-pins, which needs a console build. check:doc-snippets, check:doc-examples and check:readme-exports were measured green in patch round 1.

Ablations. Each mutation went through ablation-replace.mjs, and each was restored with the blob equal to HEAD and git diff HEAD empty:

  • A1. ObjectFormSection.fields set back to (string | FormField)[]. The types test program fails with TS2322, only on row 1 of the new types pin (the { field } entry).
  • A2. The pooled branch's inline draw deleted. 7 tests fail, all of them simple rows: the six-layout drawing row, both default-form intersection rows, the three simple rows in submitTargetRefusal, and the unit pin. The other five layouts stay green.
  • A3. The submit check set back to hasInlineFields. 2 tests fail: the simple "still work" row and the collector row. simple BOUNDARY stays green, as expected.
  • A4. The no-data-source fallback set back to hasInlineFields. 1 test fails: the simple collector row, with expected '' to be 'SEEDED'.

Acceptance notes

  • Element gate (not changed here). With an objectName and no data source, the object-form element gate requires a data source unless customFields is non-empty. All six layouts now treat all-inline sections as an inline source. So on the page-block route, such a form gets the gate's notice before it reaches any layout. This errs on the loud side and is the same for every form type. Owner: none. Patch round 1 corrected the gate's docblock so it no longer claims to be in step with the layouts (comment only). Aligning the gate itself stays outside this card.
  • Values written for undeclared fields. With a data source, a value collected by an inline section entry whose name the object does not declare is stripped from the write, on all six layouts. formWritePayload passes the object definition unless customFields is non-empty. This is unchanged and by design ("keys the object does not declare"). Owner: none.

Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL


Generated by Claude Code

claude added 5 commits October 4, 2026 16:58
…ne section entry, and `ObjectFormSection.fields` gains the form view's `{ field }` arm

The pooled branch of `buildSectionFields` dropped every section entry its
parent field pool did not hold, the self-describing inline `FormField` too,
so a section drew that entry on the five pool-less arms and skipped it on
the default (`simple`) one. It now draws an entry `isInlineFieldDef`
accepts as the pool-less branch does; name-only entries keep the
objectui#9884 intersection and its warning, which no longer names a drawn
inline member.

`SimpleObjectForm` reads the shared `hasInlineFieldSource` for its submit
carve-out, and with no adapter treats fully-inline sections as a
members-only field source, so that collector opens on `initialValues` and
its `onSuccess` is the write, as on the five other layouts.

`ObjectFormSection.fields` declares the spec's `FormFieldInput`, by
reference, beside a name and an inline `FormField`.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…ebt ledger

Its `fields` member now references `@objectstack/spec`'s `FormFieldInput`
(objectui#11615), so its "Aligns with @objectstack/spec FormSection" claim
has a compile-time tie and `check:spec-symbols` reports the ledger entry as
stale. Removed as the gate prescribes; `--claim-ledger` prints the same block.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
… entry shapes as `ObjectFormSection.fields`

`ObjectForm` hands an authored section's `fields` to the tabbed, wizard,
split, drawer and modal configs verbatim, and each of those layouts draws a
`{ field, … }` entry through `buildSectionFields`. Their config types
declared `(string | FormField)[]`, so once `ObjectFormSection.fields` names
the form view's entry (objectui#11615) the handover no longer compiled. Each
now reads `NonNullable<ObjectFormSection['fields']>`, by reference.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation package: types plugin tests labels Oct 4, 2026
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3314.3 KB 3330.4 KB
Main entry chunk (gzip) 151.6 KB 350 KB
Entry file index-yh0dSEfP.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 573.73KB 137.60KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.74KB 64.59KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.02KB 45.63KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.36KB 2.51KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

CI red on a49b506, and it is this PR's. The Type Check job (check run 111494860325) fails in packages/app-shell, where pnpm run type-check exits 2 with Property 'name' does not exist on type 'FormFieldInput | FormField'.

The cause is this PR's widening of ObjectFormSection.fields to include the spec's { field } arm. A downstream consumer reads .name off a section entry, and the dev's local type-checks covered only @object-ui/types and @object-ui/plugin-form.

A patch round is underway on the same claim and branch:

  • reproduce with every downstream consumer's type check (the prefix filter over @object-ui/types's dependents);
  • fix each consumer to read the entry by its arm, with no cast and no silent fallback;
  • rerun the full sweep.

The PR stays a draft. The contract review that was judging a49b506 is superseded once the fix lands, and a fresh one runs on the new head.

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL · 2026-10-04T18:03Z.


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a49b506f7750329319322df47b318f0f045b7596
Local-runs: none

① Derived judgments

Read from the net diff against main (c096f03, the merge-base), the card and every comment on it, the PR body and file list, and the check-runs on the head. Each accept-set or public-surface change the diff implies, judged:

  1. @object-ui/types — ObjectFormSection.fields becomes (string | SpecFormFieldInput | FormField)[]. The new arm is the spec's FormFieldInput by reference (@objectstack/spec/ui; present at the pinned 17.6.0, which spec-derived-unions.test.ts already imports). The arm is disjoint from the runtime FormField (the spec type has no name and requires field; the runtime type requires name), and it is the { field } entry buildSectionFields already drew on every arm. Direction RIGHT: a published-type widening the card asked for. But the diff carries only half of it — item 4.
  2. @object-ui/plugin-form types — FormSectionConfig, WizardStepConfig, SplitFormSectionConfig, DrawerFormSectionConfig, ModalFormSectionConfig take NonNullable of ObjectFormSection['fields'], by reference. Forced: ObjectForm hands an authored section's fields to each, so the ruled gain does not compile without it, and each layout already drew the arm. Widening; RIGHT.
  3. isInlineFieldDef exported from submitTarget.ts. Module-level only — packages/plugin-form/src/index.tsx does not re-export it — so no public surface moves. RIGHT.
  4. ⛔ WRONG (incomplete): the type widening's in-repo blast radius was not walked, and the head's Type Check is red for it. resolveSectionGroupReferences (sectionGroups.ts, exported from the @object-ui/plugin-form entry) returns ObjectFormSection[], and packages/app-shell/src/views/metadata-admin/SchemaForm.groupSectionReachability-8725.test.tsx narrows each returned entry with typeof f === 'string' ? f : f.name. On the new union that read no longer type-checks: the check-run annotation reads TS2339: Property 'name' does not exist on type 'FormFieldInput | FormField', from pnpm run type-check in packages/app-shell (tsc -p tsconfig.test.json). packages/app-shell is not in the PR's file list, and the PR body's verification type-checks @object-ui/types and @object-ui/plugin-form only — never the repo-wide pnpm type-check that Type Check runs. On the merge-base c096f03 both Type Check runs are green, so the red is this diff's. The same TS2339 is the whole of the Spec Main Shape Gate red (it compiles objectui against the spec built from objectstack main, then runs the same type-check): one cause, two red checks. The remedy lives in the diff: the consumer narrows by the identity rule the form already spells once (sectionEntryName / isInlineFieldDef), or is re-typed, and the repo-wide type-check is read green before the next push. A published type that gains an arm lands with every in-repo reader of it compiling.
  5. Runtime — the pooled branch of buildSectionFields draws an entry isInlineFieldDef accepts whatever the pool holds, through normalizeSectionField, exactly as the pool-less branch does; a bare name and the { field } entry keep the objectui#9884 pool intersection. Accept-set widening on simple, and on object-master-detail-form's parent half (it renders through SimpleObjectForm). RIGHT per triage option 1 (5980751840). Unchanged edges, checked against main: a shape-3 entry whose name the pool holds was already returned as its own definition (resolveSectionEntry ignored pooled for a non-string field), and a shape-3 entry with no name is still dropped.
  6. The objectui#9884 warning skips inline entries (sectionFieldNames filtered by isInlineFieldDef). Fewer warnings, no accept-set change; RIGHT — a drawn member is not a loss.
  7. SimpleObjectForm's submit carve-out reads hasInlineFieldSource(schema) instead of hasInlineFields. A simple form with all-inline sections, no adapter and no submitHandler now reaches onSuccess where it refused with noSubmitTargetError. Accept-set widening; RIGHT: limb (b) is all-or-nothing (one name-only entry still refuses, now pinned on all six arms), and the README's "What a form submits to" already declared the two-limb rule uniform across all six renderers — simple was the arm that made that sentence false. The old simple BOUNDARY row's premise (zero fields drawn) is removed by item 5, so replacing it is right.
  8. Schema-effect fallback to inlineOnlySchema and record-effect seeding when !dataSource && hasInlineSource. Required for the collector to open on initialValues as the five variants do; RIGHT. Observation, not a defect of this card: fetchObjectSchema's catch still falls back on hasInlineFields alone, so with an adapter that fails getObjectSchema an all-inline-sections simple form becomes an error panel where a customFields form falls back — pre-existing, adapter-present path, untouched here.
  9. scripts/check-spec-symbol-derivation.mjs — ObjectFormSection leaves CLAIM_DEBT. Rule 2's ratchet orders that deletion once the declaration binds a spec type by import; a gate-ordered ledger shrink, not a laundering. RIGHT (Lint is green on the head).
  10. Zod mirror objectql.zod.ts: comment only; the section fields entry stays z.any(), so no drift row moves. RIGHT.
  11. index.tsx registration text: the object-master-detail-form fields description is corrected for inline members; the object-form fields description describes top-level fields and stays true. RIGHT.
  12. Docs (packages/plugin-form/README.md, content/docs/plugins/plugin-form.mdx): the three entry shapes, the simple pool composition (top-level fields, else the object's fields, plus customFields — matches fieldsToShow and mergeCustomFields) and the collector sentence hold against the diff; the README anchor #what-a-form-submits-to resolves. RIGHT.
  13. Tests: the six-arm draw row, both intersection rows, the typeless row, the unit pins and the six-arm collector row pin the behaviour above; the types pin's @ts-expect-error row sits inside tsconfig.test.json's program, so it can fail. RIGHT. Test shards 1 to 8 are green on the head.

② Semver level

  • .changeset/11615-simple-inline-section-entry.md declares @object-ui/plugin-form: minor and @object-ui/types: minor; its Clause-② declaration reads yes (widening), the PR body's reads yes, and claim 5982082822 declared yes. Consistent, and yes takes at least minor — level RIGHT. Both touched published packages are declared; scripts/** and content/docs/** publish nothing; no skip-changeset; no major (fixed group, version policy). Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check and Changeset Claim Re-read are green on the head.
  • No narrowing found: nothing main accepted is refused on the head (items 5 to 7).
  • One body sentence is too wide: "nothing they accepted before is refused now" holds for authored input, not for a TypeScript reader of ObjectFormSection.fields — a consumer that narrowed string | FormField by typeof and read .name no longer compiles (item 4). The level stays minor (objectui's policy spells breaks out in the body rather than bumping major), so the body should name that reader-side break and its remedy in the same re-push that fixes the in-repo consumer.

③ Boundary flags

  • open_questions: none declared.
  • Deviation 1 (file surface beyond the claim — submitTarget.ts, five layout files, index.tsx, objectql.zod.ts, check-spec-symbol-derivation.mjs, README, mdx): each is forced or owed; accepted. The surface is still SHORT of what the type gain requires — packages/app-shell (item 4), and whatever else the repo-wide pnpm type-check names.
  • Deviation 2 (the discriminator is isInlineFieldDef, not "carries its own type"): RIGHT — the spec's FormFieldInput has no name and requires field, the runtime FormField requires name, the pool-less branch never read type, and one predicate keeps "needs no adapter" and "needs no pool" from drifting apart.
  • Deviation 3 (schema fallback and record seeding one step past the submit predicate): RIGHT, item 8.
  • Deviation 4 (commit trailers): commit metadata, outside the diff and the contract; not judged here.
  • Out-of-scope finding 1 (the object-form element gate's requiresDataSource exempts only non-empty customFields): pre-existing, loud, identical on all six arms, untouched by this diff — the acceptance-note disposition stands. For the owning seat: the gate's docblock sentence "exactly what hasInlineFields gates on inside the component, so the two stay in step" is now false for simple as well; a follow-up, not a blocker.
  • Out-of-scope finding 2 (formWritePayload strips a value for a field the object does not declare): by design (sanitizeFormData); accepted.
  • Gates NOT MEASURED locally (check:doc-snippets, check:doc-examples, check:readme-exports, check:sdui-registration-pins): answered on the head by Doc Snippet Type Check, Doc Example Id Check, README Export Check and Build & E2E, all green.
  • "CI in_progress, not awaited": at this review's read every check-run on the head had completed — 38 success, 3 skipped, 2 failure (Type Check, Spec Main Shape Gate), both on the single TS2339 signature of item 4. Not a by-design red: the workflow runs on merge_group, no source declares it red by design, and no PR comment records a gate and a cause. Escalated to the dev: fix the consumer (and amend the changeset sentence), re-push, and a fresh record on the new head.

Implemented-by: claude/issue-11615-simple-inline-section-entry
Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL

VERDICT: FAIL


Generated by Claude Code

… `sectionEntryName`, respell the app-shell reader

`ObjectFormSection.fields` gained the form view's `{ field }` arm
(objectui#11615), so an app-shell test that named every object entry the
section-group resolver returns by `.name` stopped compiling (TS2339; the read
gave `undefined` on a `{ field }` entry all along). The form package already
spells the identity rule once, `sectionEntryName`; it is now published beside
`resolveSectionGroupReferences`, whose result it reads, and the test names
each entry through it. No cast, no second predicate.

Also: the changeset names that reader-side break and its remedy, the README
and docs page say how to read an entry, and the `object-form` element gate's
docblock no longer claims its `customFields`-only exemption is in step with
the components (comment only; the gate is unchanged).

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3314.4 KB 3330.4 KB
Main entry chunk (gzip) 151.6 KB 350 KB
Entry file index-0iBOeBgt.js —
Status PASS —

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 573.73KB 137.60KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.97KB 64.67KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 177.04KB 45.65KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.36KB 2.51KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 31a9d9a784fb8820d595b4efcc8a3a8ad04cf5f0
Local-runs: none

① Derived judgments

Read from the net diff against main (merge-base c096f03), the card and every comment on it, the PR body, file list and every PR comment (the superseded-head record 5982947711 included), and the check-runs on this head. The head also merges clean onto the current origin/main 15f6702 (git merge-tree, a read). Each accept-set or public-surface change the diff implies, judged:

  1. @object-ui/types — ObjectFormSection.fields becomes (string | SpecFormFieldInput | FormField)[]. The new arm is the spec's FormFieldInput by reference from @objectstack/spec/ui, which spec-derived-unions.test.ts already imported on main at the pinned ^17.6.0; that test also pins that FormFieldInput has no name key, so the arm is disjoint from the runtime FormField, and it is the { field } entry buildSectionFields already drew on every arm. The card asked for exactly this arm. RIGHT. The reader-side consequence is item 3.
  2. @object-ui/plugin-form layout types — FormSectionConfig, WizardStepConfig, SplitFormSectionConfig, DrawerFormSectionConfig, ModalFormSectionConfig take NonNullable of ObjectFormSection['fields'], by reference. Forced: ObjectForm hands an authored section's fields to each, and each already drew the { field } entry through the pool-less branch of buildSectionFields. Widening, by reference rather than a cast. RIGHT.
  3. Item 4 of the superseded record (the TS2339 in packages/app-shell) is closed. The one typed in-repo reader of the widened union, SchemaForm.groupSectionReachability-8725.test.tsx, now names each entry with sectionEntryName; its result type is an array of arrays of string | undefined, and every assertion over it still compares to literal string arrays, so no outcome moved. Walked on the head tree myself: no other code narrows an ObjectFormSection['fields'] entry by .name. SchemaForm.tsx and the console FormPage.tsx cast the resolver's result as unknown as app-shell's own FormSectionSpec, so they compile unchanged; the two typeof f === 'string' ? f : f.name maps in AppContent.tsx and RecordFormPage.tsx read an OBJECT definition's fields typed any, not a section's, and are outside this type. The instrument agrees: Type Check is green on this head, where it was red on a49b506 for this one signature. RIGHT.
  4. @object-ui/plugin-form publishes sectionEntryName(fieldDef: unknown): string | undefined from its entry. New public surface: the existing pure function from sectionFields.ts, the one spelling of the identity rule (a string is the name; a string field names the field, the same disambiguator resolveSectionEntry and isInlineFieldDef use for the spec shape; else name; else undefined). Published beside resolveSectionGroupReferences, whose return type is ObjectFormSection[], so a consumer reads what that export hands back without re-spelling the arm. The README public-exports block, the README section and the mdx page name it; README Export Check is green. A Clause-② widening, and the changeset names it. RIGHT.
  5. isInlineFieldDef exported from submitTarget.ts — module-level only. The entry (index.tsx) does not re-export it, so no public surface moves. submitTarget.ts imports nothing, so sectionFields.ts importing it opens no cycle. RIGHT.
  6. Runtime — the pooled branch of buildSectionFields draws any entry isInlineFieldDef accepts, whatever the pool holds, through normalizeSectionField, which is resolveSectionEntry(fieldDef, ctx, undefined) — byte-for-byte the pool-less branch's answer. Accept-set widening on simple and on the parent half of object-master-detail-form (both render through SimpleObjectForm). RIGHT per triage option 1 (5980751840). Edges checked against main: a name and a { field } entry keep the objectui#9884 intersection; a shape-3 entry with no name is still dropped (new pin); a shape-3 entry whose name the pool holds was already its own definition on main (resolveSectionEntry ignores pooled for a non-string field), so its answer is unchanged and the renamed pin says so. The discriminator is isInlineFieldDef, not "carries its own type": the spec's inline arm leaves type optional, the pool-less branch never read it, and a second predicate would have let "needs no adapter" and "needs no pool" drift apart. The deviation from triage's wording is right.
  7. The objectui#9884 warning filters inline entries out of sectionFieldNames. Fewer warnings, no accept-set change; a drawn member is not a loss. RIGHT.
  8. SimpleObjectForm's submit carve-out reads the shared hasInlineFieldSource(schema). A simple form with all-inline sections, no adapter and no submitHandler now reaches onSuccess where it refused with noSubmitTargetError. Accept-set widening; RIGHT: sectionsAreFullyInline is all-or-nothing and needs at least one inline entry, so one name or { field } entry still refuses (pinned on all six); the README's "What a form submits to" already declared the two-limb rule uniform across the six renderers; and the old comment's premise ("sections only SELECT pooled fields") is removed by item 6. hasInlineFields leaves the useCallback dependency list correctly: no non-comment read of it remains inside handleSubmit, and schema is a dependency.
  9. Schema-effect fallback to inlineOnlySchema ({ name, fields: {} }) and record-effect seeding when !dataSource && hasInlineSource. Required for the collector to open on initialValues as the five variants do. hasInlineSource is a boolean in both effects' dependency lists, so nothing rests on a memoised identity. RIGHT. Observation, pre-existing and adapter-present: fetchObjectSchema's catch still falls back on hasInlineFields alone (the dev's second out-of-scope finding, ③).
  10. scripts/check-spec-symbol-derivation.mjs — ObjectFormSection leaves CLAIM_DEBT. Ratchet 3 orders deleting an entry that excuses nothing once the declaration binds a spec type by import; a gate-ordered ledger shrink. Lint is green on the head. RIGHT.
  11. Zod mirror: comment only; a section's fields entry stays z.array(z.any()), so no drift row moves. RIGHT.
  12. index.tsx prose: the object-master-detail-form fields description now distinguishes NAMED members (intersected) from inline ones (drawn) — true to item 6; the object-form fields description describes top-level fields and stays true; the element gate's docblock no longer claims to be in step with the components, and its requiresDataSource expression is unchanged. RIGHT.
  13. Docs (packages/plugin-form/README.md, content/docs/plugins/plugin-form.mdx): the three entry shapes, the simple pool composition (top-level fields when given, else the object's fields, plus customFields — matches fieldsToShow and mergeCustomFields), the collector sentence and the sectionEntryName paragraph hold against the diff; the anchors #what-a-sections-fields-entries-draw and #what-a-form-submits-to resolve to headings on the head. Internal Docs Link Check, Doc Snippet Type Check, Doc Example Id Check and Build Docs are green. RIGHT.
  14. Tests: the six-arm draw row (typed and typeless), both default-arm intersection rows, the unit pins, the six-arm collector row replacing simple's old BOUNDARY, and the types pin whose @ts-expect-error row sits in the tsconfig.test.json program (so Type Check can fail it). RIGHT. All eight Test (shard n/8) runs are green on the head.

② Semver level

  • .changeset/11615-simple-inline-section-entry.md declares @object-ui/plugin-form: minor and @object-ui/types: minor. Level RIGHT: a new entry export and a widened published type are minor; the compile-time break for TypeScript readers of ObjectFormSection.fields is stated in the changeset body with its remedy (sectionEntryName), which is objectui's spelling of a self-inflicted break (the family major follows @objectstack; no major, enforced by Changeset Bump Policy). packages/app-shell is touched under src/ by a test only and publishes nothing, so not naming it is right; scripts/** and content/docs/** publish nothing. No skip-changeset. Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read and Changeset Overwrite Report are green on the head.
  • Clause-②: line. The PR body reads Clause-②: yes at the start of its own line, token first, no arm. The changeset reads Clause-②: yes (widening, with one break for TypeScript readers): the fleet reader takes the value yes and the arm widening (the parenthetical opens with the fixed arm word; the rest is the author's reasoning). The claim (5982082822) declared yes. All three agree, and yes takes at least minor. The direction is right: the authored accept set only widens (items 6 to 9), nothing main accepted is refused on the head, and the one break is reader-side at compile time, named in prose as the superseded record asked.

③ Boundary flags

  • open_questions: none declared in either os-dev-report (5982802265, 5983129758).
  • Round-1 deviation 1 (file surface beyond the claim: submitTarget.ts, the five layout files, index.tsx, objectql.zod.ts, the gate script, README, mdx): each forced or owed; accepted. Patch-round addition (packages/app-shell/...groupSectionReachability-8725.test.tsx): the consumer the type gain broke; accepted (① item 3). The surface is now complete for the type gain: Type Check is green.
  • Round-1 deviation 2 (discriminator is isInlineFieldDef, not "carries its own type"): RIGHT, ① item 6.
  • Round-1 deviation 3 (schema fallback and record seeding one step past the submit predicate): RIGHT, ① item 9.
  • Patch-round deviation 1 (publishing sectionEntryName adds one public export): RIGHT, ① item 4 — the alternative was a hand-written narrowing in a consumer, the second dialect the export exists to prevent.
  • Commit-trailer deviations (both rounds): commit metadata, outside the diff and the contract; not judged here.
  • Out-of-scope finding, element gate (requiresDataSource exempts only non-empty customFields): pre-existing, loud, identical on all six arms, untouched; the docblock now states the mismatch truthfully. Accepted as an acceptance note; aligning the gate is a follow-up for the owning seat, not a blocker.
  • Out-of-scope finding, formWritePayload strips an undeclared key: by design (sanitizeFormData); accepted.
  • Out-of-scope finding, FormPage.tsx and SchemaForm.tsx cast the resolver's result to app-shell's FormSectionSpec (fields?: Array of string | FormFieldSpec, no inline arm): pre-existing; correct for stored form views, whose spec section refuses the inline arm; noted, not a blocker.
  • Out-of-scope finding, fetchObjectSchema's catch falls back on hasInlineFields alone: pre-existing adapter-present path; noted, ① item 9.
  • pr_body_stale (patch-round report): the PR body I read carries the reader-side break and remedy, the sectionEntryName export, the app-shell file, the 77/77 downstream sweep and the gate note — closed.
  • Gates NOT MEASURED locally in the reports (check:sdui-registration-pins): answered on the head by Bundle Analysis (the performance-budget.yml job that runs it), green. check:readme-exports, check:doc-snippets, check:doc-examples: measured green in patch round 1 and green on the head (README Export Check, Doc Snippet Type Check, Doc Example Id Check).
  • Check-runs at this record's read (2026-10-04T18:50Z): 43 on the head, all completed — 40 success, 3 skipped (dependabot, Test (coverage), the coverage shard matrix), 0 failure, 0 in progress. The superseded head's two reds (Type Check, Spec Main Shape Gate) shared one signature, the app-shell TS2339; both are green on this head, so the widened union compiles across the repo and the FormFieldInput import resolves against the spec built from objectstack main as well as the pinned release. "CI in_progress, not awaited" in the patch-round report is answered.

Implemented-by: claude/issue-11615-simple-inline-section-entry
Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

needs:contract-review removed: a conforming PASS is on record for this head.

  • Record: 5983250061 (## Contract review, Served-tier: CONTRACT_REVIEW_TIER, Local-runs: none, VERDICT PASS).
  • It supersedes the FAIL 5982947711 on a49b506. That record's item 4 (the TS2339 in packages/app-shell) and its two owed points (the changeset's reader-side break, and the element-gate docblock) were answered in patch round 1.
  • Judged head: 31a9d9a784fb8820d595b4efcc8a3a8ad04cf5f0, the PR's head at this write.
  • Independence pair: Implemented-by: claude/issue-11615-simple-inline-section-entry, Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL. That is an isolated reviewer at the review tier, adopted by the dispatching seat.
  • Checks on that head: 40 success and 3 expected skips (dependabot, the two coverage jobs). Type Check and Spec Main Shape Gate are green again.
  • Governed: objectui check-governed-queue-guard.mjs --test answers NOT GOVERNED over all 20 paths.

The seat now marks this ready and arms auto-merge through the merge queue.

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL · 2026-10-04T18:51Z.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 4, 2026 18:52
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 4, 2026 18:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 4, 2026
Merged via the queue into main with commit 9dfaca6 Oct 4, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11615-simple-inline-section-entry branch October 4, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants