Skip to content

fix(plugin-form): draw customFields members inside explicit sections on the drawer, modal, tabbed, wizard and split arms - #10451

Merged
os-litant merged 3 commits into
mainfrom
claude/issue-10254-sections-customfields-drawer-modal
Sep 25, 2026
Merged

os-litant merged 3 commits into
mainfrom
claude/issue-10254-sections-customfields-drawer-modal

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #10254
Clause-②: yes — measured on the built declarations at 49dc4dd3c. TabbedFormSchema, WizardFormSchema and SplitFormSchema (re-exported from dist/index.d.ts) each gain an optional customFields?: FormField[] member, the one DrawerFormSchema and ModalFormSchema already declare. Nothing else on the package face moves: findCustomFieldMember and SectionFieldsContext are absent from dist/index.d.ts. The claim line was amended to yes in 5825785976, and the changeset therefore declares minor.

What changes

With explicit sections plus customFields, five of the six object-form arms built every section member from the object schema alone, so the authored definition was dropped: drawer, modal, tabbed, wizard and split. The default arm resolves section names against its merged pool and drew the member. Now all six agree.

  • customFieldsMerge.ts: the override direction's lookup is extracted as findCustomFieldMember(customFields, name), and mergeCustomFields (the default arm's merge) now resolves every name through it. Same precedence, one spelling: first member naming the field wins, an empty customFields supplies nothing. withCustomFieldMembers is retired.
  • sectionFields.ts: SectionFieldsContext gains an optional customFields. In normalizeSectionField, a bare name that a member names is drawn as that member (shape 3). For a spec { field } entry, the member (a copy) is the BASE the entry's overrides are applied to, in place of fromObjectSchema. Over a member base, the widget is re-decided only from the entry's own type.
  • DrawerForm.tsx / ModalForm.tsx: the buildSectionFields callback passes customFields: schema.customFields. Derived (fieldGroups) sections go through the same callback, which retires the withCustomFieldMembers pre-map. Derived output is unchanged, and finding(plugin-form): DrawerForm/ModalForm still REPLACE customFields where ObjectForm now MERGES #10073's FIELD GROUPS rows pin it.
  • TabbedForm.tsx / WizardForm.tsx / SplitForm.tsx (patch round 1): each buildSectionFields callback passes customFields: schema.customFields. That callback is the only route by which each arm builds section fields. The wizard calls it for the rendered step and again in its final-submit required gate (missingRequiredByStep), so both see the member. The mobile auto-stepper hands WizardForm runtime FormField objects already merged, which normalizeSectionField draws verbatim, so nothing is applied twice. The three schema interfaces declare customFields?: FormField[], as the drawer and modal schemas do, because schema.customFields does not compile without it.
  • submitTarget.ts (comment-only): three passages said these three arms neither declare nor render customFields, and gave that as the reason not to widen their interfaces. This change makes both false, so the passages are corrected. No code in the file moves.

Surface

Claim 5825076379, amended by seat comment 5825490852, adds TabbedForm.tsx, WizardForm.tsx and SplitForm.tsx at their buildSectionFields context. Two things beyond that one line per arm, both forced by the wiring:

  • the three optional interface members, since the wiring does not compile without them;
  • the submitTarget.ts comment corrections, since the change falsifies those sentences.

submitTarget.ts (comment-only) joined the claim in 5825785976.

Which semantics this unifies (triage note 3)

Unified: the base definition a section member starts from. A customFields member naming the field supplies the whole definition, in place of the one generated from object metadata. It uses the same lookup and the same precedence as the default arm's merge. A field no member names is generated exactly as before.

Deliberately not unified (each arm keeps what it had):

  • Section-entry override rules. The default arm copies only visibleOn / colSpan / span from a spec entry. The other five apply normalizeSectionField's full set on top of the base.
  • Pool membership. The default arm intersects sections with top-level fields and drops a name nothing declares; the other five do not.
  • Field order inside a section.

⛔ No default-arm behaviour change: ObjectForm.tsx is untouched. The default arm's section order and its dropped section-entry overrides are separate findings, which the seat files at ACCEPT.

Mechanism assumptions, measured

  • Probe on main (0d39dc6e5), card schema: simple INLINE NOTE; drawer, modal, tabbed, wizard and split Note. After this PR all six read INLINE NOTE.

  • A1 (precedence): one lookup, findCustomFieldMember, is shared by mergeCustomFields and normalizeSectionField. No second merge.

  • A2 (member no section lists): the default arm does not draw it under explicit sections, and the other five already matched. Pinned as UNNAMED MEMBER on all six arms.

  • A3 (override rules): kept. A scratch probe through the real ObjectForm, re-run on all six arms at 2c6e298ef (not a pin), reads:

    • Member case. The entry { field: 'note', label: 'SECTION LABEL' } does not restate required, and the member (labelled INLINE NOTE) declares required: true. The default arm renders INLINE NOTE*; the other five render SECTION LABEL*, whose marker comes from the member.
    • No-member case. The entry { field: 'note', label: 'SECTION LABEL', required: true } does restate required. The default arm renders Note with no required marker; the other five render SECTION LABEL*.

    The label split, and the no-member case's dropped required, are the pre-existing override split. No pin authors an override the two rule sets answer differently.

  • Round-1 assumption, falsified: "each fix is one line". The three schema interfaces did not declare customFields, so schema.customFields did not type-check. See Surface.

  • A4 (pins): __tests__/sectionsCustomFields-10254.test.tsx, renamed from drawerModalSectionsCustomFields-10254.test.tsx now that it covers every arm. It mounts the real ObjectForm per formType, with five rows on each of the six arms: simple is the default-arm control, then drawer, modal, tabbed, wizard and split. The rows are:

    • LABEL;
    • REQUIRED: aria-required, then an empty submit refused with INLINE NOTE is required and no create, then a filled submit reaching create;
    • SPEC ENTRY: a { field, colSpan } entry keeps the member's label, required marker and textarea type;
    • UNDECLARED NAME;
    • UNNAMED MEMBER.

    Every schema has ONE section, so the wizard's rendered step is the section under test, and the drawnFields() equalities pin that only that step's fields are drawn.

Changeset

.changeset/10254-sections-customfields-drawer-modal.md declares '@object-ui/plugin-form': minor: a public-surface widening (Clause-②: yes) takes at least minor, and ⛔ never major.

Patch round 2 (2c6e298ef): no source change. The seat's contract review at 49dc4dd3c passed every code item, and this round moves only the changeset level from patch to minor. That is frontmatter line 2 only: lines 1 and 3 and the body bytes are unchanged (SHA-256 compared before and after), and the body names no level. Every test below ran at 49dc4dd3c, which differs from 2c6e298ef only in that one line. The gates were re-run at 2c6e298ef.

Tests (at 49dc4dd3c)

Every run went through os-verify-lock.sh. Verdict lines are quoted.

  • Pins: Tests 30 passed (30).
  • Reverse proof, wizard leg (round 1): ablation-replace.mjs replaced the wizard's wiring line with a marker. On disk the wiring count went 1 → 0 and the marker count to 1; the blob went b9dfeef7dab8 → 6588dafdd06f. Result: Tests 4 failed | 26 passed (30). The four are exactly the wizard's LABEL, REQUIRED, SPEC ENTRY and UNDECLARED NAME rows. Restore: blob equals HEAD (b9dfeef7dab8) and git diff HEAD is empty.
  • Reverse proof, three-arm leg (round 1): TabbedForm.tsx, WizardForm.tsx and SplitForm.tsx were checked out at bbe2b1e4b (wiring count 0 in each), trap-restored. Result: Tests 12 failed | 18 passed (30). The twelve are tabbed, wizard and split × LABEL, REQUIRED, SPEC ENTRY and UNDECLARED NAME. Restore: blobs equal HEAD and git diff HEAD is 0 bytes.
  • Round-0 legs, run at bbe2b1e4b under the file's old name:
  • pnpm exec vitest run --maxWorkers=2 packages/plugin-form/: Test Files 117 passed (117), Tests 1196 passed | 1 skipped (1197), VERDICT command-exit 0.
  • The 38 suites outside plugin-form that name DrawerForm / ModalForm / TabbedForm / WizardForm / SplitForm / buildSectionFields / normalizeSectionField / customFields / sectionFields / submitTarget / hasInlineFieldSource: Test Files 38 passed (38), Tests 1733 passed (1733), VERDICT command-exit 0.
  • Closure build pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-form^...' build, then pnpm --filter @object-ui/plugin-form type-check (tsc --noEmit plus tsconfig.test.json): both VERDICT command-exit 0. --listFiles counts the renamed test once.

Gates (at 2c6e298ef, exit codes recorded)

Acceptance notes

  • Default-arm section order and dropped section-entry overrides: these are separate defects on the default arm, left unchanged here. The seat files them at ACCEPT.
  • Two registration descriptions in index.tsx quote ObjectForm.tsx:361 and ModalForm.tsx:638. At base 0d39dc6e5, neither line holds what its description says. There, the quoted ObjectForm.tsx line is a comment, and modalCloseButton is read further down the file. The quoted ModalForm.tsx line is not the contentLayout check. This PR does not change ObjectForm.tsx or index.tsx; the quotes are left as index.tsx carries them.

Generated by Claude Code

…on drawer and modal

With explicit `sections`, DrawerForm and ModalForm built every section
member from the object schema alone, so a `customFields` member naming
that field was dropped; the default ObjectForm arm drew the member.

The section normalizer now takes the member naming a field as the base
definition, through `findCustomFieldMember`, the lookup the default arm's
`mergeCustomFields` now resolves every name through. Each arm's
section-entry override rules are unchanged. Derived (fieldGroups)
sections reach the member by the same route, which retires
`withCustomFieldMembers`.

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

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 address(es) in this pull request's own prose name a tree it replaced

Each was read from a tree this change itself moves, so a reader who follows it lands somewhere else. ⛔ Nothing here blocks and nothing here says the sentence is false — the question asked is arithmetic: does this diff move the line that number points at?

  • in this body, ModalForm.tsx:638 — this change moves packages/plugin-form/src/ModalForm.tsx:638 to :640

    • Two registration descriptions in index.tsx cite line addresses in other files (ObjectForm.tsx:361, ModalForm.tsx:638).

⛔ The repair is not to correct the number. Changing :246 to :274 is true today and born false again on the next insertion — objectui#9509 states that before anything else. Bind the number to the tree it was read from (`:246` at `b8a006883d`, `:274` at this head), which cannot re-stale because each number names its own tree; or state a rule instead of a coordinate, the way objectui#9495 replaced a file count with "every file in git diff --name-only against the merge base".

⚠️ 5 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/6237-wizard-step-config-split.md

  • names WizardForm.tsx → packages/plugin-form/src/WizardForm.tsx — edited by this change

    WizardStepConfig is now declared independently in WizardForm.tsx, which is simply what SplitFormSectionConfig, ModalFormSectionConfig and DrawerFormSectionConfig already do: each layout owns its group shape, documents className / gridClassName in its own terms, and declares visibleWhen only where its renderer honours it. The derivation flips from subtractive to additive — a key is authorable on a wizard step only if someone writes it there.

.changeset/8738-fields-warn-route1.md

  • names sectionFields.ts → packages/plugin-form/src/sectionFields.ts — edited by this change

    Top-level fields reads only bare field-name strings ({ name } tolerated) — a different vocabulary from sections[].fields, which also accepts the spec FormFieldSchema object (identity key field, e.g. { field: 'note', colSpan: 2 }). Moving one of those objects into a top-level fields array resolves to no name and used to vanish without a word; it is now reported once per distinct offender via console.warn, naming the skipped shape and the vocabulary difference, modelled on sectionFields.ts's existing warnOnMixedVocabulary.

.changeset/9884-master-detail-fields-intersection.md

  • names sectionFields.ts → packages/plugin-form/src/sectionFields.ts — edited by this change

    So the rendered outcome is unchanged and the intersection stands: the parent field pool is built from fields first, and each section resolves its members against that pool. What changed is that the loss is audible. warnSectionMemberExcludedByFields (sectionFields.ts, beside the two warnings objectui#8738 and objectui#3090 added) names the section, the member and the two keys that collided, once per distinct pair, whenever a member the object really declares is dropped for the sole reason that fields omits it — including the expensive case where it was the section's last surviving member and the section disappears with its heading. A member the object never declares at all is deliberately NOT recruited into this warning: it resolves to nothing whether or not fields is authored, which is a different silence with a different remedy.

.changeset/console-formpage-visible-predicates-5594.md

.changeset/simple-form-consults-declared-submit-handler.md

  • names submitTarget.ts → packages/plugin-form/src/submitTarget.ts — edited by this change

    The carve-out now fires only when no submitHandler is declared, and the "no submit target" refusal moved into the persistence chain after the seam — the shape the five variant renderers already use, reusing their shared refusal from submitTarget.ts rather than a private copy. A form with inline fields and no seam is unchanged: its onSuccess is still the write.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 274e14af4 (merge-base with origin/main): 9 file(s) changed outside .changeset/, read against 1398 pending declaration(s) that publish a body (1976 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3045.3 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-DES7Qlab.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) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 543.20KB 129.69KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 261.04KB 67.97KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.35KB 37.50KB
plugin-gantt (index.js) 168.47KB 41.53KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.21KB
plugin-map (index.js) 22.05KB 7.14KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 87.90KB 21.97KB
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.66KB 3.50KB
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) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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

…on tabbed, wizard and split

TabbedForm, WizardForm and SplitForm built every section member from the
object schema alone, so a `customFields` member naming that field was
dropped, as the drawer and modal did before the previous commit.

Each arm now passes `customFields` into its `buildSectionFields` context,
the one route it builds section fields by (the wizard's final-submit
required gate reads the same callback). Their schema interfaces declare
the key, as DrawerFormSchema and ModalFormSchema do, and the
`submitTarget.ts` comments that said these three neither declare nor
render it are corrected. The pins cover all six arms.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3045.2 KB 3104.5 KB
Main entry chunk (gzip) 148.0 KB 350 KB
Entry file index-BnLLVvyl.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) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 543.20KB 129.69KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.84KB 65.07KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.45KB 35.33KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 261.04KB 67.97KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.50KB 37.51KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.21KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.90KB 21.97KB
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.66KB 3.50KB
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) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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

Since the previous commit, TabbedFormSchema, WizardFormSchema and
SplitFormSchema declare `customFields`, which widens the package's public
type surface. A widening takes a minor level, so the changeset's level moves
from patch to minor. The body is unchanged.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3044.7 KB 3104.5 KB
Main entry chunk (gzip) 147.8 KB 350 KB
Entry file index-DwRUYD4_.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) 16.68KB 6.20KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
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.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.52KB 3.45KB
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.13KB 7.95KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 544.08KB 129.99KB
core (index.js) 9.22KB 3.71KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 223.86KB 62.25KB
fields (index.js) 256.90KB 65.08KB
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.40KB 12.91KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
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.01KB 3.93KB
plugin-calendar (index.js) 51.22KB 14.56KB
plugin-charts (index.js) 74.76KB 20.82KB
plugin-chatbot (index.js) 198.36KB 47.20KB
plugin-dashboard (index.js) 133.59KB 35.39KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 231.66KB 61.23KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.50KB 37.51KB
plugin-gantt (index.js) 168.25KB 41.46KB
plugin-grid (index.js) 215.43KB 58.90KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.37KB 28.24KB
plugin-map (index.js) 21.83KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.64KB 8.94KB
plugin-tree (index.js) 10.52KB 3.69KB
plugin-view (index.js) 87.90KB 21.97KB
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.66KB 3.50KB
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) 114.58KB 37.60KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.03KB 1.86KB
react (schema-input.js) 4.25KB 2.04KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
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) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 17.15KB 6.32KB
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: 49dc4dd3c242c978239868d321b2df9125a7f22d

Rendered by an isolated review subagent spawned by the domain:ui#4 seat; its served tier was checked against its transcript stamps (94 of 94 model stamps at the review tier). Adopted by this seat.

① Derived judgments

  • Inputs. Head 49dc4dd3c242c978239868d321b2df9125a7f22d resolved from the PR API (matches the expected value) and fetched as pull/10451/head; its merge-base with origin/main is 0d39dc6e5d2c8e1ac3516a4a65ec81ab52e210ea, the dev's BASE. Two commits (bbe2b1e4b, 49dc4dd3c), 10 files, +334 / −63. The whole diff was read; nothing was re-run locally. Card body, triage 5817773218, claim 5825076379, reports 5825475081 / 5825763039, amendments 5825490852 / 5825785976 and proposed-body.md were read first.

  • (a) Public surface / acceptance set (Clause-②). The package face is exports["."] → dist/index.d.ts, built from src/index.tsx, whose blob is byte-identical at base and head (059806a71). git diff of the exported types, member by member:

    • TabbedFormSchema.customFields?: FormField[] — new optional member on a type re-exported by src/index.tsx. CORRECT: TabbedForm now reads schema.customFields in its buildSectionFields context, so declared = read.
    • WizardFormSchema.customFields?: FormField[] — same. CORRECT (read at the rendered step and inside missingRequiredByStep).
    • SplitFormSchema.customFields?: FormField[] — same. CORRECT.
    • Nothing else on the face moves. findCustomFieldMember (new module export of customFieldsMerge.ts) and SectionFieldsContext.customFields (new optional member) are module-internal: src/index.tsx has no export * and imports nothing from customFieldsMerge, sectionFields or flatFields (the only other reader of SectionFieldsContext, through FlatFieldsContext); git grep at head confirms, which agrees with the dev's dist/index.d.ts reading. withCustomFieldMembers is deleted from customFieldsMerge.ts but was never on the face, so no narrowing. FormSectionConfig, WizardStepConfig, DrawerFormSchema and ModalFormSchema are unchanged. package.json (exports, main, module, types, files) is not in the diff. The three interfaces are standalone export interface declarations with no extends, so the member could not have been inherited; the widening is exactly those three lines and the dev's "does not compile without it" is right.
    • Verdict on the widening: Clause-② is correctly yes. An object literal carrying customFields now type-checks against the three schema types where it was refused before, which is a public-surface widening, and it is the honest one because the renderers read the key. The registered object-form input at src/index.tsx ({ name: 'customFields', type: 'array', description: 'Field definitions merged over the set generated from object metadata. With inline definitions and no data source, this becomes the only field source.' }) is now TRUE on all six formTypes in the direction sections have, override in place; the append direction does not exist under sections on any arm, the default included (A2, pinned), so the description is uniformly true instead of true on one arm and false on five.
  • (b) One merge (A1). findCustomFieldMember is the single precedence lookup. Its only callers at head: mergeCustomFields in customFieldsMerge.ts (the default arm's merge; const member = findCustomFieldMember(members, name) replaces the byte-equivalent inline find) and normalizeSectionField in sectionFields.ts (shape 1 and shape 2). No second merge exists. withCustomFieldMembers has zero occurrences anywhere at head (git grep), so the two former callers in DrawerForm.tsx / ModalForm.tsx and their imports are gone with it. Derived (fieldGroups) output stays byte-identical on drawer and modal by construction: deriveFieldGroupSections emits bare NAMES; the old pre-map resolved a name to the member object with the same find on name and handed the object to normalizeSectionField, which drew it as shape (3); the new path resolves the same name through findCustomFieldMember inside normalizeSectionField and drops into the same shape (3), attachVisibility(member, member.visibleOn), returning the same object (or the same spread copy when visibleOn is set). finding(plugin-form): DrawerForm/ModalForm still REPLACE customFields where ObjectForm now MERGES #10073's FIELD GROUPS rows (drawerModalCustomFieldsMerge-10073.test.tsx, describe.each over simple / drawer / modal) pin it, and the dev's round-0 leg (drawer FIELD GROUPS going red when the drawer wiring is removed) is exactly what the derived path depending on that wiring predicts.

  • (c) Member base in normalizeSectionField.

    • Bare name: if (member) return normalizeSectionField(member, ctx); the member carries no string field, so it lands in shape (3) and is drawn as the member. Correct.
    • Spec { field } entry: const base = (member ? { ...member } : fromObjectSchema(fieldName, ctx)), a shallow copy. Every later write is a top-level assignment on base (base.fields = fd.fields included, an assignment not a push), and attachVisibility returns a spread copy when a predicate is present, else the object itself. The author's member is never written to. Downstream, applyFieldPermissions (fieldWriteGate.ts) also spreads ({ ...f, readOnly: true, disabled: true }), and Tabbed / Wizard / Split only read the built array. No mutation, verified. (Shape 3 returns the author's object by reference, the pre-existing behaviour for inline runtime entries and for the old derived pre-map; unchanged.)
    • const rawType = fd.type ?? (member ? undefined : ctx.objectSchema?.fields?.[fieldName]?.type): over a member base only the entry's own type re-decides the widget id. Correct. Precision note: the entry's separate widget key still passes through to base.widget as it always did; the changeset's "widget changes only when the entry restates type" is about the type-derived widget id, this codebase's vocabulary.
    • Fields with NO member: findCustomFieldMember returns undefined (also for an omitted or empty customFields), so shape (1) falls to fromObjectSchema + attachVisibility as before, shape (2) takes the object-schema base with the pre-existing rawType fallback, and shape (3) is untouched. No section-entry override rule moved on any of the five arms (A3 holds).
  • (d) Wizard. WizardForm.tsx has one buildSectionFields callback (context now carries customFields: schema.customFields, dependency added). It is called from currentSectionFields (the rendered step) and from missingRequiredByStep, which iterates buildSectionFields(section) over every step and reads field.required, so the final-submit gate sees the member. No other field-building route exists in the file (no fromObjectSchema / normalizeSectionField / buildFlatFields reference). The mobile auto-stepper in the unchanged ObjectForm.tsx: wantsStepper requires !schema.formType && !hasSections; its synthetic sections carry fields: chunk, slices of fieldsWithMobile, which are runtime FormField objects already produced by mergeCustomFields; in normalizeSectionField they have no string field, so they take shape (3) verbatim and findCustomFieldMember is never consulted for them. Nothing is applied twice. Same single-route reading for TabbedForm (tabGroups → buildSectionFields(section)) and SplitForm (pane loop → buildSectionFields(section)).

  • (e) Default arm. packages/plugin-form/src/ObjectForm.tsx blob 703e05327 at both base and head: byte-identical. The pin's ARMS tuple begins with simple and every row runs on it through describe.each(ARMS), so the default-arm control is present, five rows.

  • (f) Pins. packages/plugin-form/src/__tests__/sectionsCustomFields-10254.test.tsx (a git diff -M rename from drawerModalSectionsCustomFields-10254.test.tsx between the two commits; "added" relative to base). describe.each over simple, drawer, modal, tabbed, wizard, split × 5 it rows (LABEL, REQUIRED, SPEC ENTRY, UNDECLARED NAME, UNNAMED MEMBER) = 30, each mounting the real ObjectForm with the arm's formType (plus open: true off the default arm). Every schema uses MAIN_SECTION, one section; drawnFields() equalities appear in LABEL, UNDECLARED NAME and UNNAMED MEMBER. The reported proofs are the dev's counts (not re-run here), and the case list makes each count the only one possible: wizard wiring removed → 4 red (that arm's LABEL / REQUIRED / SPEC ENTRY / UNDECLARED NAME; UNNAMED MEMBER is green either way by design, as the file header states); three arms reverted → 3 × 4 = 12 red; round-0 drawer wiring → 5 red = the drawer's 4 member rows + finding(plugin-form): DrawerForm/ModalForm still REPLACE customFields where ObjectForm now MERGES #10073's drawer FIELD GROUPS row, over 30 = 15 (this file, then 3 arms) + 15 (the 10073 file, 3 arms × 5 rows); type guard → 2 red = drawer + modal SPEC ENTRY out of 15 (only those two arms were wired at bbe2b1e4b, and simple never enters normalizeSectionField); base sources → 8 red = drawer + modal × 4, 7 green = 5 simple + 2 UNNAMED MEMBER. These pins are real and can fail.

  • (g) submitTarget.ts. Three hunks, every changed line inside a JSDoc block; the comment-stripped source hashes identical at base and head, so hasInlineFieldSource, sectionsAreFullyInline and the InlineFieldSource shape are byte-identical in logic. The three corrected passages are TRUE at head: (1) a sectioned form with no customFields at all expresses inline fields through its sections; (2) the three arms receive customFields through ObjectForm's {...schema} spread (verified at the three mounts in ObjectForm.tsx), did not declare or render it before finding(plugin-form): with explicit sections plus customFields, the drawer and modal arms ignore the authored members (the default arm honours them) #10254, and now declare and render it; (3) the object parameter reasoning and "they declare it since objectui#10254" hold. No stale sentence saying the three arms lack customFields remains in packages/plugin-form/src, the package README or content/docs/plugins/plugin-form.mdx (grep at head).

  • (h) Changeset. .changeset/10254-sections-customfields-drawer-modal.md: frontmatter '@object-ui/plugin-form': patch, SHA-256 of the three frontmatter lines identical at bbe2b1e4b and head. No line-address citation. Every sentence checked against head is TRUE: the registered-description quote matches src/index.tsx; the five arms did build section members from the object schema alone at base (their buildSectionFields contexts carried no customFields); the default arm resolves sections against its merged pool (sourceFields.filter over formFields built by mergeCustomFields); the "same lookup" is findCustomFieldMember; bare name and spec entry both covered; the type sentence is true as the type-derived widget id (see (c)); "a plain text input labelled with the field name" is exactly fromObjectSchema's { name, label: name, type: 'input' } fallback; the A2 sentence is pinned on all six arms; the three interfaces now declare customFields as the drawer and modal ones did; the override-rule sentence is true. Level: NOT right any more, see ②. Flagged pending changesets re-read at head and still true: The tabbed modal arm (ModalForm contentLayout: 'tabbed') cannot carry a section visibleWhen at all — FormFieldTab declares no predicate slot #6237 (6237-wizard-step-config-split.md: WizardStepConfig is untouched by this diff, which adds to WizardFormSchema only; the *When type assertion stands), finding(plugin-form): object-form carries TWO surfaces spelled fields with different member vocabularies — the spec FormFieldSchema object is silently dropped by the top-level one #8738 (both files: warnOnMixedVocabulary present, buildFlatFields untouched), finding(plugin-form): object-master-detail-form.fields 声明「Ignored when sections is given」,而运行时把两者取交集 —— 落在 fields 之外的 section 成员静默消失,是最后一个时整段一起消失 #9884 (warnSectionMemberExcludedByFields present in sectionFields.ts), Console FormPage (the standalone /forms and internal FormView renderer) never evaluates visibleWhen/visibleOn — objectui#2212 was fixed in the OTHER form renderer #5594 (console-formpage-visible-predicates-5594.md: sectionFields.ts still resolves fd.visibleWhen ?? fd.visibleOn canonical-first on the unchanged return line), and simple-form-consults-declared-submit-handler.md (the shared refusal in submitTarget.ts is unchanged code). Of the pending changesets that mention customFields (finding(plugin-form): DrawerForm/ModalForm still REPLACE customFields where ObjectForm now MERGES #10073, objectui#8068 的过渡背书:58 个 array/object 类 input 仍无 per-block 成员 pin(逐条转 pin,删豁免) #8071 slice 11, object-form:空的 initialData 会静默丢弃写满的 initialValues(整对象 ||,不是逐成员合并) #9760, finding(plugin-form): object-form.customFields 注册描述说 merge,渲染器 replace —— 逐成员合并的代码存在但不可达 #9778, loud-lamps-shake, retire-spec-bridge-6366, tidy-forms-refuse-target-less-submit), none says tabbed / wizard / split do not take it; tidy-forms-refuse-target-less-submit.md's "a non-empty customFields, or sections whose fields are all inline" is still true.

  • (i) Proposed body (proposed-body.md). Factual sentences hold against head EXCEPT the following, which must be edited before the seat writes the body:

    1. "The claim line reads no; the seat is asked to amend it on the card." — STALE: amendment 2 (5825785976) already set the claim line to yes. Rewrite as: amended to yes in 5825785976.
    2. "The seat is asked to add submitTarget.ts (comment-only) to the claim." — STALE for the same reason: 5825785976 added it. Rewrite as: added in 5825785976.
    3. A3 paragraph, "with no member, it is Note against SECTION LABEL*": the required marker on the other five appears only when the ENTRY restates required: true (the round-0 finding's entry), but the entry the paragraph names is { field: 'note', label: 'SECTION LABEL' } without it. State that the no-member entry carries required: true, or drop the *.
    4. Acceptance note on the two index.tsx citations: "The ModalForm one was already false at BASE" is TRUE (base line 638 is the if (loading) region; the contentLayout check is at 727 at base and 729 at head, the drift the changeset-claim bot reported). But the ObjectForm.tsx:361 one is ALSO false at base and head (line 361 is a comment line; modalCloseButton is read at line 528, and the file is unchanged), which the note's wording implies is not so. Say both are false at base.
      The line-address mention itself is FINE AS WRITTEN and needs no sha binding: it quotes the literal strings index.tsx carries, and that file is byte-identical at base and head (blob 059806a71), so the quote cannot drift with this PR; the bot's :638 → :640 reading is about ModalForm.tsx's line positions and changes nothing about what index.tsx says. Keep both strings in backticks as quotes; the new-citation gate reads the diff, not the body.
      Verified true elsewhere: the Clause-② paragraph (three types re-exported, the two internals absent from the face, see (a)); "five of the six arms"; all five What changes bullets; the Surface paragraph (claim + amendment 1 named correctly); the unify / not-unify lists (the default arm copies only visibleOn / colSpan / span, intersects with fields, keeps pool order, all read in ObjectForm.tsx); ObjectForm.tsx untouched; the A1 / A2 / A4 paragraphs; the rename and the one-section design; "9 source file(s)" (nine files under packages/plugin-form/src, the test included, which is how the presence gate counts); the frontmatter-identical sentence; the quoted gate and test lines are the dev's readings, consistent with the case list and with the head's check-runs. No model identifier anywhere in the body; the footer carries only the session URL.

② Semver level

minor, not patch. The changeset frontmatter must become '@object-ui/plugin-form': minor. Never major.

objectui AGENTS.md, 「版本号策略(version alignment)」: 「minor/patch 独立演进——objectstack 没动时不必跟发;objectui 自己的改动照常用 changeset 推进(从当前 major 起步 …)。」 and 「推论:changeset 里不要声明 major —— fixed 组任一 major 都会把全组推上去、脱离 objectstack 的节奏 … objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)」. The rule fixes only the top: major is never declared and breaking changes fold into minor; below that, minor / patch advance 「照常」, by ordinary semver. Ordinary semver puts an additive public-API change at minor, and that is what this diff is on top of its fix: three exported interfaces accept a member they refused before. The seat's own Clause-② ruling (5825785976: "a public-surface widening, so the answer is yes") names the same fact, and the charter Clause-② belongs to (objectstack AGENTS.md, Post-Task Checklist item 3) makes the link explicit: 「The declaration is Clause-②: yes|no … yes takes at least minor」. The contract-review charter's item ② is 「semver 定级与 changeset 声明一致」, and here the two disagree: the PR declares yes and the changeset declares patch.

Precedent agrees. Among merged objectui PRs declaring Clause-②: yes in the last 300 closed (#10426, #10421, #10424, #10400, #10395, #10389), every widening declared minor; the one patch (#10388) was a narrowing, a new refusal, not a widening. At round 0 patch was right (drawer and modal already declared customFields; no exported type moved; the claim read no), and the behaviour half alone, five arms honouring an already-registered promise, would still be patch as #10073 was. Round 1's widening is exactly what should have moved the level, and the frontmatter being byte-identical since round 0 is the defect, not a virtue. A changeset covers the whole diff and the higher level wins. Cost: one frontmatter line; the fixed group already has pending minor entries, so the release level does not move.

③ Boundary flags

Implemented-by: claude/issue-10254-sections-customfields-drawer-modal
Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33C

VERDICT: FAIL


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 2c6e298efcb9f93a2b77cfbd9f942e570933863b

Rendered by an isolated review subagent spawned by the domain:ui#4 seat; its served tier was checked against its transcript stamps (64 of 64 model stamps at the review tier). Adopted by this seat. Focused re-review of patch round 2; the prior record at 49dc4dd3c242c978239868d321b2df9125a7f22d covers the unchanged code.

① Derived judgments

  • Inputs. Head 2c6e298efcb9f93a2b77cfbd9f942e570933863b resolved from the PR API (matches the expected value) and fetched as pull/10451/head (FETCH_HEAD equals it). Its parent is 49dc4dd3c242c978239868d321b2df9125a7f22d, the prior head; the merge-base with origin/main is still 0d39dc6e5d2c8e1ac3516a4a65ec81ab52e210ea, the dev's BASE. Three commits (bbe2b1e4b, 49dc4dd3c, 2c6e298ef), 10 files, +334 / −63. Read first: the prior record, proposed-title-r2.txt, proposed-body-r2.md, the claim 5825076379, amendments 5825490852 / 5825785976 and the REWORK comment 5826395815. Nothing was re-run locally; every judgment below is by git show / git grep / git diff on the fetched objects plus REST reads.

  • (a) Round 1 to round 2 is one line. git diff 49dc4dd3c…2c6e298ef names exactly one path, .changeset/10254-sections-customfields-drawer-modal.md (blob bd57bbee9 to e896916f1), one hunk, one changed line: frontmatter line 2, '@object-ui/plugin-form': patch to '@object-ui/plugin-form': minor. diff-tree -r --raw between the two roots lists only that entry. The packages tree object is 7f3003bce098b2244f66e3f4e2f22c5033ddd713 at both heads, so no code or test file moved. The changeset body (line 4 onward) hashes SHA-256 f0c71cfe… at both heads, and lines 1 and 3 hash identically as well: body bytes identical, frontmatter lines 1 and 3 unchanged.

  • (b) The changeset at head is TRUE, cites no line address, names no level. Read sentence by sentence against the head tree: the registered-description quote is what the unchanged src/index.tsx carries; at BASE the five arms' buildSectionFields contexts carried no customFields (TabbedForm.tsx / WizardForm.tsx / SplitForm.tsx had zero occurrences of the word) and at head all five pass customFields: schema.customFields (TabbedForm.tsx, WizardForm.tsx, SplitForm.tsx, DrawerForm.tsx twice, ModalForm.tsx twice); "the same lookup the default arm's merge uses" is findCustomFieldMember, whose only callers are mergeCustomFields and normalizeSectionField (shape 1 and shape 2); the bare-name and spec-entry sentences match those two branches; "the widget changes only when the entry restates type" is the rawType line (fd.type ?? (member ? undefined : …)); "a plain text input labelled with the field name" is fromObjectSchema's { name, label: name, type: 'input' } fallback; the A2 sentence is pinned as UNNAMED MEMBER on all six arms; the three interfaces now declare customFields?: FormField[] as DrawerFormSchema and ModalFormSchema did at base; the closing "Unchanged" sentence is what ObjectForm.tsx's section loop (copies only visibleOn / colSpan / span) versus normalizeSectionField's full override set shows. A regex sweep finds no path:line, #L or "line N" citation. The body contains none of the words patch, minor, major or breaking, so nothing in it contradicts the frontmatter's minor.

  • (c) The proposed title is TRUE, names the five arms, and fits. fix(plugin-form): draw customFields members inside explicit sections on the drawer, modal, tabbed, wizard and split arms is 120 bytes (the file includes its trailing newline), under 256. The five words drawer, modal, tabbed, wizard, split are present and are exactly the five formTypes whose buildSectionFields context gained customFields; ObjectForm.tsx (the default arm) is byte-identical at base and head (blob 703e05327), so the title claims neither too much nor too little. As the squash subject it agrees with the changeset heading and with the three commit subjects' fix(plugin-form) scope.

  • (d) The proposed body. Every factual sentence was checked against head; the findings:

    • Clause-② paragraph. src/index.tsx re-exports TabbedFormSchema, WizardFormSchema, SplitFormSchema, DrawerFormSchema and ModalFormSchema by export type, and package.json maps exports["."].types to dist/index.d.ts, so the three types are on the package face. findCustomFieldMember and SectionFieldsContext are absent from it: index.tsx has no export * and imports nothing from customFieldsMerge, sectionFields or flatFields (its one mention of flatFields.ts is inside a description string), and FlatFieldsContext, the only other type extending SectionFieldsContext, is not exported there either. "The claim line was amended to yes in 5825785976" is what that comment says ("The claim line now reads: Clause-②: yes"). "the one DrawerFormSchema and ModalFormSchema already declare": true at base.
    • What changes. The five bullets carry over from the prior record's (a) through (g) and the code has not moved; re-confirmed cheaply: withCustomFieldMembers has zero occurrences at head; the three schema files had no customFields at base, so "does not compile without it" holds.
    • Surface paragraph. Claim 5825076379's file surface is DrawerForm.tsx, ModalForm.tsx, "a helper beside buildSectionFields", tests beside them and one changeset. Amendment 5825490852 adds TabbedForm.tsx, WizardForm.tsx and SplitForm.tsx "each at its buildSectionFields context". Amendment 5825785976 adds submitTarget.ts, comment-only. All three sentences of the paragraph match those comments.
    • Unified / not unified. Read in ObjectForm.tsx: sectionDefByName is keyed by the entry's field or name; members are the sourceFields entries whose name is in sectionFieldNames (a filter by name), so a name the pool does not hold is dropped and the order is the pool's; the merged copy takes only visibleOn, colSpan, span; the pool is formFields, built by mergeCustomFields. All four sentences TRUE.
    • A3 paragraph, judged by reading normalizeSectionField and the default arm. Member case, entry { field: 'note', label: 'SECTION LABEL' } over a member INLINE NOTE with required: true. Default arm: mergeCustomFields puts the member in the pool whole; the section loop copies none of label / required from the entry, so the drawn field keeps INLINE NOTE and required: true, which renders INLINE NOTE*. Other five: shape 2, base = { ...member }, fd.label != null sets SECTION LABEL, fd.required == null skips the required branch so the member's true survives, which renders SECTION LABEL* with the marker from the member. Both sentences TRUE. No-member case, entry { field: 'note', label: 'SECTION LABEL', required: true } with no member naming note. Default arm: the pool's note is generated from the object schema (the card fixture's note: { type: 'text', label: 'Note' }, not required), and neither label nor required is copied from the entry, so it renders Note with no marker. Other five: fromObjectSchema gives label Note, fd.label sets SECTION LABEL, fd.required runs isRequiredInForm({ required: true, defaultValue: undefined }, create) which returns true, so SECTION LABEL*. Both sentences TRUE. The *-as-label-suffix reading is the convention the SPEC ENTRY row itself pins (labelOf('note') equals INLINE NOTE*). The round-1 defect (a * on an entry that did not restate required) is gone: the no-member entry now carries required: true.
    • A4 paragraph. The test file at head: ARMS = ['simple', 'drawer', 'modal', 'tabbed', 'wizard', 'split'], describe.each(ARMS), five it rows named LABEL, REQUIRED, SPEC ENTRY, UNDECLARED NAME, UNNAMED MEMBER; MAIN_SECTION is the single section; open: true off simple; aria-required, INLINE NOTE is required, create not called then called with note: 'filled', textarea type, colSpan, drawnFields() equalities are all present as described.
    • Reverse-proof counts. Carried over from the prior record, which reconciled every number with the case list (30 = 6 arms × 5 rows; wizard leg 4 / 26; three-arm leg 12 / 18; round-0 legs 5 / 25 of 30, 2 / 13 of 15, 8 / 7 of 15). Two anchors re-checked here: WizardForm.tsx at head is blob b9dfeef7dab8…, the restore value the wizard leg quotes; 117 test files exist under packages/plugin-form at head, and 38 test files outside it name at least one of the listed symbols (both git counts), matching the two suite counts.
    • Changeset section. TRUE in full: level minor; frontmatter line 2 only; lines 1 and 3 and the body identical (hashes above); the body names no level; 49dc4dd3c and 2c6e298ef differ only in that line.
    • Gate list. The seven scripts exist at head and their success strings match the quotes (the no-major tool spells major in backticks, which a code span cannot carry; immaterial). Presence: nine files under packages/plugin-form/src, the test included, and the tool's format string is … of N released package(s). Claims gate "flags five": by the tool's own rule (a backticked basename resolving to exactly one tracked file, excluding empty-frontmatter changesets), the five are 6237-wizard-step-config-split.md (WizardForm.tsx), 8738-fields-warn-route1.md, 9884-master-detail-fields-intersection.md, console-formpage-visible-predicates-5594.md (sectionFields.ts) and simple-form-consults-declared-submit-handler.md (submitTarget.ts); 4783-drawerform-shared-container-grid-cols.md names DrawerForm.tsx / ModalForm.tsx but declares no bump, which the tool skips (its exclusion 2). Every one of those basenames resolves to one file at base. "14 other pending changesets name TabbedForm / WizardForm / SplitForm": 14 by git grep excluding this PR's own (one of them, 6237-wizard-step-config-split.md, is also among the five, so "other" reads as other than this PR's changeset). One clause is an overstatement and the seat writes it corrected: "none says anything about customFields on those arms" is not so; tidy-forms-refuse-target-less-submit.md names all five arms and says a non-empty customFields is a legitimate inline field source for their submit-target test. What is true, and what the prior record verified: none of the 14 says those arms lack or ignore customFields, none speaks to customFields under sections, and none is falsified by this change. The seat writes the clause as "and none speaks to customFields under sections on those arms, or is falsified by this change". The head's CI agrees with the rest of the list: Changeset Claim Re-read, Changeset Bump Policy, Changeset Declaration, Changeset Fixed Group Check, Changeset Overwrite Report, Control Byte Scan, Line Citation Gate, Governed Surface Queue Guard, Lint and Type Check are all success on 2c6e298ef.
    • Acceptance note on the two index.tsx quotes. index.tsx (blob 059806a71, identical at base and head) carries ObjectForm.tsx:361 and ModalForm.tsx:638 in two description strings. At 0d39dc6e5: ObjectForm.tsx line 361 is a line inside a comment block and modalCloseButton is read at line 528 ("further down the file"); ModalForm.tsx line 638 is the if (loading) return and the contentLayout check is at line 727. So neither line holds what its description says: TRUE, for both, as the round-1 record asked. The numbers are bound to 0d39dc6e5 in the same sentence, the sha-bound shape the claims gate exempts; they are quotes of what index.tsx carries, not new citations.
    • Closing keyword. Fixes #10254 on line 1 is the only closing-keyword match in the proposed body. Other issue mentions are bare (#10073 twice, #5594, #6237, #8738, #9884), none preceded by a closing verb.
    • Identifiers and brackets. No model or version identifier in the title, the body or the new commit message (scanned with the recogniser's id-shape rule and the trailer rule; the only hits are the generic co-author trailer's address and the session-URL trailer). No angle brackets in the title or body. The only line-address strings in the body are the two index.tsx quotes above, resolved at the tree they name. The short shas 49dc4dd3c, 2c6e298ef, 0d39dc6e5, bbe2b1e4b all resolve.
  • (e) Carry-over. The packages tree object is identical at 49dc4dd3c and 2c6e298ef, so the prior record's PASS items on the code (one lookup, member copy, wizard gate, default arm untouched, the 30 pins, submitTarget.ts comment-only), the pins, the Clause-② face, the surface and the serial reading stand unchanged. ObjectForm.tsx and index.tsx are byte-identical at base and head.

② Semver level

minor is right, and it is what head declares. Three exported interfaces (TabbedFormSchema, WizardFormSchema, SplitFormSchema, all re-exported from the package entry) accept a member they refused at base: a public-surface widening, ruled Clause-②: yes in 5825785976. objectstack AGENTS.md, Post-Task Checklist item 3: "The declaration is Clause-②: yes|no plus at most one arm from the closed pair (widening)/(narrowing): yes takes at least minor". objectui AGENTS.md 「版本号策略(version alignment)」: 「minor/patch 独立演进 … objectui 自己的改动照常用 changeset 推进」 and 「推论:changeset 里不要声明 major … objectui 自身的破坏性变更也标 minor(在正文里写清 breaking 语义即可)」. So the level is capped at minor (never major, breaking folds into minor) and a widening advances to minor by ordinary semver. Evidence at head: frontmatter line 2 reads '@object-ui/plugin-form': minor; lines 1 and 3 (--- / ---) hash identically to round 1; the body is byte-identical and names no level. The Changeset Bump Policy (no-major) and Changeset Declaration (presence) check-runs are success on the new head. The round-1 disagreement between the PR's yes and the changeset's patch is closed.

③ Boundary flags

  • Closing keyword. Fixes #10254 is the only closing keyword in the current PR body and in the proposed body. None of the three commit messages carries one.
  • Model identifier. None in the new commit 2c6e298ef's message or trailers: the body is four plain sentences, and the trailers are the generic co-author trailer and the session-URL trailer (the dispatch's model-free pair). Author and committer identities are the same generic pair.
  • File surface. Still exactly 10 files, git diff --name-status -M 0d39dc6e5…2c6e298ef agreeing with the PR API: A .changeset/10254-sections-customfields-drawer-modal.md; M packages/plugin-form/src/DrawerForm.tsx; M packages/plugin-form/src/ModalForm.tsx; M packages/plugin-form/src/SplitForm.tsx; M packages/plugin-form/src/TabbedForm.tsx; M packages/plugin-form/src/WizardForm.tsx; A packages/plugin-form/src/__tests__/sectionsCustomFields-10254.test.tsx; M packages/plugin-form/src/customFieldsMerge.ts; M packages/plugin-form/src/sectionFields.ts; M packages/plugin-form/src/submitTarget.ts. ObjectForm.tsx and index.tsx untouched.
  • mergeable_state: behind (mergeable: true). origin/main has advanced 23 commits past the merge-base 0d39dc6e5 to 274e14af4; none of them touches any of the 10 files, and the only packages/plugin-form touch on that range is 274e14af4 adding a new test file (cascadePruneWire-10291.test.tsx). No textual conflict; whether to update the branch before merging is the seat's call at merge time.
  • Check-runs on 2c6e298efcb9f93a2b77cfbd9f942e570933863b. Polled in the foreground every 60s from 04:04Z: 12 in_progress at the first read (test shards, Type Check, Lint, Build Docs, Spec Main Shape Gate), 5, 2, 1, then none. Final at 04:08:38Z: 43 total, 40 success, 3 skipped, 0 failure, 0 in_progress / queued. The three skipped: Test (coverage), dependabot, Test (coverage shard ${{ matrix.shard }}/4), matrix / bot placeholders, not red. Every non-success is named here; nothing remained in progress, so the 15-minute budget was not needed.
  • Required at write time (no dev round). The seat writes the proposed title verbatim and the proposed body with the one clause in ①(d) Gate list reworded as given. Nothing else in the two texts needs a change.

Implemented-by: claude/issue-10254-sections-customfields-drawer-modal
Reviewed-by: session_01BP8CMtACxTdLjqR6rhd33C

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet objectstack-fleet Bot changed the title fix(plugin-form): draw customFields members inside explicit sections on the drawer and modal arms fix(plugin-form): draw customFields members inside explicit sections on the drawer, modal, tabbed, wizard and split arms Sep 25, 2026
@os-litant
os-litant marked this pull request as ready for review September 25, 2026 04:19
@os-litant
os-litant added this pull request to the merge queue Sep 25, 2026
Merged via the queue into main with commit d5cb261 Sep 25, 2026
45 checks passed
@os-litant
os-litant deleted the claude/issue-10254-sections-customfields-drawer-modal branch September 25, 2026 04:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(plugin-form): with explicit sections plus customFields, the drawer and modal arms ignore the authored members (the default arm honours them)

2 participants