From 377e2ab794875d9584ffc10e6882c10859d12505 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 6 Aug 2026 09:06:38 +0000 Subject: [PATCH] fix(console): declare `type: 'home'` on the page preview sample (#3454) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `PageSchema.type` is `PageTypeSchema.default('record')`, so the page sample omitting `type` did not mean "unspecified" — it materialised the CRM welcome screen as a RECORD page bound to no object (`object` is optional, so nothing complained). The `app` sample in the same file routes that very page as the CRM's landing entry, so `home` is the kind it is actually used as. The mismatch was invisible: the gallery renders the UNPARSED draft, so no test would ever have gone red over it. It matters because these samples are the worked example authors — increasingly models generating metadata — copy. Pinned on the PARSED value, since only the parse distinguishes "declared `home`" from "omitted, therefore `record`". Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01GTRjn8xBqp75dk7kFupVRt --- .../preview-samples-spec-valid.test.ts | 39 +++++++++++++++++++ apps/console/src/preview-samples.ts | 12 ++++++ 2 files changed, 51 insertions(+) diff --git a/apps/console/src/__tests__/preview-samples-spec-valid.test.ts b/apps/console/src/__tests__/preview-samples-spec-valid.test.ts index 1a9925aec7..5a2685ee6d 100644 --- a/apps/console/src/__tests__/preview-samples-spec-valid.test.ts +++ b/apps/console/src/__tests__/preview-samples-spec-valid.test.ts @@ -224,6 +224,18 @@ function issuesFor(type: string): { path: string; message: string }[] { .map((issue) => ({ path: issue.path.join('.'), message: issue.message })); } +/** + * The `page` sample as the spec actually MATERIALISES it — i.e. after defaults + * are applied, not as authored. Parsing is the only way to see a defaulted key: + * reading `SAMPLES.page.type` shows what the author wrote, which is precisely + * the blind spot objectui#3454 was filed for. + */ +function parsedPage(): Record { + const result = ObjectStackSchema.safeParse({ pages: [SAMPLES.page] }); + if (!result.success) throw new Error('page sample no longer parses'); + return (result.data as { pages: Record[] }).pages[0]; +} + describe('preview-samples conform to @objectstack/spec', () => { // Without this, a sample added to the gallery tomorrow would be validated by // nothing and no test would notice — the exact failure mode this file exists @@ -242,6 +254,33 @@ describe('preview-samples conform to @objectstack/spec', () => { expect(issuesFor(type)).toEqual([]); }); + /** + * objectui#3454 — the page sample must declare the page KIND it is used as. + * + * Being valid is not the same as meaning what it says. `PageSchema.type` is + * `PageTypeSchema.default('record')`, so a page that omits `type` parses + * clean and MATERIALISES as a record page — one bound to no object, since + * `object` is optional. This sample is not a record page: the `app` sample in + * the same file routes it as the CRM's landing entry + * (`navigation[0] = { id: 'home', type: 'page', pageName: 'crm_welcome' }`), + * and its content is a welcome header with quick links. + * + * The mismatch is invisible in the gallery, which renders the UNPARSED draft + * — so nothing would ever have gone red over it. It matters because these + * samples are the worked example an author (increasingly, a model generating + * metadata) copies: a landing page silently defaulting to `record` is wrong + * semantics propagating from the file that exists to demonstrate right ones. + * + * Asserted on the PARSED value, not the authored literal, because the defect + * lives in the default: only the parse distinguishes "declared `home`" from + * "omitted, therefore `record`". Deleting the sample's `type` line turns this + * red with the received value `'record'`. + */ + it('page sample declares `home`, the kind the app sample routes it as', () => { + expect(parsedPage().type).toBe('home'); + expect(SAMPLES.page.type).toBe('home'); + }); + // Reverse assertion (same shape as objectui#3212): a ledger nobody re-checks // becomes a dumping ground. If a quarantined sample starts parsing — because // someone fixed it, or the spec relaxed — this fails and demands it be diff --git a/apps/console/src/preview-samples.ts b/apps/console/src/preview-samples.ts index 83355a309b..f6e115be26 100644 --- a/apps/console/src/preview-samples.ts +++ b/apps/console/src/preview-samples.ts @@ -49,6 +49,18 @@ export const SAMPLES: Record> = { page: { name: 'crm_welcome', label: 'CRM Welcome', + // Declared, NOT defaulted (objectui#3454). `PageSchema.type` is + // `PageTypeSchema.default('record')`, so omitting it does not mean + // "unspecified" — it materialises this welcome screen as a RECORD page + // bound to no object (`object` is optional, so nothing complains). The app + // sample below routes this very page as the CRM's landing entry + // (`navigation[0]` = `{ id: 'home', type: 'page', pageName: 'crm_welcome' }`), + // so `home` is the kind it is actually used as. Spelling it out is what + // makes the sample mean what it demonstrates: the gallery renders the + // unparsed draft and would never have shown the difference, but these + // samples are copied — increasingly by models generating metadata — and a + // landing page that silently defaults to `record` propagates as such. + type: 'home', regions: [ { name: 'main',