Repository navigation
Commit a69a40a
feat(spec)!: a page declares nothing the console does not honour — isDefault only on a bound record page, a closed template set, regions its template places (#23708)
Part of #23610
Clause-②: yes (narrowing)
Executes ruling `6190536588` items 1, 2 and 3 (Q1 A · Q2 A, plus half 1)
under claim `6194235384`. Item 4
(`skills/objectstack-ui/rules/pages.md`) is a separate Tier H PR (claim
`6204703954`). That PR carries the card's closing keyword, so this one
is `Part of` and the card closes with its last piece; item 5's
follow-ups (the objectui card, the hotcrm memo) belong to the director
seat. One PR, not split: +1055/−100 across 33 files after patch round 2
(pin bump included), generated artifacts included, about two of
precedent #23331's size for two refusals, as the ruling's workload line
estimated.
## What changes
**Q1 — `isDefault: true` off a record page bound to an object is refused
at every door.** One sentence, `misplacedDefaultPageRefusal`
(`packages/spec/src/ui/default-record-page.ts`, next to
`defaultRecordPageObject`, whose reading it reuses — no second judge).
It names where a landing is really set: the app's first navigation item,
and the app's own `isDefault` for the root landing. A record page with
no `object` gets the bind-the-object variant.
**Q2 — `template` closes and each template declares its slots.**
`PageTemplateSchema` = `default`, `header-sidebar-main`, `three-column`,
`dashboard`; `full-width` is retired by name through
`enumWithRetiredValues` (prescription: use `default`, renders the same).
`PAGE_TEMPLATE_SLOTS` declares the slots per the ruling;
`PAGE_TEMPLATE_OPEN_GRID` = `dashboard`. `checkPageRegionPlacement`
refuses, at `regions.N.name`, a region the template does not place,
naming the template's slots and a rename hint (`left_sidebar` to
`sidebar`, `right_sidebar` to `aside`, `content` to `main`). Judged on
`kind: 'full'` pages only (a `slotted` page renders its `slots`, `html`
/ `react` their `source`) and case-insensitively, as the renderer's
`findRegion` is. A second region that fills a slot an earlier region
already fills is refused the same way, naming the region that fills it,
because `findRegion` places the first match only. This came in the
seat's patch round (record `6199121442` item 2). On `dashboard`, a
second `header` or `footer` is refused; two regions of any other name
stay two grid cells.
**Half 1 — the text says what runs.** `PageTypeSchema`'s docblock and
enum comments: `home`, `app` and `utility` are one region layout at
three content widths; `home` is not a landing declaration; `utility` is
an ordinary inline page with no floating or docked shell slot, and stays
a type because the print door and the page-type width read it. The
`print`-on-`utility` refusal no longer says "floating panel" (its pin in
`page-print.test.ts` now also asserts the word is gone).
`content/docs/ui/pages.mdx` (page types, a Templates table, the
`isDefault` row, the region snippet's `content` to `main`), and the
liveness `type`, `template`, `regions`, `isDefault` rows (re-read at
objectui `0656b0e3c0`).
## Door by door (PM mechanism assumption 1, measured)
| Door | Goes through `PageSchema`? | What this PR does there | Reading
|
| :--- | :--- | :--- | :--- |
| `definePage` / `defineStack` (`stack.zod.ts`, `pages:
z.array(PageSchema)`), so `os validate` / `os build` | yes | nothing —
the two new `superRefine`s refuse | `STACK_SCHEMA_INVALID` 422 at
`pages.N.isDefault` / `pages.N.template` / `pages.N.regions.M.name`
(`page-default-and-region-placement.test.ts`,
`stack-default-record-page.test.ts`) |
| metadata save door (`saveMetaItem`, `getMetadataTypeSchema('page')`) |
yes | nothing | `422 INVALID_METADATA`, nothing stored, both topologies
(`default-record-page-save-door.test.ts`) |
| package registration, package door (`SchemaRegistry.installPackage`) |
**no** — the registry parses only object / app / package / plugin bodies
| `refuseDefaultRecordPageConflicts` first refuses misplaced defaults
(`misplacedDefaultPages`, same sources as `declaredDefaultRecordPages`,
nested `plugins[]` included), ahead of every mutation |
`MisplacedDefaultPageError`, `INVALID_METADATA` 422 (existing code, no
new one), no package record left
(`registry-default-record-page.test.ts`) |
| package registration, item seam (`registerItem` with a `packageId`) |
**no** | refuses before the holder check | same envelope |
| bare slot (`registerItem` with no package: `sys_metadata` hydration) |
— | not judged, by the door's existing design | pinned as a control |
| `core` `stack-organization-record-page.ts` | n/a — a reader of
`defaultRecordPageObject`, not a door | unchanged | `core` suite green |
Q2's refusals are parse-only (the ruling names `os validate` and save),
so the registry carries no template or region judgement.
## Stored metadata (assumption 4, measured)
No D2 conversion touches these shapes and the read path does not refuse
them: a `sys_metadata` page row laid in the store with a non-record
`isDefault: true`, `template: 'full-width'` or a `left_sidebar` region
is served by `getMetaItem` unchanged, on both the environment-scoped and
the host-config kernel; saving it again answers 422.
`applyConversionsToStoredItem` returns each row byte-equal with zero
notices, and the parse still refuses it. The changeset's "Stored
metadata keeps loading" paragraph states exactly this.
## First-party writers (assumption 5)
- `examples/app-showcase/src/ui/pages/capability-map.page.ts` drops
`isDefault: true`; it stays the landing as the first
`ShowcaseApp.navigation` item.
- `content/docs/protocol/objectui/layout-dsl.mdx`: three `os:check-yaml`
pages named `template: standard` / `console` (with `left` / `right`
regions), red under `check:yaml-examples` — now `default` and
`header-sidebar-main` with `sidebar` / `main`; the callouts that called
`template` a free-form string are corrected.
- `examples/app-showcase/src/ui/pages/task-visualizations.pages.ts`: its
shared base held `template: 'default'` as a widened `string` and stopped
type-checking against the closed set (+7 root-program errors under
`check:type-check-debt`); now `as const`. The changeset carries the same
one-line migration for consumers.
- No first-party page wrote `full-width` or an unplaced region in
source. Census: `full-width` in two test fixtures only; every `regions`
entry in `examples/**`, `packages/**`, `content/docs/**` read by name
(main 226, header 40, sidebar 9, footer 4, aside 1, the rest test-only).
Fixture triage (rule's consumption radius, not the edited package):
- **Replaced** (pinned exactly the removed branch):
`stack-default-record-page.test.ts` "an isDefault page that is not a
record page is outside the rule", `registry-default-record-page.test.ts`
same, `default-record-page-save-door.test.ts` same — each now asserts
the refusal, with a no-flag control beside it. `page.test.ts` "should
accept page with custom template" (`three-column-layout`) and "should
accept default page" (no `object`).
- **Respelled**: the two `buildDefaultPageSchema` seed fixtures (`spec`
and `lint` `*-22537.test.ts`) from `full-width` to `default`, with a
note.
- **Re-anchored**: `expression-scope-app-root.pin.test.ts`'s LIT control
quoted the old page-type sentence.
- **Extended**: `object-refinement-check-exports.test.ts` catalogues the
two new checks (bijection, attached-by-identifier, barrel identity).
## Renderer premise (assumption 3, re-read)
objectui `main` at `0656b0e3c0` (and the sparse read at `2bacbf08b4`):
`TEMPLATE_REGISTRY` still holds `default`, `full-width`,
`header-sidebar-main`, `three-column`, `dashboard`; `default` and
`full-width` are the same `FullWidthTemplate`, so **no rendering fork**
and nothing was posted on the card for it. Slots match the ruling's
table; `findRegion` is case-insensitive, which this PR follows.
**One premise did not hold — "zero first-party writers" of
`full-width`.** objectui's
`packages/plugin-detail/src/synth/buildDefaultPageSchema.ts` returns
`template: 'full-width'`, and Studio's record-page create seed
(`app-shell/src/views/metadata-admin/anchors.ts`, `createSeed`) copies
it into the page it saves. Retirement proceeds as ruled (identical
rendering, no alias window), but that seed must say `default` before or
with the pin bump that ships this, or a Studio "new record page" save
answers 422. objectui's `PageNodeSchema` also redeclared `template` as
`string` over the spec member, which is TS2430 against this spec
(measured on a mirror, report `6200739195`). Both are fixed in
objectstack-ai/objectui#12585, the sibling fix that AGENTS.md (Post-Task
Checklist step 4) ships with this PR.
## Ledger
Two D3 entries, `page-is-default-off-record-page-refused` and
`page-template-closed-region-unplaced-refused`; no D2 conversion
(ruling: no grace or alias window); no tombstone (no key removed). No
`STEP18_RATIONALE` fragment was added: neither precedent entry for
`isDefault` carried one, and draft PR #23418 is editing
`registry.ts.template`. BREAKING changeset
`.changeset/23610-page-isdefault-template-slots.md`: `@objectstack/spec`
minor, `@objectstack/objectql` patch, ADR-0087 marker `registered`
naming both ids. New exports regenerate `api-surface/`,
`export-origins/`, `declaration-map/` and `content/docs/references/`.
The template block sits after `PageComponentSchema` on purpose, so
`skills/objectstack-ui/references/_index.md` (first five exports of the
file) stays byte-identical and no `skills/**` path enters this PR.
## Verification (final head `4815bfa99`, after merging `origin/main`
`f77ba411e`)
- `@objectstack/spec`: 742 files / 21,868 tests passed.
`check:generated`: all 14 artifacts current.
- Downstream consumers of the narrowed schema, all green: `objectql`
8,157, `metadata-protocol` 20,560 (19 skipped), `core` 2,586, `lint`
6,771, `metadata-core` 728, `platform-objects` 1,091, `plugin-approvals`
1,080, `cloud-connection` 555, `mcp` 467, `downstream-contract` 31,
`example-showcase` 411, `example-crm` 46, `cli` unit 4,354 (integration
layer declared to CI). `objectql`, `metadata-protocol`,
`example-showcase` re-run on the final head after the merge.
- `typecheck` green for spec, objectql, metadata-protocol, core, lint,
cli, platform-objects, plugin-approvals, cloud-connection, mcp,
downstream-contract, example-showcase, example-crm; root program back to
its ledger's 26.
- Derived gates (`dispatch-gates --commands`, 119 families) on the final
head: all exit 0; `--ran` reconciliation "119 derived, 119 run, 0
NOT-MEASURED (a DERIVED zero)". Two families first refused on the
shallow clone (`check-engine-split-ratio`, `check-plugin-teardown-shape
--self-test`) and passed after the deepening they prescribe. Local-only:
`check:dev-prereqs` 0, `check:skill-frame-freshness` 0.
- Ablations (each through `ablation-replace.mjs`, mutation proven on
disk, restore proven by an empty `git diff HEAD`): drop
`checkPageDefaultPlacement` 8 red; drop `checkPageRegionPlacement` 4
red; `template` back to `z.string()` 5 red; drop the package-door throw
1 red (the item seam still catches the top-level pages; the
nested-plugin and no-record pin goes red); drop the item-seam throw 1
red.
- Reverse type verification: `template: 'full-width'` in a showcase page
gives `TS2322 … not assignable to type '"dashboard" | "default" |
"header-sidebar-main" | "three-column"'`, so the rebuilt `.d.ts` was
read.
## Landing order
1. Done: objectstack-ai/objectui#12585 merged on objectui `main` as
`daeb77fc84dd`. The Studio record-page seed says `default`, and
`PageNodeSchema.template` takes the spec's type in both the TS and zod
faces.
2. Done (patch round 2, head `d36a804054`):
- The branch merged `main` at `140238a47b`, after #23738 had moved the
pin to `f96f771140f1`.
- `scripts/bump-objectui.sh` moved `.objectui-sha` forward to
`daeb77fc84dd`, which carries #12585, and wrote
`.changeset/console-daeb77fc84dd.md` (`@objectstack/console`: minor).
- `scripts/sdui-manifest.record.json` and
`packages/sdui-parser/objectui-lockstep.json` are re-recorded at the new
pin. `sdui.manifest.json` is byte-unchanged, and the lockstep needs no
port.
3. Done: the new head has its delta contract-review record, PASS
`6206263948` (the full PASS `6204434889` on `c2b9935b4` stands for
everything it judged). The seat then marks this PR ready.
4. After this PR merges, the Tier H PR #23758 (ruling item 4, `Fixes
#23610`) is marked ready, and #23776 (#23710) merges `main` and lands
after it (landing-operations D).
## Patch round (head `c2b9935b4`, `origin/main` `8c85141c5` merged)
- `@objectstack/spec`: 742 files / 21,876 tests passed. The downstream
consumer set is unchanged and green. The duplicate-slot ablation (the
branch never fires) gives 6 red of 285; restored.
- `dispatch-gates --commands`: 119 families, 119 exit 0 (two
`PREREQUISITE NOT MET` exits on a fresh worktree passed after the full
build). `check:generated` 14/14. CI on `c2b9935b4`: 41 check-runs, 36
success, 5 skipped.
## Acceptance notes
- Two regions filling the same slot (two `main`) were accepted and the
second was dropped by the renderer. They are now refused in this PR
(patch round, `c2b9935b4`).
- `skills/objectstack-ui/rules/pages.md` rows for the Tier H follow-up
are listed in the dispatch report on #23610.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TC6DKN349m4i7okGWPe4RG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 0b1179f commit a69a40a
33 files changed
Lines changed: 1055 additions & 100 deletions
File tree
- .changeset
- content/docs
- protocol/objectui
- references
- ui
- ui
- examples/app-showcase/src/ui/pages
- packages
- lint/src
- objectql/src
- sdui-parser
- spec
- api-surface
- declaration-map
- export-origins
- json-schema.manifest
- liveness
- src
- migrations/entries/semantic
- ui
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
72 | 72 | | |
73 | 73 | | |
74 | 74 | | |
75 | | - | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
76 | 84 | | |
77 | 85 | | |
78 | 86 | | |
| |||
102 | 110 | | |
103 | 111 | | |
104 | 112 | | |
105 | | - | |
| 113 | + | |
106 | 114 | | |
107 | 115 | | |
108 | 116 | | |
| |||
150 | 158 | | |
151 | 159 | | |
152 | 160 | | |
| 161 | + | |
| 162 | + | |
153 | 163 | | |
154 | 164 | | |
155 | 165 | | |
156 | 166 | | |
157 | 167 | | |
158 | | - | |
| 168 | + | |
159 | 169 | | |
160 | | - | |
| 170 | + | |
161 | 171 | | |
162 | 172 | | |
163 | 173 | | |
164 | 174 | | |
165 | 175 | | |
166 | 176 | | |
167 | | - | |
168 | | - | |
| 177 | + | |
169 | 178 | | |
170 | 179 | | |
171 | 180 | | |
| |||
218 | 227 | | |
219 | 228 | | |
220 | 229 | | |
221 | | - | |
222 | | - | |
223 | | - | |
| 230 | + | |
| 231 | + | |
224 | 232 | | |
225 | 233 | | |
226 | 234 | | |
| |||
757 | 765 | | |
758 | 766 | | |
759 | 767 | | |
760 | | - | |
| 768 | + | |
761 | 769 | | |
762 | 770 | | |
763 | 771 | | |
| |||
843 | 851 | | |
844 | 852 | | |
845 | 853 | | |
846 | | - | |
| 854 | + | |
847 | 855 | | |
848 | 856 | | |
849 | 857 | | |
850 | 858 | | |
851 | | - | |
| 859 | + | |
852 | 860 | | |
853 | 861 | | |
854 | 862 | | |
| |||
1016 | 1024 | | |
1017 | 1025 | | |
1018 | 1026 | | |
1019 | | - | |
| 1027 | + | |
1020 | 1028 | | |
1021 | 1029 | | |
1022 | 1030 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
4 | | - | |
| 4 | + | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
36 | | - | |
37 | | - | |
| 36 | + | |
| 37 | + | |
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
| |||
364 | 364 | | |
365 | 365 | | |
366 | 366 | | |
367 | | - | |
| 367 | + | |
368 | 368 | | |
369 | 369 | | |
370 | 370 | | |
| |||
381 | 381 | | |
382 | 382 | | |
383 | 383 | | |
384 | | - | |
| 384 | + | |
385 | 385 | | |
386 | 386 | | |
387 | 387 | | |
| |||
0 commit comments