Skip to content

fix(plugin-form): a record dialog with no authored layout opens bounded with a two-column grid (objectui#12521) - #12546

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12521-form-dialog-layout
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-12521-form-dialog-layout

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #12521
Clause-②: no. No type, prop, export or spec declaration changes: the console's interim is one module-private function in AppContent.tsx, and the dialog's new helpers are module-private in ModalForm.tsx.

Implemented by the dispatched dev of the domain:ui seat 1 PM loop, rework round 1, session https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB. It executes letter B (comment 6203721639), its section-columns fork (6204264665) and the interim ruled in 6205068311 on the maintainer's 「可以,12521 走快速方案」. This PR now closes the card. The interim's deletion is a follow-up card the seat files.

What lands

1. The console's interim desktop bound (@object-ui/app-shell)

  • deriveRecordFlowSurface (installed @objectstack/spec 18.0.0-next.1, re-exported by @object-ui/plugin-view) answers size: 'full' for a create, edit, child-create or child-edit flow on an object with 12 or more authorable fields. AppContent spread that into modalSize: 'full', so HotCRM Leads → New opened at 1368 × 810 (95vw).
  • One module-private function, interimBoundedTaskFlowSurface, now wraps the derivation's answer at its one call site. It maps size: 'full' to size: 'auto', the shape letter B makes the spec derive (spec(record-surface): a field-heavy create/edit flow on desktop derives a bounded modal, not full-screen; mobile keeps full (objectui#12521 letter B) objectstack#23734). So the dialog is handed no derived modalSize, and ModalForm's own bounded default decides: xl or lg, never full.
  • Its docblock cites the interim (6205068311) and names its exit: delete the function and call deriveRecordFlowSurface directly once objectui installs a spec carrying spec(record-surface): a field-heavy create/edit flow on desktop derives a bounded modal, not full-screen; mobile keeps full (objectui#12521 letter B) objectstack#23734.
  • The exit is pinned. AppContent.recordDialogInterimBound-12521.test.tsx calls the installed deriveRecordFlowSurface for a 14-field object on a desktop create flow, the way AppContent calls it (no viewport). While it answers full, the test asserts the interim applies. Once it answers anything else, the test fails with a message naming the deletion. So the bump that installs the new spec cannot land with the interim in place. There is no skip or todo; it is a real assertion today.
  • Phone: no posture check is needed (A1 below). The phone sheet is ModalForm's own presentation below sm, and no modalSize reaches it.

2. The dialog's own defaults (@object-ui/plugin-form, ModalForm)

  • Size (round 1, unchanged). With no modalSize, the dialog picks lg or xl from the grid it renders and its field count, never full. The rule is inferModalSize(min(columns, inferColumns(fieldCount))). An authored modalSize, 'full' included, is used as written.

  • Flat grid (round 1, unchanged). A form with no columns and no sections draws two per row from the container's @md tier (grid gap-4 grid-cols-1 @md:grid-cols-2). Each field takes one cell; a wide kind or a span: 'full' field takes the row. The object's fieldGroups fallback lays out the same way.

  • Section density (new, the fork's renderer side). In a form that declares no columns, a section that declares none draws two per row from @md. A declared count, 1 included, is drawn as written. The rule lives in two module-private helpers:

    • dialogSectionColumns gives a section's count: declared, else the form's declared columns, else 2;
    • sectionedDialogColumns gives the grid width: the form's columns, else the widest section count.

    Both feed objectui#12548's sectionedGridLayout. When every count fits the widest grid, the grid stays CONTAINER_GRID_COLS'. Otherwise it switches to the 12-track grid, where an undeclared section's fields span 6 at @md and a wide kind or span: 'full' spans 12. Below @md it is one column, as before.

    • Undeclared beside columns: 1: the grid is two wide; the undeclared fields take one cell, the columns: 1 fields @md:col-span-2.
    • Undeclared beside columns: 3: grid gap-4 grid-cols-1 @md:grid-cols-12. The undeclared fields carry @md:col-span-6, two per row at every tier. The columns: 3 fields carry @md:col-span-6 @2xl:col-span-4.
    • A form-level columns still sets an undeclared section's count, exactly as before.
    • Everything declared draws the classes main draws.

3. The merge with main

main was merged in as a merge commit (no rebase, no force-push). ModalForm.tsx conflicted with PR #12556 (objectui#12548, the per-section 12-track grid) and PR #12571 (objectui#12559, the dialog-owned Suspense). The resolution keeps both landed behaviours: every sectioned dialog lays out through sectionedGridLayout, and the body's Suspense boundary is untouched. The round-1 CONTROL pin (a HotCRM-shaped columns: 2 + columns: 3 form) now asserts the classes PR #12556 draws on main.

Measurements behind the route

  • A1, the phone path. AppContent calls the derivation with no viewport, so it only ever receives the desktop answer. Every ModalForm size class is sm:-prefixed (sm:max-w-2xl, sm:max-w-5xl, sm:max-w-[95vw] sm:w-full). MobileDialogContent's base classes are fixed inset-0 w-full h-[100dvh]. So below sm the dialog is a full-screen sheet whatever modalSize says.

    • Pinned: the real dialog's classes with no responsive prefix are identical with the interim and under the old modalSize: 'full'.
    • Measured in Chromium at 390 × 844: both are 390 × 844 at (0, 0), and every field is 358 px wide, one per row.
    • So the interim needs no posture signal, and it carries none.
  • A2, where the section default is applied: upstream, at the authoring producer. @objectstack/spec FormSectionSchema.columns is .default(1). Probed on the installed spec 18.0.0-next.1, a section with no columns:

    • FormViewSchema.parse answers [1, 1, 3] for sections declaring nothing, 1 and 3;
    • defineView, the call HotCRM's lead.view.ts uses, answers [1, 1, 3] too;
    • defineStack in strict mode returns the parse's result.data.

    objectui never parses form views client-side; resolveFormViewLayout hands objectDef.form.sections through as served. So a section from an authored stack reaches the dialog DECLARED columns: 1 and draws one column. The undeclared rule is correct but takes effect only after the spec drops the default (spec(view): FormSectionSchema.columns drops default(1) — an undeclared section density is the renderer's (by available width), a declared one is the author's ceiling (objectui#12521 fork) objectstack#23740). It is not worked around: a declared 1 is never read as undeclared, and a pin holds that. NOT MEASURED: the HTTP leg (a live server's served JSON), and whether a view saved through the metadata API at runtime is parsed on save. Neither objectstack nor hotcrm is installed in this container.

  • A3, the merge. Resolved as above; PR fix(plugin-form): each form section draws its own columns count per row (objectui#12548) #12556's and PR fix(plugin-form): a record dialog's collapsed section opens in place, not behind the console's loading screen (objectui#12559) #12571's own pins were re-run as CONTROLs, all green (see Tests).

  • A4, the no-section default against default(1). With the fork ruled, the only remaining question is A2. A form with no sections never meets FormSectionSchema, so its two-per-row default is unaffected. The fieldGroups fallback stamps its own columns on the sections it derives, so it is unaffected too.

Tests (local, head 91fd2dbe68)

Every vitest run went through objectstack's os-verify-lock.sh.

  • packages/app-shell/src/console/__tests__/AppContent.recordDialogInterimBound-12521.test.tsx (new, 8 cases), on a real AppContent mount with the real ModalForm where a case reads the dialog:
    • the 14-field object is field-heavy (the installed deriveRecordSurface pages it);
    • desktop create: no modalSize is handed over, and the dialog is sm:max-w-5xl, not sm:max-w-[95vw];
    • edit, child-create and child-edit: no derived modalSize is handed over;
    • phone: the classes that apply below sm are identical to those of the old 95vw dialog, the sheet's fixed inset-0 w-full h-[100dvh] included, and no max-w-* reaches a phone;
    • CONTROL: a modalSize: 'full' handed to the dialog still opens at 95vw;
    • EXIT: the installed derivation still answers full, and while it does the interim applies.
  • packages/plugin-form/src/__tests__/dialogDefaultLayout-12521.test.tsx (14 cases, 5 new): the round-1 cases; the CONTROL re-expressed on PR fix(plugin-form): each form section draws its own columns count per row (objectui#12548) #12556's classes; and the density cases. Those are: undeclared beside columns: 1; undeclared beside columns: 3 on the 12-track grid; a columns: 3 section draws three; every section at columns: 1 (what the spec's parse writes today) draws one column; and a form-level columns still sets an undeclared section's count.
  • Ablation of the interim: interimBoundedTaskFlowSurface made the identity through objectstack's scripts/ablation-replace.mjs (anchor 1 → 0, blob 91e724d1d8 → a9cf6e490d), trap-protected. Result: 5 failed, 3 passed.
    • Red: the create, edit, child-create and child-edit cells, and the EXIT cell, each at expected 'full' to be undefined.
    • Green: the precondition, the phone cell and the authored-size CONTROL.
    • Restore proven: the blob equals HEAD (91e724d1d8) and git diff HEAD is empty.
  • The exit message, simulated once. The exit cell's own read was mutated to answer size: 'auto', as a spec carrying spec(record-surface): a field-heavy create/edit flow on desktop derives a bounded modal, not full-screen; mobile keeps full (objectui#12521 letter B) objectstack#23734 would. The cell then fails with: "The installed @objectstack/spec no longer derives size: 'full' for a field-heavy desktop task flow: spec(record-surface): a field-heavy create/edit flow on desktop derives a bounded modal, not full-screen; mobile keeps full (objectui#12521 letter B) objectstack#23734 is installed. Delete the interim interimBoundedTaskFlowSurface in packages/app-shell/src/console/AppContent.tsx, call deriveRecordFlowSurface directly at its call site, and delete this file (objectui#12521, interim ruled in comment 6205068311).: expected 'auto' to be 'full'". Restore was proven the same way. No permanent test file.
  • CONTROLs plus the new pins in one run, 8 files, 123 passed:
  • The whole packages/plugin-form/ plus form-twelve-track-span-12548.test.tsx: 187 files, 2294 passed, 1 skipped, vitest exit 0.
  • Dialog hosts plus the census and ratchet sweep, 88 files, 2558 passed, VERDICT command-exit 0. The set is:
    • every packages/app-shell and apps/console test that git grep -l -E "ModalForm|useActionModal|openRecordForm|form=new|createTitle|editTitle|formType: .modal.|resolveFormViewLayout|deriveRecordFlowSurface" finds on the merged tree (40, the new file included);
    • the designer ObjectManager tests and ObjectFormDesigner.test.tsx;
    • the packages/types files that read ModalForm, and plugin-view's ObjectView.formTitleI18n and recordSurface tests;
    • the 43 census and ratchet files (git grep -l -E "census|ratchet|WRITE_SITES|AFFORDANCE_GRANTS") whose subject this diff touches (AppContent, ModalForm, autoLayout, the changesets, the docs page).
  • type-check for @object-ui/plugin-form and @object-ui/app-shell: exit 0, after turbo run build --filter='@object-ui/app-shell^...' (29 packages, VERDICT command-exit 0). Each runs tsc --noEmit and tsc -p tsconfig.test.json, whose include src/**/*.test.tsx covers both pin files.

Gates (local subset, each exit code captured before any pipe)

All exit 0:

  • changeset gates: check-changeset-presence ("6 source file(s) of 2 released package(s) changed, and this change declares 2 changeset(s)"), -no-major, -overwrite, -fixed, check:changeset-claims, check:pending-changeset-literals;
  • check:control-bytes and check:new-line-citations ("0 new citation(s)");
  • doc gates: check:doc-snippets ("796 of 796 block(s) judged, 0 failed"), check-doc-links, check:doc-fences, check:doc-json, check:doc-types, check-doc-expression-carriage, check:doc-example-ids. check:doc-snippets first answered PRECONDITION NOT MET (exit 2, read as not measured) and passed after its scoped build;
  • check:spec-symbols, check:installed-pin-claims;
  • check-test-path-roots and the three vi.mock gates;
  • eslint on the six changed TS files: 0 errors, warnings only, all no-explicit-any;
  • check-governed-queue-guard --test on the nine paths: NOT GOVERNED.

Left to CI: the full farm, including repo-wide pnpm lint and performance-budget.

What the landing seat's browser reading should show

Expected values below come from the DOM the real ModalForm renders for a HotCRM-Leads-shaped form (sections columns 2 / 3 / 2 / 1 / 2, last_name span: 'full'). It was measured in headless Chromium against a Tailwind 4.3.3 compile of that DOM's own classes, not the console's full sheet.

  • HotCRM Leads → New at 1440 × 900: the dialog is within the xl bound, 1024 × 810 (it was 1368 × 810). The sections are drawn as authored:

    • Contact Information two per row, 455 px each, with last_name on its own row at 926 px;
    • Lead Classification three per row, 298 px each;
    • a columns: 2 section two per row; Additional Information (columns: 1) one per row.

    Collapsed sections open in place.

  • At 390 × 844: the sheet is unchanged. It is a full-screen 390 × 844 sheet with every field 358 px wide, one per row, the same as before the interim.

  • Between sm and xl: at a 1024-wide window the dialog fills the width, 1024 px (it was 973 px at 95vw).

Acceptance notes


Generated by Claude Code

…ed with a two-column grid (objectui#12521)

ModalForm's derived size never reaches `full`: `inferModalSize` maps one
column to `lg` and two or more to `xl`, and the dialog picks it from the
grid that renders and its field count. A form that declares no `columns`
(on the form, and on no section) lays out at two columns from `@md` through
the explicit-columns route, so each field takes one cell and wide kinds or a
`span: 'full'` field take the row. Authored `modalSize`, `columns`, `colSpan`
and `span` are used as written.

Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB
Co-authored-by: Claude <noreply@anthropic.com>
…ares columns or a size (objectui#12521)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 69 chunks) 3087.6 KB 3119.4 KB
Main entry chunk (gzip) 73.3 KB 350 KB
Entry file index-Bem6s9uR.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) 20.90KB 7.67KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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.62KB 0.83KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 594.75KB 143.42KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 254.55KB 71.19KB
fields (index.js) 282.89KB 72.11KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
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) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.72KB 48.07KB
plugin-dashboard (index.js) 143.88KB 38.88KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.16KB 69.28KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 179.38KB 46.63KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 262.33KB 72.76KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 127.76KB 32.64KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.05KB 23.86KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
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.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

github-merge-queue Bot pushed a commit that referenced this pull request Oct 15, 2026
…ow (objectui#12548) (#12556)

Fixes #12548
Clause-②: no — no type, prop, export or spec declaration changes;
`sectionedGridLayout` / `TWELVE_TRACK_GRID_COLS` live in `autoLayout.ts`
and are not re-exported from the package entry.

## What changed

A sectioned form draws all its sections in ONE field grid, sized by the
widest section, and each field spanned `Math.round(grid /
sectionColumns)` cells of it. When a section's count did not fit that
grid, the section drew the wrong number of fields per row. Measured on
`main` at `9c2785b`, ModalForm, 2 / 3 / 1 form:

| field | grid | cell |
|---|---|---|
| `columns: 2` field | `grid-cols-1 @md:grid-cols-2 @2XL:grid-cols-3` |
`@md:col-span-2` → **one per row at every tier** |
| `columns: 3` field | same | (none) → 2 / 3 per row (correct) |
| `columns: 1` field, `span: 'full'` | same | `@md:col-span-2
@2XL:col-span-3` (correct) |
| `columns: 3` beside `columns: 4` | `… @4XL:grid-cols-4` | (none) →
**four per row at `@4xl`** |

ObjectForm, SplitForm, TabbedForm and WizardForm (under a form-level
`columns: 3`) drew the same classes.

**One shared rule**, `sectionedGridLayout(formColumns,
sectionColumns[])` in `packages/plugin-form/src/autoLayout.ts`, now
called by all five hosts in place of the per-host `applyAutoColSpan` +
`containerGridColsFor` pair:

- **Every section is 1 or the grid width:** the rule returns the old
`CONTAINER_GRID_COLS` grid and the old `applyAutoColSpan` spans.
Single-density forms, and forms with no section `columns`, are therefore
byte-identical.
- **Otherwise:** the grid is `grid gap-4 grid-cols-1 @md:grid-cols-12`,
and each field's `colSpan` counts twelfths of the row:
  - `span: 'full'` and wide widgets take 12;
- an authored `colSpan` counts that many of its own section's columns,
clamped to them;
  - any other field takes 12 / the section's columns.
- A section's count is still clamped to the form grid, so a form-level
`columns` still outranks it.

**`@object-ui/components` form renderer** (where the span classes are
drawn): on a container carrying `@md:grid-cols-12`, a twelfths `colSpan`
maps through a literal table, `TWELVE_TRACK_SPAN_CLASS`. The table keeps
each count's existing step ladder:

| section `columns` | twelfths | classes | per row at `@md` / `@2xl` /
`@4xl` |
|---|---|---|---|
| 1 | 12 | `@md:col-span-12` | 1 / 1 / 1 |
| 2 | 6 | `@md:col-span-6` | 2 / 2 / 2 |
| 3 | 4 | `@md:col-span-6 @2XL:col-span-4` | 2 / 3 / 3 |
| 4 | 3 | `@md:col-span-6 @2XL:col-span-4 @4XL:col-span-3` | 2 / 3 / 4 |
| authored 2 of 3 | 8 | `@md:col-span-12 @2XL:col-span-8` | |
| authored 3 of 4 | 9 | `@md:col-span-12 @4XL:col-span-9` | |

Below `@md` the form stays one column. Every other container is rendered
by the old code path, unchanged. The literal `'@md:grid-cols-12'` sits
in components' source, so the published `style.css` carries the utility
too.

## Verification record (at `88bfef0`)

- New pins,
`plugin-form/src/__tests__/mixedSectionColumns-12548.test.tsx` (25
tests):
- per host (modal / object / split / tabbed, plus wizard under a
form-level `columns: 3`), a 2 / 3 / 1 form reads 2 / 3 / 1 per row at
`@4xl` and 2 / 2 / 1 at `@md`, and the 3-column section reads 3 at
`@2xl`;
- `span: 'full'` takes the row; base `grid-cols-1`; 3-beside-4 reads
three per row;
- CONTROL: all `columns: 2` and all `columns: 3`, per host, keep the
literal grid and span classes `main` drew;
- a drift guard: each count's ladder on the 12-track grid equals
`min(columns, tier)` against `CONTAINER_GRID_COLS[4]`'s tiers;
  - pure-rule pins.
- Renderer pins,
`components/src/renderers/form/__tests__/form-twelve-track-span-12548.test.tsx`
(7), including a CONTROL that the 1–4 containers emit as before.
- Before the fix, the new file ran 6 failed / 8 passed on `main`: the
mixed pins red, the CONTROLs green.
- Ablation 1: `sectionedGridLayout` forced to the old path. 12 mixed
pins red, 13 green (all 8 CONTROLs among them). Restore was proven by
blob hash and an empty `git diff HEAD`.
- Ablation 2: the renderer's 12-track detection turned off. 16 red, 16
green, restored the same way.
- `pnpm --filter '@object-ui/plugin-form^...' build` → 0
- `pnpm --filter @object-ui/plugin-form type-check` → 0
- `pnpm --filter @object-ui/components type-check` → 0
- tsc over both new test files → 0 (the package type-checks exclude
tests)
- Whole `packages/plugin-form` suite: 184 files, 2269 passed, 1 skipped
→ 0
- Every suite that renders a host or touches the census, plus
`components/src/renderers/form/__tests__/*`: 225 files → 0
  - packages: 208 files, 3067 passed, 17 skipped
  - apps / examples / scripts: 17 files, 1465 passed
- `check:control-bytes`, `check:new-line-citations` (0 new),
`check-changeset-presence`, `check-changeset-no-major`,
`check:changeset-claims`, `check:pending-changeset-literals`,
`check:test-path-roots`, `check:vi-mock-specifiers`,
`check:vi-mock-inherit`, `check:vi-mock-override-shape`,
`check:unreferenced-sources`, eslint on the 9 changed files (0 errors) →
all 0
- Tailwind v4 compile of `apps/console/src/index.css` emits
`@md:grid-cols-12`, `@md:col-span-{6,12}`, `@2xl:col-span-{4,8}` and
`@4xl:col-span-{3,9}`; `@md:col-span-8` and `@2xl:col-span-12` are
absent, as expected. Compiling `packages/components/src/index.css` also
emits `@md:grid-cols-12`.
- NOT MEASURED locally: `check:readme-exports` (needs every package's
dist; no export changed) and `performance-budget` (a full console
build). Both are left to CI.

## What the landing seat's browser reading should show

HotCRM Leads → New. Pre-merge reading: the HotCRM-shaped DOM rendered by
`ModalForm`, measured in Chromium with the console's Tailwind build:

| dialog | Contact Information (2) | `last_name` (`span: 'full'`) | Lead
Classification (3) | Company (2) | Additional (1) |
|---|---|---|---|---|---|
| 1368px, before | **1 per row, 875px** | 1320px | 3 per row, 429px | 1
per row, 875px | 1320px |
| 1368px, after | **2 per row, 652px each** | 1320px | 3 per row, 429px
(unchanged) | 2 per row, 652px | 1320px |
| 1024px, after | 2 per row, 480px | 976px | 3 per row, 315px
(unchanged) | 2 per row, 480px | 976px |
| 640px, after | 2 per row, 288px | 592px | 2 per row, 288px (unchanged
ladder) | 2 per row | 592px |
| 400px, after | 1 per row | 1 per row | 1 per row | 1 per row | 1 per
row |

At 1440 the seat should see:
- Contact Information at two fields per row, of one width;
- `last_name` on its own full row;
- Lead Classification at three per row;
- nothing empty in the right third except a row's natural remainder.

## Acceptance notes

- The dialog's size (`inferModalSize`, `modalSize`, `AppContent.tsx`)
and the no-section-`columns` default are untouched. objectui#12521 and
PR #12546 keep them. #12546 touches `ModalForm.tsx` and `autoLayout.ts`
in different regions, so expect a merge of `main` in its rework round.
- In a mixed form an authored `colSpan` counts its SECTION's columns (2
in a 3-column section = two thirds of the row). Before, it counted cells
of the widest section's grid. A single-density form keeps the old
reading.
- Authored `colSpan: 2` in a `columns: 4` section of a mixed form
reduces to one half and reads half the row at every tier. On a 4-only
grid it reads full / two thirds / half.
- A section `gridClassName` in a tabbed or split form with mixed
densities sits beside twelfths spans. That combination was already
inconsistent before this change.
- objectui has no `scripts/pm/dispatch-gates.mjs`, so the gate list was
derived by hand from `package.json` `check:*` and the workflows.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01DN3fpUznSTqNMkk9nXd5BG

Co-authored-by: Claude <noreply@anthropic.com>
Resolves ModalForm.tsx against objectui#12548 (PR #12556, the per-section
12-track grid) and objectui#12559 (PR #12571, the dialog-owned Suspense
boundary). The sectioned dialog keeps this branch's grid width
(sectionedDialogColumns) and lays each section out through
sectionedGridLayout, so both landed behaviours stand.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3092.5 KB 3119.4 KB
Main entry chunk (gzip) 73.3 KB 350 KB
Entry file index-BheSORv0.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) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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.62KB 0.83KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 596.60KB 143.79KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 254.55KB 71.19KB
fields (index.js) 283.09KB 72.13KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
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) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 145.69KB 39.39KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.91KB 69.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.85KB 46.68KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 262.82KB 72.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 129.82KB 33.09KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
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.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

…alog on desktop as a pinned interim; an undeclared section draws two per row (objectui#12521)

- AppContent: one module-private function, interimBoundedTaskFlowSurface,
  gives the installed deriveRecordFlowSurface's field-heavy task-flow answer
  the bounded shape letter B moves into the spec
  (objectstack-ai/objectstack#23734), so ModalForm's lg / xl default sizes
  the dialog. The phone sheet is ModalForm's own below-sm presentation and
  no modalSize reaches it.
- The exit is pinned: AppContent.recordDialogInterimBound-12521 calls the
  installed derivation and fails, naming the deletion, once it no longer
  answers 'full'.
- ModalForm: in a form that declares no columns, a section that declares
  none draws two per row from @md, through objectui#12548's per-section
  grid; a declared count, 1 included, is drawn as written.

Claude-Session: https://claude.ai/code/session_01JZnppgHRp4sREcouhkQYDB
Co-authored-by: Claude <noreply@anthropic.com>
…sole's interim bound, in the docs and changesets (objectui#12521)

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 70 chunks) 3092.5 KB 3119.4 KB
Main entry chunk (gzip) 73.3 KB 350 KB
Entry file index-CjzxT9ZA.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) 21.19KB 7.76KB
app-shell (runtime-config.js) 22.59KB 7.89KB
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.15KB 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.62KB 0.83KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 41.19KB 11.12KB
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.26KB 8.03KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (commentAuthorship.js) 1.39KB 0.78KB
collaboration (index.js) 1.93KB 0.85KB
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) 596.60KB 143.79KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 254.55KB 71.19KB
fields (index.js) 283.09KB 72.13KB
i18n (LocalizationContext.js) 2.92KB 1.42KB
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.52KB 2.39KB
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) 36.87KB 9.88KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 40.67KB 11.85KB
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) 15.35KB 5.51KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.82KB 2.38KB
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) 54.94KB 16.05KB
plugin-charts (index.js) 84.97KB 23.36KB
plugin-chatbot (index.js) 201.89KB 48.13KB
plugin-dashboard (index.js) 145.69KB 39.39KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 262.91KB 69.49KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.88KB 46.69KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 262.82KB 72.91KB
plugin-kanban (index.js) 53.23KB 16.71KB
plugin-list (index.js) 129.82KB 33.09KB
plugin-map (index.js) 27.07KB 8.96KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 46.66KB 13.13KB
plugin-timeline (index.js) 39.21KB 11.85KB
plugin-tree (index.js) 15.07KB 5.33KB
plugin-view (index.js) 93.43KB 23.94KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 12.07KB 3.68KB
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.07KB 1.30KB
sdui-parser (index.js) 7.30KB 3.12KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.81KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 23.87KB 7.83KB
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.78KB 2.70KB
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 (node-slots.js) 7.18KB 2.34KB
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-approval-decision.js) 0.20KB 0.20KB
types (record-components.js) 0.20KB 0.19KB
types (record-panels.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.26KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.48KB 3.50KB
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

Seat ACCEPT: objectui PR #12546 at 91fd2dbe68 · domain:ui seat 1 · session_01JZnppgHRp4sREcouhkQYDB · 2026-10-16T01:21Z

Verdict: ACCEPT. Reviewed against:

  • the card objectui#12521;
  • letter B 6203721639 and its section-columns fork 6204264665;
  • the interim ruling 6205068311, on the maintainer's 「可以,12521 走快速方案」;
  • the rework claim 6205076950;
  • the dev report on the card.

No contract-review face applies:

  • Clause-②: no on line 2;

  • no type, prop, export or spec read is added;

  • none of the 9 files is a published-schema or governed path (content/docs/** is prose, read below).

  • Shape: draft; base main; assignee sunhaolin. Line 1 is Fixes #12521, the body's only closing keyword. 9 files, +749/−55. Two changesets, @object-ui/plugin-form and @object-ui/app-shell, neither major.

  • CI on 91fd2dbe68: 34 success, 3 expected skips, 0 failures. mergeable_state is clean.

  • Ablation: the interim made the identity turns 5 red (create, edit, child-create, child-edit and the exit) and keeps 3 green (the precondition, the phone cell and the authored-size CONTROL). The exit message was proven by mutating the exit cell's read.

  • Browser reading. HotCRM 63db83e2 was served with its pinned @objectstack/cli 18.0.0-next.1. Readings were taken in Chromium against the head and main 3eb3ec138a, by the seat's read-only helper; every dialog was cancelled.

    Claim Head Base Verdict
    1. Desktop bound: Leads → New at 1440 1024 × 810, sm:max-w-5xl 1368 × 810, sm:max-w-[95vw] PASS
    1. Lead → Edit at 1440 1024 × 810 1368 × 810 PASS
    2. Phone sheet at 390 390 × 844, inputs 358, one per row identical PASS
    3. Authored sections at 1440 per-row counts as authored: contact 2 (last_name full), classification 3, company 2, address 1, privacy 2; cells 455 / 298 px the same counts, at 627 / 412.7 px PASS
    4. Objects lighter than 12 fields crm_event_attendee and crm_campaign_member identical to base; crm_article_feedback (5 fields, no form view) 672 px lg, two per row 512 px default, one per row PASS as ruled (below)
    Errors 0 page / 0 console after open, 16 dialog readings identical PASS
  • Accepted deviations:

    • A no-layout light object changes, as the card's own rulings 1–2 ask: a bounded size chosen by field count, and two per row from md. crm_article_feedback is that case. The change comes from the plugin-form default, which its changeset states, not from the console bound. The seat's brief worded claim 4 too broadly.
    • Where every density fits the 2-wide grid, PR fix(plugin-form): each form section draws its own columns count per row (objectui#12548) #12556's own single-density path keeps grid-cols-2. The drawing is the same: two per row from @md.
    • "An authored size still wins" has no path at the console host, because resolveFormViewLayout passes only sections, contentLayout and subforms. So that CONTROL hands modalSize: 'full' to the real ModalForm directly. No producer writes FormView.modalSize today: HotCRM, objectstack examples/ and objectui examples//apps/ give 0 hits.
  • Measured and stated, not worked around: @objectstack/spec's FormSectionSchema.columns default(1) is applied upstream at parse (defineView, defineStack). So the undeclared-section rule is dormant on authored stacks until spec(view): FormSectionSchema.columns drops default(1) — an undeclared section density is the renderer's (by available width), a declared one is the author's ceiling (objectui#12521 fork) objectstack#23740 lands. Until then, a defaulted 1 draws one column.

  • Out of scope:

    • Filed objectui#12593 (finding). Between sm and 1024 px, an xl dialog runs edge to edge with no viewport margin: 1024 at x = 0 for a 1024 window, 768 at x = 0 for a 768 window. This is ModalForm's existing size map, now reached by every field-heavy dialog.
    • Filed objectui#12592 (pm:blocked on spec(record-surface): a field-heavy create/edit flow on desktop derives a bounded modal, not full-screen; mobile keeps full (objectui#12521 letter B) objectstack#23734 / #23740), the interim's exit. It deletes interimBoundedTaskFlowSurface and rewrites the exit pin. It updates plugin-view/recordSurface.test.ts, which pins full and goes red on the same bump. It follows letter B's item 2 split for forms with an embedded child table, which the interim bounds too. It also brings the page ObjectForm / SplitForm / TabbedForm / WizardForm hosts' undeclared density in line with the dialog.
    • Acceptance notes: the console's global record dialog never reads FormView.modalSize / FormView.columns; there is zero pull.
  • Prose read against the diff: the plugin-form.mdx paragraph and both changesets say what lands: the bounded default, two per row for an undeclared density, the console interim with its exit, and the upstream default caveat.

Landing: ready and auto-merge now. Its merge closes objectui#12521.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 01:22
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 01:22
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit 7b5e558 Oct 16, 2026
38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation package: app-shell plugin tests

Projects

None yet

2 participants