Skip to content

fix(plugin-detail,app-shell): a field group's visibleWhen gates its record:details section through the spec's { group } reference (objectui#11630) - #11641

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-11630-detail-group-visible-when
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-11630-detail-group-visible-when

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #11630
Clause-②: yes

What was wrong

An object's fieldGroups[].visibleWhen gated the group on the entry form but not on the record detail page. deriveFieldGroupDetailSections rebuilt each group as an enumerated section and dropped the predicate that the shared derivation (deriveFieldGroupLayout, @objectstack/spec) passes through, and record:details read none. So the "Pro details" section of the card's reproduction rendered, header included, on a basic row.

Route (patch round 1: route B, seat ruling on the card)

@objectstack/spec 17.6.0 RecordDetailsProps.sections[] is strict. It declares group, the ADR-0085 §5 reference form that inherits a group's members and presentation, visibleWhen included. It declares fields as bare names, and it refuses visibleWhen on an enumerated section. So the fix writes pages in that vocabulary:

  • Synthesizer (packages/plugin-detail/src/synth/buildDefaultPageSchema.ts): resolveDetailSections, and through it buildDefaultDetails / buildDefaultTabs / buildDefaultPageSchema, now emit each declared group as { group: KEY, columns }. The trailing ungrouped section lists bare field names. The derivation sits behind one package-internal function, deriveFieldGroupDetailEntries. It returns each resolved section with the group's visibleWhen carried BESIDE it, never on it. The public deriveFieldGroupDetailSections returns exactly what it returned before this card: resolved sections, no predicate.
  • Renderer (packages/plugin-detail/src/renderers/record-details.tsx): record:details resolves a { group } reference against the object's fieldGroups and evaluates the group's visibleWhen against the bound record. It drops the section, heading and members, when the verdict is FALSE. It does not read visibleWhen off an enumerated section, a key the spec refuses there.
  • Runtime default page (packages/app-shell/src/views/RecordDetailView.tsx): the console's default record page builds its own section list, so it now writes { group: KEY, showBorder: true } for each declared group. Without that change the card's reproduction path stays ungated, because an enumerated copy cannot carry the predicate. Its ungrouped primary and "More details" sections are unchanged.
  • The record:details registration's sections input description now says a { group } reference inherits the group's visibleWhen, and that one written on a section itself is not read.

One evaluator, the form's

The form renderer gates a section-divider row with resolveFieldRuleState (@object-ui/core) under usePredicateScope() (@object-ui/react). record:details makes the same call with the same three inputs. Both modules are existing peer dependencies of @object-ui/plugin-detail: no plugin-form dependency and no copy of the evaluator.

  • record: every declared field seeded to null, then the defined values of the bound record, as the form's ruleRecord is built. A relation is collapsed to its stored id (toPredicateRecord), because the edit form reads its record without $expand and this page's record may arrive expanded.
  • previous: the same row. An edit form binds previous to the persisted row, and the row this page shows is the persisted row.
  • scope: the host predicate scope (current_user, features, ...).

A predicate that cannot be evaluated SHOWS the section and warns once. That is the form's direction (VISIBLE_WHEN_FAULTED, ADR-0137 D3), taken from the shared evaluator rather than restated.

Consumers of the synthesized sections, and what each does now

consumer path after this PR
console default record page RecordDetailView builds synthParts.sections from deriveFieldGroupDetailSections(objectDef) and passes them into buildDefaultPageSchema changed: writes { group: KEY, showBorder: true } per group; RecordContextProvider gets objectSchema={objectDef}, so the reference resolves
console page-assignment probe RecordDetailView's early buildDefaultPageSchema(objectDef, slots) no code change; it only decides whether a page exists
Studio page create metadata-admin/anchors.ts createSeed stores buildDefaultPageSchema(objectDef).regions no code change; the seed now holds { group } references and parses as RecordDetailsProps
Studio slotted-page preview metadata-admin/previews/PagePreview.tsx calls buildDefaultPageSchema(recordSchema, { slots }) no code change; its RecordContextProvider gets objectSchema={recordSchema}, so the references resolve
authored { group } reference record:details resolves it through deriveFieldGroupDetailEntries now gated by the group's visibleWhen
deriveFieldGroupDetailSections callers public export output unchanged from before this card

Pins that cover them: RecordDetailView.sectionHeadingsRenderPath-6190 (mounts RecordDetailView), anchors.page-seed, PagePreview, defaultFieldGroupsPage.sectionHeadings (real synthesized page through the real registry), and the two new files below.

Published surface (Clause-②)

The emitted shape changes for grouped objects: BREAKING for code that reads the synthesized sections (the changeset is minor, per AGENTS.md §9).

  • buildDefaultPageSchema, buildDefaultTabs, buildDefaultDetails and resolveDetailSections used to emit each group as an enumerated copy: name, label, icon, description, collapsible, defaultCollapsed, columns, and fields as rich { name, label, type, ... } descriptors. They now emit { group: KEY, columns }. The ungrouped section's fields are bare names. An explicit sections option is still returned unchanged.
  • record:details gates a { group } reference with the group's visibleWhen.
  • RecordDetailView (@object-ui/app-shell) writes { group: KEY, showBorder: true } per group.
  • Not added: a type member, zod member, registered inputs entry, prop or i18n key. deriveFieldGroupDetailEntries is exported from its module for the renderer and is not re-exported from the package entry.

Migration (also in the changeset): read a group's heading or members via deriveFieldGroupDetailSections(def) instead of off a synthesized section. Replace a stored section copied from an older seed ({ name: 'pro', label: ..., fields: [...] }) with { group: 'pro' }; only the reference form inherits the group's visibleWhen.

Spec legality, measured (installed @objectstack/spec 17.6.0, RecordDetailsProps.safeParse):

input verdict
buildDefaultDetails(def).properties for a grouped object OK
the record:details node inside buildDefaultPageSchema(def) OK
control: an enumerated section plus visibleWhen unrecognized_keys @ sections.0 [visibleWhen]
control: an enumerated section with a field object invalid_type @ sections.0.fields.0

This also retires the round-0 finding that the synthesized grouped sections were refused at every field object.

Tests

Gates on 75c794c (route B, after merging origin/main at 0abd4f9), all under the shared verify lock, from the repo root:

  • Targeted, six files (the two new files, buildDefaultPageSchema.test.ts, defaultFieldGroupsPage.sectionHeadings, recordDetailsInputs.spec-parity, buildDefaultPageSchema.strictPayload): Test Files 6 passed (6), Tests 147 passed (147).
  • pnpm exec vitest run packages/plugin-detail/: Test Files 238 passed | 1 skipped (239), Tests 2382 passed | 8 skipped (2390).
  • pnpm exec vitest run packages/app-shell/: Test Files 1001 passed | 1 skipped (1002), Tests 9951 passed | 9 skipped (9960).
  • turbo run build --filter='@object-ui/app-shell^...': exit 0. pnpm --filter @object-ui/plugin-detail type-check: exit 0. lint: exit 0 for both packages, 0 errors.
  • pnpm --filter @object-ui/app-shell type-check was red on 75c794c (TS2345: flatMap inferred its element type from the first return). Fixed in 594b154 and aeddcd7 by annotating the element type as unknown-valued records, a type-only change.

Rerun on final HEAD aeddcd7 under the lock: app-shell type-check exit 0; app-shell lint exit 0, 0 errors, with the warning count equal to 75c794c; the three app-shell consumer pins (RecordDetailView.sectionHeadingsRenderPath-6190, anchors.page-seed, PagePreview): Tests 15 passed (15); the two new files: Tests 18 passed (18). The full plugin-detail and app-shell suites ran on 75c794c, whose runtime code aeddcd7 does not change. Unlocked root checks on 594b154, each exit 0: check:new-line-citations (0 new), check:control-bytes, check:changeset-claims, check:pending-changeset-literals, check:test-path-roots, check:phantom-deps, check:installed-pin-claims, check:esm-specifiers, check:spec-symbols, scripts/check-changeset-presence.mjs. On aeddcd7, check:new-line-citations, check:control-bytes, check:changeset-claims, check:test-path-roots, check:phantom-deps and the changeset presence check were rerun, exit 0 each.

renderers/__tests__/record-details.groupVisibleWhen-11630.test.tsx renders every case twice on the same row: through record:details, fed by buildDefaultDetails (now { group } references), and through the entry form renderer (@object-ui/components form), given the section-divider row projectSectionDivider emits for a group, as an edit form of that row. Each leg asserts the form's verdict, the detail page's verdict and the expected verdict on [basic, pro]:

predicate expected
envelope record.kind == 'pro' (the card) hidden, shown
bare string, same source hidden, shown
no predicate (control) shown, shown
bare identifier kind == 'pro' (unbound, so it faults) shown, shown
parse error shown, shown
record.tier == 'gold', a declared field no row carries hidden, hidden
record.owner == 'u2', a lookup that arrives expanded here hidden, shown
previous.kind == 'pro' hidden, shown
'sales_manager' in current_user.positions, denied then allowed hidden, shown

Further legs:

  • An authored { group: 'pro' } reference: hidden, shown, on both spellings.
  • The runtime default page's { group, showBorder } shape: hidden, shown.
  • An enumerated section carrying a spec-refused visibleWhen: shown, shown, because it is not read.

Every render reads an ungated anchor first and throws HARNESS DEAD before any verdict. synth/__tests__/buildDefaultPageSchema.groupVisibleWhen-11630.test.ts pins the emitted references, the absence of visibleWhen and field objects on every section the page writes, the two-control legality reading above, and the predicate travelling beside the resolved section.

Ablation on route B (one-shot, on 75c794c)

Each mutation went through ablation-replace.mjs: an anchor that must hit once, a blob change verified on disk, and a restore proven by blob equal to HEAD with an empty git diff HEAD. Afterwards the diff against HEAD was 0 bytes and the two new files passed again (18/18). The red direction was predicted before running.

mutation predicted observed
drop the gate in resolveSectionGroup 8 gated legs red; 4 controls and 6 synth legs green 8 failed, 10 passed
emit the old enumerated copy instead of { group } 3 synth shape and legality legs, plus the 6 gated matrix legs fed by buildDefaultDetails, red 9 failed, 9 passed
read visibleWhen off an enumerated section only the enumerated-not-read leg red 1 failed, 17 passed
bind previous to nothing only the previous leg red 1 failed, 11 passed
skip the relation collapse only the lookup leg red 1 failed, 11 passed
skip the null seed only the declared-but-absent leg red 1 failed, 11 passed

No HARNESS DEAD or partial-group error appeared in any ablation run.

NOT MEASURED: live before/after on a running backend. Reason: the shared objectstack checkout has no built CLI (packages/cli/dist is absent). A backend needs a fresh objectstack worktree plus a full app-showcase closure build, and that build goes through the shared verify lock, which stayed saturated this session. The console path is covered in two halves, not by a live run. The RecordDetailView mount pin shows that its references resolve and the group headings render. The runtime-default-page leg shows that the same { group, showBorder } shape is gated.

Acceptance notes

  • The predicate is evaluated against the bound, persisted record, not an unsaved inline-edit draft.
  • A page stored from an older Studio seed keeps its enumerated copy of a group and is not gated. The changeset's migration line covers it.
  • RecordDetailView's ungrouped primary and "More details" sections still carry rich field descriptors. That page is synthesized at runtime and never stored or parsed, and route B does not need them changed.
  • The spec's deriveFieldGroupLayout TSDoc calls the section gate fail-closed. The form, and now the detail page, fail open on a faulted predicate. This PR keeps parity with the form; the seat carries that question to the maintainer.

Attribution: implemented in session https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL.


Generated by Claude Code

claude added 4 commits October 5, 2026 03:17
…ils section (objectui#11630)

deriveFieldGroupDetailSections now carries each group's visibleWhen verbatim
and unevaluated, and record:details evaluates it per record with the entry
form's own evaluator (resolveFieldRuleState + usePredicateScope), dropping a
section whose verdict is FALSE. An authored { group } reference inherits the
predicate the same way.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…he form's verdicts (objectui#11630)

A parity matrix renders each predicate case through record:details and through
the entry form's own section gate on the same row, and the synthesizer's
carriage is pinned on both spellings. Adds the changeset.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…tail-group-visible-when

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
… (objectui#11630)

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

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

claude added 2 commits October 5, 2026 04:52
…'s { group } reference (objectui#11630)

Patch round 1, route B (seat ruling on objectui#11630). The synthesized
record:details body writes each declared field group as the spec's own
{ group: KEY, columns } reference and the ungrouped bucket as bare field
names, so the node parses as RecordDetailsProps. The console's runtime
default page writes { group: KEY, showBorder: true }. record:details resolves
the reference and gates it with the group's visibleWhen, which now travels
beside the resolved section (deriveFieldGroupDetailEntries, package-internal)
rather than on any section; visibleWhen on an enumerated section is no longer
read. deriveFieldGroupDetailSections returns exactly what it returned before
this card.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
…tail-group-visible-when

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

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Console Performance Budget — not measured

This run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget.

This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.

Step Outcome
Build packages failure
Check console performance budget skipped

See the workflow run for details.

No package size report: it is only generated from a complete package build, so a partial one is never shown.

@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

…d records (objectui#11630)

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

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

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

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

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

Size Limits

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

@objectstack-fleet objectstack-fleet Bot changed the title fix(plugin-detail): a field group's visibleWhen gates its record:details section (objectui#11630) fix(plugin-detail,app-shell): a field group's visibleWhen gates its record:details section through the spec's { group } reference (objectui#11630) Oct 5, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

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

Isolated review of PR objectui#11641 for card objectui#11630, read-only: the card body and its six comments (triage 5986853410, claim 5987402622, dev reports 5988206775 and 5988831078, seat ruling 5988275811, ACCEPT 5988848394), the PR body and file list (8 files, +781/-57), the net diff against main at the merge-base 0abd4f9, the installed contract @objectstack/spec 17.6.0 (dist/ui/index.d.ts and dist/data/index.d.ts of the npm tarball), and the 43 check-runs on the head. Published surface read off each touched package's exports map: @object-ui/plugin-detail and @object-ui/app-shell each expose only . (dist/index.*); neither package.json is touched. No governed-surface path is in the file list.

Check-runs on the head: 43 runs, 40 success, 3 skipped (dependabot and the two coverage-matrix placeholders), 0 failure. Type Check, Lint, Test, Test (shard 1/8 … 8/8), Test (dist pins), Build & E2E, Changeset Bump Policy, Changeset Declaration, Changeset Claim Re-read, Changeset Fixed Group Check, Spec Main Shape Gate, Line Citation Gate, Governed Surface Queue Guard all success. The dev's deviation that the full suites ran on 75c794c is answered by these: the shards and the type-check are green on aeddcd7 itself.

① Derived judgments

  1. resolveDetailSections, buildDefaultDetails, buildDefaultTabs, buildDefaultPageSchema (@object-ui/plugin-detail, re-exported from src/index.tsx, so published) — emitted record:details properties.sections[] for an object with fieldGroups changes. Each declared group is now { group: KEY, columns }; the trailing ungrouped bucket is { columns, fields: string[] } (bare names, via the new module-private fieldGroupSectionReferences). Before, every group was an enumerated copy with rich { name, label, type, … } field descriptors. RIGHT: the installed RecordDetailsProps.sections[] is z.core.$strict and declares group, columns, fields: z.array(z.string()), and no visibleWhen; sectionGroupReferenceRefinement refuses name/label/description/collapsible/collapsed/visibleWhen beside group and permits columns, so the emitted reference is the spec's own ADR-0085 §5 form. This also retires the pre-existing off-spec rich descriptors (the dev's round-0 class-b finding). An explicit options.sections is returned unchanged (first branch of resolveDetailSections). The change is BREAKING for a reader of the synthesized sections and is named as such in the changeset with a FROM → TO line.

  2. deriveFieldGroupDetailSections (published) — output unchanged. It now maps deriveFieldGroupDetailEntries(def) to each entry's section (null when the derivation returns null), and section is the identical spread the function returned before (name/label, icon, description, collapsible, defaultCollapsed, columns, fields: s.fields.map(toField)). No visibleWhen on it. RIGHT; the changeset's "Unchanged" bullet is true.

  3. deriveFieldGroupDetailEntries and FieldGroupDetailEntry — new exports of the synth module, not of the package. src/index.tsx re-exports thirteen synth names and three types; neither new name is among them (verified at the head), and the exports map makes dist/index.* the only addressable entry. Per the contract-review rule, a .d.ts unreachable from the entry and not addressable through exports is shipped bytes, not the published accept set. RIGHT. The predicate rides on the entry (visibleWhen?: unknown) beside section, never on the section, so no public caller can obtain an enumerated section carrying the spec-refused key.

  4. record:details (registered block; RecordDetailsRenderer published) — a { group } reference is now dropped, heading and members, when the group's visibleWhen is FALSE for the bound record. The gate sits in resolveSectionGroup, keyed off groupEntryByKey built from deriveFieldGroupDetailEntries(objSchema); entry.visibleWhen == null short-circuits, so an ungated page renders exactly as before. The evaluator is resolveFieldRuleState({ visibleWhen }, record, {}, record, scope, where).visible from @object-ui/core with usePredicateScope() from @object-ui/react: the same function and hook packages/components form.tsx calls, with arguments in the signature's order (rules, record, statics, previous, scope, fieldContext). sectionPredicateRecord mirrors the form's ruleRecord (every declared field seeded null, defined values overlaid) and adds toPredicateRecord so an expanded relation compares as its stored id. Fail-open on a faulted predicate is inherited from VISIBLE_WHEN_FAULTED, not restated. RIGHT: this is the card's "Done when" (the detail page evaluates visibleWhen per group exactly as the form does), with one evaluator as triage directed. The renderer's accept set is unchanged: no key added or removed, and visibleWhen written on an enumerated section is NOT read, which matches the spec refusing it there (declared = enforced in both directions). Both packages the renderer now imports from (@object-ui/core, @object-ui/react) are existing peerDependencies of @object-ui/plugin-detail; no plugin-form dependency and no second evaluator.

  5. ComponentRegistry.register('details', …) inputs[].sections description (prose on the registration) — now says a { group } reference inherits the group's visibleWhen and that one written on a section itself is refused by the spec and not read. RIGHT and true of the diff; no registered input is added or removed.

  6. RecordDetailView (@object-ui/app-shell, exported from the . entry) — the console default page writes { group: sec.name, showBorder: true } per declared group instead of an enumerated copy with a pre-resolved label. RIGHT and measurably needed, as the seat's amendment allowed: an enumerated copy cannot carry the predicate under this spec. The heading's i18n re-resolution moves to the renderer, which already applies sectionLabel(objectName, s.name, …) to every named section in filteredSections, so the convention is kept. The ungrouped primary and "More details" sections are untouched (still rich descriptors; runtime-only, never stored or parsed; the dev names this as out of scope and the PM carries it). The two trailing commits (594b154, aeddcd7) are a type-only annotation on this flatMap.

  7. Every consumer of the synthesized sections stays inside a record context that carries objectSchema. I enumerated the non-test consumers at the head independently: RecordDetailView (the page-assignment probe buildDefaultPageSchema(objectDef, { slots }), the composed default page buildDefaultPageSchema(objectDef, { sections: synthParts.sections, … }), and deriveFieldGroupDetailSections(objectDef)), metadata-admin/anchors.ts createSeed (stores buildDefaultPageSchema(objectDef).regions), and metadata-admin/previews/PagePreview.tsx (buildDefaultPageSchema(recordSchema ?? undefined, { slots })); packages/react usePageAssignment.ts and packages/types public-blocks.zod.ts mention the function in comments only. That list equals the PR body's table. RecordDetailView renders the composed page, and any assigned (Studio-seeded) page, under its one RecordContextProvider with objectSchema={objectDef}; PagePreview passes objectSchema={recordSchema ?? undefined}. So the { group } references the seed now stores resolve on the same path authored { group } references already resolved on before this card. RIGHT.

  8. Spec legality of the emitted node is pinned on the installed contract in synth/__tests__/buildDefaultPageSchema.groupVisibleWhen-11630.test.ts with two refused controls (an enumerated section plus visibleWhen; an enumerated section with a field object). My reading of the 17.6.0 .d.ts agrees with both controls. RIGHT.

  9. Type surface, noted, not a failure. BuildPageOptions['sections'], the declared return type of resolveDetailSections, is an array of objects with only the optional members label, columns and fields and does not name group; the emitted value now carries it. This is pre-existing looseness of an input-override type (the changeset states no type member is added, which is accurate), Type Check is green, and the consumer-facing declared type, @object-ui/types RecordDetailsComponentProps.sections[], already declares group and fields?: string[]. Follow-up for the PM to carry, not a blocker: name group on BuildPageOptions.sections so the synth's own type describes what it returns.

  10. Behaviour the diff does not change, confirmed: hideEmpty default and showBorder derivation on a resolved reference ({ ...entry.section, ...authored } then the existing filteredSections map); the unresolved-group report path; the pre-route-B stored seed (an enumerated copy stays ungated; the changeset's second migration line covers it, and under this spec that is the only possible answer, since an enumerated section cannot carry the predicate).

② Semver level

  • Changeset .changeset/11630-detail-group-visible-when.md: '@object-ui/plugin-detail': minor, '@object-ui/app-shell': minor. Both packages are in the fixed group of .changeset/config.json; seven published src/ files across the two change, so a changeset is owed and present (Changeset Declaration, Changeset Bump Policy, Changeset Fixed Group Check, Changeset Claim Re-read all success).
  • Clause-②: line: the PR body carries Clause-②: yes on its own line, matching the dispatch's declaration and the seat ruling's "stays declared". yes takes at least minor; minor is declared. The level is right for what the diff publishes: a BREAKING output-shape change of four published functions plus a rendering behaviour change, graded minor because objectui's major follows the @objectstack family major and its own breaking changes ship as minor with the breaking semantics stated in the body. The changeset states BREAKING, names the four functions, the old and new shapes, the record:details gate, the RecordDetailView change, the registration prose, the "Unchanged" surface, and two FROM → TO migration lines. Consistent with the diff sentence by sentence.
  • Observation, not a defect: the changeset's own line reads Clause-②: yes (output shape change); the parenthetical is descriptive and is not an arm from the closed (widening)/(narrowing) pair. The declaration of record is the PR body's, which is bare yes, and no gate reads an arm here. No model identifier appears in the changeset, the PR body or the record.
  • skip-changeset is not claimed and would be wrong here. No major.

③ Boundary flags

  • Round-0 open_questions[0] (contract convergence A / B / C) — answered by the seat ruling 5988275811: route B. The head implements B as ruled: the synthesizer emits the spec-legal { group } reference, the renderer gates only through the group entry, and it does not read visibleWhen off an enumerated section (the parity matrix's "enumerated, spec-refused visibleWhen" leg pins shown/shown). Answered, closed.
  • Round-1 open_questions[0] (fault direction) — the spec's deriveFieldGroupLayout TSDoc says evaluation "FALSE hides the whole group, header included, fail-closed", while ADR-0137 D3, the objectui form and now the detail page fail OPEN on a predicate that cannot be evaluated. Not decided here, and not a blocker on this card: the card's acceptance criterion is parity with the form, which this head delivers through one shared evaluator, so whichever way the maintainer rules, one constant moves both surfaces. Escalated to the maintainer as the seat and the ACCEPT already carry it; a spec-text change is its own card in the spec lane.
  • Deviation: full suites on 75c794c, not aeddcd7 — answered by the head's check-runs (all test shards, Type Check, Lint, Build & E2E green on aeddcd7).
  • Deviation: plugin-detail/src/index.tsx registration prose edited — inside the amended file surface, named in the changeset, judged right in ① item 5.
  • Deviation: docs (objectui commandment Add automated testing infrastructure and CI/CD workflows #2) not updated — I checked the only published mentions at the head: content/docs/guide/slotted-pages.md lists the sub-builders without describing a section's shape, and the plugin-detail README has no fieldGroups or synthesized-section-shape statement, so nothing published became false. A prose surface outside the contract-review faces; the PM carries a docs addition. Escalated, non-blocking.
  • Deviations: model-free commit trailers; locked gate scripts run in the background — process notes with no contract bearing.
  • NOT MEASURED: live before/after on a running backend — reason stated (no built CLI in the shared checkout; verify lock saturated), the dispatch allowed it, and the parity matrix plus the three app-shell consumer mount pins and CI stand in for it. Acknowledged; not a contract item.
  • Out-of-scope findings (ungrouped RecordDetailView sections still rich descriptors; pre-route-B seeds ungated) — noted by the dev, carried by the PM, each consistent with ① items 6 and 10; the round-0 class-b finding is retired by this head.
  • Reviewer's own flag (① item 9): BuildPageOptions.sections does not name group. Carried to the PM as a follow-up, not a blocker.

Implemented-by: claude/issue-11630-detail-group-visible-when
Reviewed-by: session_015W8GBu6sBiqus2L2xjMsAL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

needs:contract-review removed: a conforming PASS is on record for this head. The record is 5989046218, with Served-tier: CONTRACT_REVIEW_TIER and Head-sha aeddcd7389b2b9661d3430029fbfc1ba49354d5b, which is the PR's current head. All 43 check runs on it are green (40 success, 3 skipped). Next in this stroke: ready, then auto-merge into the merge queue.

The record's non-blocking items are carried by the seat: BuildPageOptions.sections does not name group; no docs page describes the field-group section shape; and the fault direction of section predicates (spec TSDoc "fail-closed" against ADR-0137 D3) goes to the maintainer.

domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T06:07Z.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 06:09
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 06:09
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit 76993f8 Oct 5, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-11630-detail-group-visible-when branch October 5, 2026 06:26
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.

console(detail): a field group's visibleWhen gates the form but not the record detail page — the section renders whatever the predicate says

2 participants