Skip to content
This repository was archived by the owner on Oct 26, 2026. It is now read-only.

fix(components): a slotted page template draws only the regions it places, and full-width retires (objectui#12476) - #12901

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12476-page-template-slots
Oct 17, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-12476-page-template-slots

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Fixes #12476
Clause-②: no (narrowing)

What changes

packages/components/src/renderers/layout/page.tsx, against @objectstack/spec 18.0.0-next.3 (PAGE_TEMPLATE_SLOTS, checkPageRegionPlacement):

  • Slotted templates draw only what they place. On default, header-sidebar-main and three-column, a region the template does not place is no longer stacked under main. It is drawn nowhere and never moved into another slot. Where it used to stack, the page shows a refusal (data-page-refusal="unplaced-region") naming the region and the template's slots.
  • full-width retires. TEMPLATE_REGISTRY is now keyed by the spec's PageTemplate, so a template the spec adds or retires fails to compile. FullWidthTemplate is renamed DefaultTemplate. A template name the registry does not hold draws a refusal (data-page-refusal="unregistered-template") instead of silently taking the pageType layout.
  • The spec floor moves. @object-ui/components now reads the spec's PAGE_TEMPLATE_SLOTS, which @objectstack/spec 18.0.0-next.1 does not carry, so its declared range moves from ^18.0.0-next.1 to ^18.0.0-next.2 (the lockfile's one specifier line moves with it; the resolved version stays 18.0.0-next.3).
  • A pending changeset is corrected. .changeset/23610-record-page-seed-default.md no longer says the renderer recognises full-width: a stored full-width row keeps rendering because the platform rewrites it to default on read. Three comments that named FullWidthTemplate or the old stacking are corrected too.
  • Unchanged: dashboard's open grid; the 12-column block grid in each placed region (objectui#12624); the narrow-region compact list (objectui#12703); the pageType dispatch (no template), which still stacks.

Why a visible refusal, not drawing nothing

The spec migration rewrites only full-width to default on read. A stored row with an unplaced region is served unchanged, so it does reach the renderer. Drawing nothing would silently lose content. Stacking is the mis-layout the ruling retires. A visible refusal tells the author what to rename.

Callers read (H3)

  • The synthesized record page (plugin-detail buildDefaultPageSchema, template: 'default', regions main and aside only) and approval pages: unaffected.
  • PageView and PagePreview spread the raw stored row with no parse: they get the new behaviour for slotted templates.
  • Preview samples and examples carry no template: pageType path, unchanged.
  • No in-repo producer emits full-width.

Tests

  • New page-template-slots-12476.test.tsx: 16 tests. 7 pin the refusals, 9 are controls.
  • Ablation: with page.tsx reverted to the main blob, 7 failed and 9 passed. Restored by HEAD blob hash, 16/16.
  • Moved pin: layout-default-jsdoc-7361 row 7 now reads DefaultTemplate (rename only).
  • No pin asserted that an unplaced region is stacked.
  • Families naming the page renderer: 66 files, 1447 tests green.
  • @object-ui/components test: 4036 green.
  • Live reading: NOT MEASURED. Reason: it needs the console dev server and a seeded preview route.

Acceptance notes

  • Studio's "Add region" (PageBlockCanvas addRegion) writes region_N, which the save door refuses on a default or template-less page. Reported to the seat, not fixed here.
  • The template-less pageType path still stacks, although the spec judges such a page as default. Left as is per the claim's surface.
  • The objectstack migration's "renders as before" sentence no longer holds for unplaced regions.

Provenance: built by the cloud session session_01KduCFcSEActo1Znrb6DLqw (claim 6235363143) and opened by the domain:ui seat 1 PM (session_011K9g23W67dy2zwnyzaoB6o) from that session's final message, because a cloud session cannot open a PR from its container. The seat replaced the dev's footer line with this paragraph. After the first head (572fd84) failed its contract review (6236121598), the dev reworked it to fd893dd, and the seat added the two bullets above on the floor and the corrected changeset (body refreshed 2026-10-17T16:31Z, after the contract reviews 6236121598 and 6236677561); nothing else changed.


Generated by Claude Code

…aces, and full-width retires (objectui#12476)

The default, header-sidebar-main and three-column templates no longer
stack a region they do not place under main; the page shows a refusal
naming the region and the template's slots. The template registry is
keyed by the spec's PAGE_TEMPLATE_SLOTS and drops full-width; an
unregistered template draws a refusal instead of the pageType layout.
dashboard keeps its open grid; the pageType dispatch is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KduCFcSEActo1Znrb6DLqw
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3152.6 KB 3744.1 KB
Main entry chunk (gzip) 80.4 KB 350 KB
Entry file index-CHXIRxDs.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.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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) 610.30KB 148.25KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.60KB 74.45KB
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) 41.33KB 11.96KB
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) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 265.18KB 70.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 272.78KB 75.66KB
plugin-kanban (index.js) 62.90KB 19.39KB
plugin-list (index.js) 139.20KB 35.82KB
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.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
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.23KB 39.46KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 572fd84958050b4529c3f4b2fa831c94197db00c
Local-runs: none
Scope: packages/components/src/renderers/layout/page.tsx, packages/components/src/tests/page-template-slots-12476.test.tsx, packages/types/src/tests/layout-default-jsdoc-7361.test.ts + .changeset/12476-page-template-slots.md (prose face, judged under ②), packages/components/package.json, pnpm-lock.yaml (the packages/components importer), packages/components/vite.config.ts, scripts/check-spec-range-floors.mjs with .github/workflows/spec-range-floors.yml and the root changeset:publish script, .changeset/23610-record-page-seed-default.md, packages/types/src/layout.ts, packages/types/src/zod/layout.zod.ts, packages/plugin-detail/src/synth/buildDefaultPageSchema.ts, packages/app-shell/src/views/PageView.tsx, packages/app-shell/src/views/metadata-admin/previews/PagePreview.tsx, packages/app-shell/src/views/metadata-admin/anchors.ts; @objectstack/spec at 18.0.0-next.3 (packages/spec/src/ui/page.zod.ts, the semantic migration 18.page-template-closed-region-unplaced-refused, the conversion page-template-full-width-to-default) and page.zod.ts at 18.0.0-next.1 and 18.0.0-next.2 for the floor

Read against the card #12476 (body and all six comments), the ruling objectstack#23610 comment 6190536588 (items 2 and 5), the PR body, the net diff from merge-base d2b21a1225, and the 36 check-runs on this head.

① Derived judgments

  • Slotted templates draw only what they place: RIGHT. On default, header-sidebar-main and three-column, a region outside PAGE_TEMPLATE_SLOTS[template] is no longer stacked under main. It is drawn nowhere, and the data-page-refusal="unplaced-region" panel in the main column names the region and the slots. The accept set now equals the spec's checkPageRegionPlacement for kind: 'full' pages: the same slot table is imported, and names match case-insensitively on both sides. This is what ruling items 2 and 5 bind, and the card's "the renderer keeps no fallback for a shape the spec refuses". A visible refusal is better than a silent drop: the semantic migration at next.3 serves a stored row with an unplaced region "unchanged", so real rows reach this branch.
  • dashboard keeps its open grid: RIGHT. getRemainingRegions(regions, PAGE_TEMPLATE_SLOTS.dashboard) excludes header and footer exactly as the old literal did, so this template's accept set is unchanged. A control test pins it.
  • full-width leaves TEMPLATE_REGISTRY, and FullWidthTemplate becomes DefaultTemplate: RIGHT. Both names pointed to one component. The ruling allows no alias window. The spec enum refuses full-width, and the data-at-rest conversion page-template-full-width-to-default rewrites a stored row to default on read, so a served row keeps its layout. Neither name was exported, because the module's only export is PageRenderer, so the rename does not change the public surface.
  • An unregistered template draws UnregisteredTemplateRefusal instead of the pageType layout: RIGHT. The spec enum refuses every other name at the parse. The own-property lookup also fixes a latent crash at the merge-base: there, template: 'constructor' resolved the inherited Object constructor as a layout component. A test now pins that case.
  • The registry is a Record keyed by the spec's PageTemplate: RIGHT. A missing or extra key is a type error, so the renderer's set cannot drift from the spec without failing to compile. Type Check is green on this head.
  • The pageType dispatch (no template) still stacks: RIGHT for this claim, with a residual. The claim scoped this path out, and the dev measured real producers on it: PageView and PagePreview spread raw rows, and Studio's addRegion writes region_N. The renderer is still more permissive here than the spec, whose check judges an absent template as default. That residual is carried to ③.
  • Residual: one shape is refused beyond the spec, not a defect. The spec does not judge region names on a kind: 'slotted' page. If such a raw row with a region its template does not place reached PageRenderer, the renderer would now refuse it. No producer was found. Slotted pages carry regions: [] and render through buildDefaultPageSchema (RecordDetailView, PagePreview), and the served approval page has the same shape.
  • In-repo producers are unaffected: RIGHT, as the PR body says. buildDefaultPageSchema is the only code that sets template. It always writes default, with main and an optional aside.
  • New DOM hook data-page-refusal, with English refusal text and no i18n key: RIGHT. It follows the html tier's compile-error panel, the existing author-facing refusal on this page. No export, prop, type or language-pack key changes.
  • WRONG: the @objectstack/spec floor of @object-ui/components. The head adds a runtime import of PAGE_TEMPLATE_SLOTS from @objectstack/spec/ui and evaluates it at module scope (TEMPLATE_NAMES). That symbol is absent from page.zod.ts at the @objectstack/spec@18.0.0-next.1 tag and present from next.2. The package still declares "@objectstack/spec": "^18.0.0-next.1".
    • The build keeps spec external and inlines the package into one bundle. Under a spec at the declared floor, the whole of @object-ui/components therefore fails to load, not just the page renderer.
    • objectui's floor rule (objectui#5793, scripts/check-spec-range-floors.mjs) reports this as floor-too-low. Its blocking copy runs inside pnpm changeset:publish, so landing this head cancels the next release of the fixed group.
    • The gate is deliberately not a pull-request job, which is why every check on this head is green.
    • The package's other runtime spec imports (resolveI18nLabel, isFileIdToken) exist at next.1, so this head is what introduces the violation.
    • Precedent: PR deps(v18): resolve @objectstack/* 18.0.0-next.2 and follow its contract (objectui#12626) #12648 (objectui#12626) raised @object-ui/types to ^18.0.0-next.2 in the same PR, for the same reason (checkPageRegionPlacement).
    • The seat's card note recorded this floor and left it unchanged. I reverse that judgment.

② Semver level

  • '@object-ui/components': minor: RIGHT. The narrowing is breaking. objectui's fixed group never declares major (check-changeset-no-major.mjs), so a breaking change is a minor that states its breaking semantics in the body, and this body does: what used to stack, what draws now, that full-width no longer resolves, and what is unchanged. No other package publishes from this diff, because the @object-ui/types change is a test file.
  • The Clause-② declaration no (narrowing), on the PR body and the changeset: RIGHT. No published schema or type changes. PageNodeSchema.template is already SpecPage['template'], which is closed at next.3, regions[].name stays string, and nothing under packages/types/src moves. The narrowing is in what a published component draws, and that face is what this record covers.
  • The changeset prose: RIGHT at this head. I checked each sentence against the diff and the spec at next.3, and each is true. It owes one sentence once the floor moves: that @object-ui/components now needs @objectstack/spec 18.0.0-next.2 or later. Recommended, not required: spell out the renames (full-width → default; left_sidebar → sidebar, right_sidebar → aside, content → main).
  • WRONG: a pending changeset this head makes false. .changeset/23610-record-page-seed-default.md is still pending (pre mode has not consumed it), so it ships in the same release. It says "The renderer maps default and full-width to the same FullWidthTemplate" and "The renderer still recognises full-width, so pages already stored with it keep rendering". This head makes both false, so the @object-ui/plugin-detail and @object-ui/types changelog entries would contradict the @object-ui/components entry in the same release. The Changeset Claim Re-read gate does not catch it: its went-false reading looks for a pending body that names a file this change touches, and this body names symbols, not page.tsx.
  • Check-runs on this head: none failed. Of 36, 33 succeeded and 3 were skipped (the two coverage jobs and dependabot).

③ Boundary flags

  • Dev flag, the ruling comment was unreadable (403): answered. I read 6190536588. Item 2 is the closed template set, the declared slots, the refusal at validate and at save, and full-width retiring with no alias window. Item 5 names this card: it "retires the stack-the-rest branch for authored pages". Nothing in the ruling keeps a fallback.
  • Dev flag, H2 is partly falsified (stored rows with an unplaced region are not migrated): confirmed. The semantic migration's own text says such a row "is served unchanged and renders as before". That is why the refusal in open question 1 is right.
  • Dev flag, the live reading was not measured: accepted. The pins render through SchemaRenderer and the component registry, and Build & E2E is green, so no contract gap is left open.
  • Dev flag, gates were hand-derived and no CI was running yet: superseded. CI on this head is green.
  • Open question 1, a visible refusal (A): right. B (draw nothing) would silently hide live content that the migration leaves in place. C (stack) is the mis-layout the ruling retires.
  • Open question 2, a refusal for an unregistered template (A): right. The spec refuses the name at the parse. The pageType layout would draw a different layout from the one named.
  • Open question 3, the template-less path still stacks: escalated. A is right for this PR. What remains of "authored pages", and of the card's "keeps no fallback", is on the follow-up the seat records as objectui#12902 (comment 6235773759); I did not read that card. Fixes #12476 closes the card with that remainder still open, so the ACCEPT should name it.
  • Open question 4, the upstream migration sentence "renders as before" becomes false: escalated to objectstack. Its owner is objectstack's migration text. The seat's record says it files that card with the ACCEPT. It is owed at landing and does not block this head.
  • Out-of-scope finding, class c (Studio's addRegion writes region_N): escalated with open question 3 to objectui#12902. After this head, Studio's preview of a template: 'default' draft shows the refusal for the region "Add region" just wrote. The renderer now agrees with the save door; the button is the defect.
  • Out-of-scope finding, class b: same as open question 4.
  • Reviewer-raised, non-blocking: stale prose naming the old behaviour.
    • FullWidthTemplate is still named in the comment above template: 'default' in buildDefaultPageSchema.
    • It is also named in the PageNodeSchema.template docblock in packages/types/src/layout.ts. Row 7 of the moved pin guards that docblock: the pin moved and its prose did not.
    • The layout.zod.ts comment still says the refusal names "the region the renderer stacks under main".
    • Fix these with this round, or card them.

To pass on the next head:

  1. Raise @objectstack/spec in packages/components/package.json to ^18.0.0-next.2, and change the matching specifier of the packages/components importer in pnpm-lock.yaml. The alternative is to drop the runtime import.
  2. Correct the two sentences in .changeset/23610-record-page-seed-default.md that this head makes false, or have this PR's changeset say it supersedes them. Add the floor sentence to this PR's changeset.

The residuals above do not block. A new head needs a new record.

Implemented-by: claude/issue-12476-page-template-slots
Reviewed-by: session_011K9g23W67dy2zwnyzaoB6o

VERDICT: FAIL

…rect prose the full-width retirement made false (objectui#12476)

@object-ui/components reads PAGE_TEMPLATE_SLOTS, which the spec carries
from 18.0.0-next.2. The pending 23610 changeset, the buildDefaultPageSchema
seed comment, the PageNodeSchema.template docblock and the zod mirror's
placement comment no longer name FullWidthTemplate or the stack under main.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KduCFcSEActo1Znrb6DLqw
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 75 chunks) 3153.4 KB 3744.1 KB
Main entry chunk (gzip) 80.4 KB 350 KB
Entry file index-D000v3w_.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.42KB 7.85KB
app-shell (runtime-config.js) 23.60KB 8.19KB
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) 45.59KB 12.03KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 20.67KB 6.12KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.74KB 3.94KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.62KB 0.83KB
auth (authStyles.js) 5.27KB 1.86KB
auth (createAuthClient.js) 42.84KB 11.66KB
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) 610.30KB 148.25KB
core (index.js) 10.18KB 4.04KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 248.83KB 69.44KB
fields (index.js) 291.60KB 74.45KB
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) 41.33KB 11.96KB
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) 210.60KB 49.94KB
plugin-dashboard (index.js) 146.23KB 39.59KB
plugin-designer (index.js) 233.95KB 50.08KB
plugin-detail (index.js) 265.18KB 70.24KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 180.22KB 46.59KB
plugin-gantt (index.js) 178.36KB 45.08KB
plugin-grid (index.js) 272.78KB 75.66KB
plugin-kanban (index.js) 63.18KB 19.45KB
plugin-list (index.js) 139.20KB 35.82KB
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.27KB 5.40KB
plugin-view (index.js) 94.68KB 24.36KB
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.23KB 39.46KB
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

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fd893ddcb62e4c5473aeacd4e4bb23ed15de9c32
Local-runs: none
Scope: packages/components/src/renderers/layout/page.tsx, packages/components/src/tests/page-template-slots-12476.test.tsx, packages/components/package.json, pnpm-lock.yaml, packages/plugin-detail/src/synth/buildDefaultPageSchema.ts, packages/types/src/layout.ts, packages/types/src/zod/layout.zod.ts, packages/types/src/tests/layout-default-jsdoc-7361.test.ts + .changeset/12476-page-template-slots.md and .changeset/23610-record-page-seed-default.md (prose faces, judged under ②), .changeset/pre.json, packages/components/vite.config.ts, packages/types/package.json, packages/plugin-detail/package.json, scripts/check-spec-range-floors.mjs with .github/workflows/spec-range-floors.yml and the root changeset:publish script, scripts/check-changeset-presence.mjs with .github/workflows/changeset-presence.yml; @objectstack/spec packages/spec/src/ui/page.zod.ts and packages/spec/src/ui/index.ts at 18.0.0-next.1, 18.0.0-next.2 and 18.0.0-next.3, and at next.3 the semantic migration 18.page-template-closed-region-unplaced-refused and the conversion page-template-full-width-to-default

Read against card #12476 (its body and all six comments), the ruling on objectstack#23610 (comment 6190536588, items 2 and 5), the PR body, file list and comments (the earlier record 6236121598 on 572fd84 is an input here, not a verdict on this head), the net diff from merge-base f5009db72c, and the 38 check-runs on this head.

What moved since 572fd84: one merge of main (it touches none of this PR's paths, so page.tsx and the new test are byte-identical to the head the dev tested and ablated, blob c464da3), and fd893dd, which raises the spec floor, corrects the pending 23610 changeset and three comments, and adds the floor sentence to this PR's changeset.

① Derived judgments

  • On default, header-sidebar-main and three-column, a region the template does not place is no longer stacked under main: RIGHT.
    • It is drawn nowhere and never moved into another slot. The data-page-refusal="unplaced-region" panel sits where the stack used to be. It names each region and the template's slots.
    • Each template's placed set is PAGE_TEMPLATE_SLOTS[template], read from the spec. The match is case-insensitive, as in the spec's checkPageRegionPlacement. So for kind: 'full' pages the renderer's accept set equals the spec's.
    • This is what ruling item 2 binds, what item 5 assigns to this card, and what the card means by "the renderer keeps no fallback for a shape the spec refuses".
    • A refusal you can see is right, not a silent drop. The next.3 semantic migration serves a stored row with an unplaced region "unchanged", so real rows reach this branch.
  • dashboard keeps its open grid: RIGHT. The spec's slots for dashboard are exactly the header and footer the old code excluded, so its accept set does not change. A control test pins the cell order.
  • full-width leaves TEMPLATE_REGISTRY, and FullWidthTemplate is renamed DefaultTemplate: RIGHT.
    • Both names pointed at one component, so there was no fork to report.
    • The ruling allows no alias window. The spec enum refuses full-width, and the conversion page-template-full-width-to-default rewrites stored rows on read.
    • The module still exports only PageRenderer (at base and at head), so the rename does not change the public surface.
  • A template the registry does not hold now draws UnregisteredTemplateRefusal instead of the pageType layout: RIGHT.
    • The spec refuses that name at the parse.
    • The lookup now checks own properties only. That also closes the inherited-key path: template: 'constructor' used to resolve to Object. The test pins it.
  • The registry is a Record keyed by the spec's PageTemplate: RIGHT. A missing key, or an extra key in the literal, is a type error. The renderer's template set therefore cannot drift from the spec silently. Type Check is green on this head.
  • The @objectstack/spec floor of @object-ui/components: RIGHT at this head (it was WRONG on 572fd84).
    • The package now declares ^18.0.0-next.2 in dependencies. The packages/components importer specifier in pnpm-lock.yaml moved with it. The resolved version stays 18.0.0-next.3, and Lockfile Integrity and Lockfile Dedupe are green.
    • PAGE_TEMPLATE_SLOTS and PageTemplate are absent from page.zod.ts at the 18.0.0-next.1 tag. They are present at 18.0.0-next.2, with the same slot table as next.3, and ui/index.ts re-exports page.zod at both tags.
    • So the module-scope read of the table (TEMPLATE_NAMES) is safe at every version the range admits.
    • The floor scan runs only in changeset:publish and nightly, never as a PR job. On symbol presence it now has nothing to report for this import.
  • The template-less pageType dispatch still stacks: RIGHT for this claim, with a residual.
    • The claim scoped this path out, and the dev measured real producers on it: PageView, PagePreview and Studio's addRegion.
    • The spec judges a missing template as default, so on this path the renderer is still more permissive than the spec. That residual is carried to ③.
  • Residuals, beyond the spec's refusals, not defects.
    • The renderer would also refuse an unplaced region on a kind: 'slotted' row that reached PageRenderer with a template. The spec does not judge region names on that kind. No producer is known: slotted pages render through buildDefaultPageSchema.
    • A region with no name is refused on a slotted template. Both parse faces (the spec's PageRegionSchema and objectui's PageNodeRegionSchema) require name.
  • In-repo producers are unaffected: RIGHT. buildDefaultPageSchema is the only non-test code that sets template. It writes default, with regions main and an optional aside. A head-wide grep finds no full-width, left_sidebar or right_sidebar producer under packages, apps, examples, content or skills.
  • New DOM hook data-page-refusal, with English refusal text and no i18n key: RIGHT. It follows the html tier's compile-error panel on this page. No export, prop, type, pageMeta input or language-pack key changes.
  • Stale prose from 572fd84 is corrected: RIGHT, comment-only.
    • The buildDefaultPageSchema seed comment no longer names FullWidthTemplate.
    • The PageNodeSchema.template docblock in layout.ts now names DefaultTemplate. So the docblock agrees with the moved pin, which reads 'default': then DefaultTemplate from page.tsx.
    • The layout.zod.ts placement comment now gives both behaviours: a refusal under a slotted template, and stacking without a template.
    • A head-wide grep finds no FullWidthTemplate anywhere.
  • Tests: RIGHT.
    • The new file has 16 cases. 7 pin refusals: three templates × unplaced names, default with no placed region, and full-width / two-column / constructor. 9 are controls: each placed slot on its 12-column grid, case-insensitive names, the dashboard cells, and the four pageType pages that still stack.
    • The dev ablated against the same page.tsx blob, with the base's blob dad6a91 as the mutant: the 7 pins went red and the 9 controls stayed green. That evidence carries over to this head unchanged.

② Semver level

  • '@object-ui/components': minor: RIGHT.
    • The narrowing is breaking. objectui's fixed group never declares major (check-changeset-no-major.mjs; Changeset Bump Policy is green), so it is a minor whose body states the breaking semantics.
    • This body does: what used to stack and what draws now; that full-width no longer resolves and that unregistered names refuse; what is unchanged; the renames (full-width → default, left_sidebar → sidebar, right_sidebar → aside, content → main, or dashboard); and the new floor, @objectstack/spec 18.0.0-next.2 or later.
  • The pending .changeset/23610-record-page-seed-default.md (@object-ui/plugin-detail and @object-ui/types, both patch): RIGHT at this head (it was WRONG on 572fd84).
    • .changeset/pre.json lists no consumed changesets, so this file ships in the same release.
    • Its two sentences this PR made false are rewritten. Seeded pages are drawn by the component default still names. Stored full-width rows keep rendering through the platform's on-read conversion, and the renderer no longer recognises the name. Both are true.
    • The levels are unchanged. This PR's edits under packages/types/src and packages/plugin-detail/src are comments only, and this pending changeset already names both packages. Changeset Declaration is green.
  • The Clause-② declaration no (narrowing), on the PR body and the changeset: RIGHT.
    • No published schema or type changes. PageNodeSchema.template is already SpecPage['template'], closed at next.2 and next.3. regions[].name stays string. The @object-ui/types diff is comments and one test.
    • The narrowing is in what a published component draws, plus the dependency floor. That is this record's face, and the changeset states both.
  • Changeset prose: RIGHT. I checked each sentence of 12476-page-template-slots.md against the diff and against the spec at next.2 and next.3, and each is true.
  • Non-blocking, older than this head: two sentences of the 23610 changeset date from next.1. They are "Against today's spec, whose member is still string, the type is unchanged" and the zod bullet's "against today's spec it accepts exactly what it accepted before". The next.2 / next.3 bump made them stale, not this diff. Fix them while the file is open, or card them.
  • Check-runs on this head: none failed. Of 38, 35 succeeded and 3 were skipped (dependabot and the two coverage jobs). The changeset gates (Declaration, Claim Re-read, Bump Policy, Overwrite Report, Fixed Group Check), Type Check, Lint, Repo Gates, Line Citation Gate, Governed Surface Queue Guard, Build & E2E, Test (dist pins) and all 16 test shards are green.

③ Boundary flags

  • The prior record's two conditions to pass: met.
    • (1) The floor is raised in both package.json and the lockfile importer.
    • (2) The 23610 changeset sentences are corrected, and the floor sentence is added.
    • Its non-blocking stale-prose note is also closed (see ①).
  • Dev flag, the ruling comment was unreadable (403): answered. I read 6190536588.
    • Item 2 closes the template set and declares each template's slots. It refuses an unplaced region at validate and at save, and retires full-width with no alias window.
    • Item 5 names this card: it "retires the stack-the-rest branch for authored pages".
    • Nothing in the ruling keeps a fallback.
  • Dev flag, H2 partly falsified (stored rows with an unplaced region are not migrated): confirmed. The next.3 migration text says such a row "is served unchanged and renders as before". That is the reason for open question 1's refusal.
  • Dev flag, live reading not measured: accepted. The pins render through SchemaRenderer and the registry, and Build & E2E is green.
  • Dev flag, gates hand-derived with no CI yet: superseded. CI on this head is green, as listed in ②.
  • The surface grew beyond the claim's file list, through fd893dd: in bounds. The new paths are the floor in package.json and the lockfile, the 23610 changeset, and three comments in @object-ui/types / @object-ui/plugin-detail. Each one is what the prior record set as a pass condition or asked to "fix with this round". None of them changes behaviour.
  • Open question 1, a visible refusal for an unplaced region (A): right. B would silently hide live content the migration leaves in place. C is the mis-layout the ruling retires.
  • Open question 2, a refusal for an unregistered template (A): right. The spec refuses the name at the parse. The pageType layout would draw a different layout from the one named.
  • Open question 3, the template-less path still stacks: escalated, as on the prior head. A is right for this PR. The rest of "authored pages", and of the card's "keeps no fallback", belongs to the follow-up the seat records as objectui#12902 (card comment 6235773759). That card is outside this review's inputs and I did not read it. Fixes #12476 closes the card with that remainder still open, so the ACCEPT should name Studio's "Add region" writes region_N, a name the save door refuses on every template but dashboard; and a page that names no template still stacks such a region (objectstack#23610 Q2 A follow-up) #12902.
  • Open question 4, the upstream migration sentence goes false: escalated to objectstack, and widened.
    • The sentence in 18.page-template-closed-region-unplaced-refused covers a stored row with "an unregistered template or an unplaced region". This head changes both halves: an unregistered name now draws a refusal too, not the page-type layout.
    • The PR body and the dev's report name only the unplaced-region half. The objectstack card the seat files at the ACCEPT should correct both.
    • It is owed at landing and does not block this head.
  • Out-of-scope finding, class c (Studio's addRegion writes region_N): escalated with open question 3 to objectui#12902. After this head, Studio's preview of a template: 'default' draft refuses the region "Add region" just wrote. The renderer now agrees with the save door; the button is the defect.
  • Out-of-scope finding, class b: same as open question 4.
  • Reviewer-raised, non-blocking.
    • The PR body's "What changes" does not mention the floor raise or the 23610 changeset correction from fd893dd. Add a line before landing if the body becomes the squash message.
    • RegionLayout still lists its five slots as a literal rather than reading PAGE_TEMPLATE_SLOTS.default. The values are identical at next.2 and next.3, and its findRegion calls are literal anyway, so this is a tidy-up, not drift.

Implemented-by: claude/issue-12476-page-template-slots
Reviewed-by: session_011K9g23W67dy2zwnyzaoB6o

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:ui seat 1 · session_011K9g23W67dy2zwnyzaoB6o · 2026-10-17T16:33Z

What was reviewed:

  • Head fd893dd, against objectui#12476. The first head, 572fd84, failed its contract review (6236121598: the @objectstack/spec floor of @object-ui/components, and a pending changeset made false); the dev reworked it to fd893dd on the seat's rework message, which extended the claim's surface to exactly the files that review named.
  • The claim 6235363143, and the dev's report with the seat's answers on the card (6235773759).
  • Tier: the declared narrowing (Clause-②: no (narrowing), line 2 of the body and at the start of a line in the changeset) owes one CONTRACT_REVIEW_TIER review. It is the record 6236677561 on this PR, pinned to this head, VERDICT: PASS, scope every non-prose path of the diff plus the code it cites. This ACCEPT covers the remainder at TIER_DEFAULT: the changeset, the findings and the landing.
  1. Shape.
    • Fixes #12476 is the first line and the only closing keyword. The assignee is os-tesla.
    • 10 files, NOT governed (check-governed-merges --pr on this file list: 0 of 10 paths hit the register, 354 changed lines, under the human-merge threshold):
      • the claim's surface: page.tsx, one new test, one pin in @object-ui/types' test tree (a rename it reads), the @object-ui/components changeset;
      • the seat's extension after 6236121598: the @objectstack/spec floor in packages/components/package.json (^18.0.0-next.1 → ^18.0.0-next.2) and its one specifier line in pnpm-lock.yaml (the resolved version stays 18.0.0-next.3), two corrected sentences in the pending .changeset/23610-record-page-seed-default.md, and three comment-only hunks (buildDefaultPageSchema.ts, layout.ts, layout.zod.ts).
    • The merge 99c99a3 brings only main (f5009db). Nothing on its "Outside the surface" list moves: no spec edit, no pages.md, the pageType dispatch keeps stacking (pinned for record, home, app, utility), and the first-match findRegion is untouched.
  2. The change (read by the seat; judged at tier in 6236677561).
  3. The changeset (TIER_DEFAULT, read sentence by sentence against the diff).
    • @object-ui/components: minor, carrying Clause-②: no (narrowing) at the start of a line, spelled as line 2 of the body. It now also states the floor (@objectstack/spec 18.0.0-next.2 or later) and the renames the spec names.
    • .changeset/23610-record-page-seed-default.md (pending, same release) no longer says the renderer recognises full-width: a stored full-width row keeps rendering because the platform rewrites it to default on read.
    • Each sentence matches the diff: the three slotted templates refuse an unplaced region and do not re-place it; full-width no longer resolves and an unregistered template draws a refusal; the registry is keyed by the spec's set; the placed regions, dashboard and the template-less pageType path are unchanged; no export, prop, type or pack key moves.
    • Its sentence on stored rows ("served unchanged … this is what it shows until the region is renamed") matches the spec entry's own account of rows at rest, which is why the upstream text is filed below.
  4. Tests and evidence.
    • 16 new pins through the real SchemaRenderer: 7 refusals (each slotted template, a default page with no placed region, and full-width / two-column / constructor), 9 controls.
    • Ablation: page.tsx reverted to the main blob turns all 7 refusal pins red and leaves the 9 controls green; the restore matches the HEAD blob.
    • Families naming the page renderer: 66 files, 1447 tests; the @object-ui/components suite, 4036 tests (at 572fd84). On the reworked head fd893dd: the new pin, the moved pin and every test naming buildDefaultPageSchema or layout.zod, 68 files and 2471 tests, exit 0. Live reading: NOT MEASURED (it needs the console with a seeded preview route).
  5. Gates (check-runs pinned to this head).
    • 35 success and 3 skipped (coverage and dependabot, skipped by design). The two added runs, Lockfile Integrity Check and Lockfile Dedupe Check, run because the lockfile moved; both pass. Included are Lint, Type Check, Test with all 16 shards, Test (dist pins), Build & E2E, Bundle Analysis, Repo Gates, Governed Surface Queue Guard and every changeset check.
    • The console performance budget passes: eager closure 3153.4 KB against 3744.1 KB.
    • The floor rule itself (pnpm check:spec-floors, which runs inside changeset:publish, not on pull requests) exits 0 on the dev's full build at fd893dd, and exits 1 with floor-too-low for PAGE_TEMPLATE_SLOTS when ^18.0.0-next.1 is put back (the dev's control leg).
  6. Findings (out_of_scope_findings and Acceptance notes).
  7. Landing.
    • Ready, then auto-merge into the merge queue.
    • After the merge: verify by content, close the card through Fixes, strip pm:dispatched, and archive cloud session session_01KduCFcSEActo1Znrb6DLqw.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 17, 2026 16:34
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 17, 2026 16:34
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 17, 2026
Merged via the queue into main with commit 044f231 Oct 17, 2026
39 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-12476-page-template-slots branch October 17, 2026 16:49
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

page renderer: retire the stack-the-rest branch for authored regions once the spec declares each template slots (objectstack#23610 Q2 A follow-up)

2 participants