From b3f07249a255b6c9f2b7813b0836e810d318d04e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 06:45:02 +0000 Subject: [PATCH 1/3] docs: default record page remedies name the page, not the package; AppPlugin.bundle names its readers The save door's refusal, PageSchema.isDefault's TSDoc and the error-code ledger comment now say what the registry's remedy says: compose the deployment without the other package's page, not without the whole package. The save door's pin moves with its text. AppPlugin's private bundle field names the three structural readers outside the class. Claude-Session: https://claude.ai/code/session_01CN3bgHivgMGBHWTNUbYCDp Co-authored-by: Claude --- packages/metadata-protocol/src/protocol.ts | 2 +- .../objectql/src/default-record-page-save-door.test.ts | 2 +- packages/runtime/src/app-plugin.ts | 10 ++++++++++ packages/spec/src/api/error-code-ledger.zod.ts | 2 +- packages/spec/src/ui/page.zod.ts | 4 ++-- 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/metadata-protocol/src/protocol.ts b/packages/metadata-protocol/src/protocol.ts index aa4b0557dee..cccb1a93dbf 100644 --- a/packages/metadata-protocol/src/protocol.ts +++ b/packages/metadata-protocol/src/protocol.ts @@ -21757,7 +21757,7 @@ export class ObjectStackProtocolImplementation implements const others = named.length === 1 ? `${named[0]} already is` : `${named.join(' and ')} already are`; const remedy = unique.some((h) => h.shipped) ? `Save '${saveName}' with \`isDefault: false\`: a page a package ships stays the default unless the ` - + `deployment is composed without that package.` + + `deployment is composed without that package's page.` : `Save '${saveName}' with \`isDefault: false\`, or first save ${unique.map((h) => `'${h.page}'`).join(' and ')} ` + `with \`isDefault: false\` to make '${saveName}' the default instead.`; const err = new Error( diff --git a/packages/objectql/src/default-record-page-save-door.test.ts b/packages/objectql/src/default-record-page-save-door.test.ts index 6897260d428..5e8d4a9233d 100644 --- a/packages/objectql/src/default-record-page-save-door.test.ts +++ b/packages/objectql/src/default-record-page-save-door.test.ts @@ -194,7 +194,7 @@ describe.each(TOPOLOGIES)('%s kernel — a page saved as a second default record expectRefusal(outcome, 'env_org_detail', 'acme_org', `page 'acme_org_detail' of package '${PKG}'`); expect(String((outcome as { error?: any }).error?.message)).toContain( "Save 'env_org_detail' with `isDefault: false`: a page a package ships stays the default unless the deployment " - + 'is composed without that package.', + + "is composed without that package's page.", ); expect(storedPages()).toEqual([]); }); diff --git a/packages/runtime/src/app-plugin.ts b/packages/runtime/src/app-plugin.ts index 64714de98bf..f234593240f 100644 --- a/packages/runtime/src/app-plugin.ts +++ b/packages/runtime/src/app-plugin.ts @@ -150,6 +150,16 @@ export class AppPlugin implements Plugin { */ requiresServices: string[] = ['manifest']; + /** + * Private to the type checker only: three readers outside this class read + * `bundle` structurally off a plugin entry, with no type to break when it + * changes. `stackDeclaresOrganizationRecordPage` (`@objectstack/core`, + * `stack-organization-record-page.ts`) looks for an app's own + * `sys_organization` record page; `@objectstack/cli`'s `serve.ts` looks for + * declared translations, and its `graft-runtime-hooks.ts` finds an app's + * bundle by manifest id to graft hooks onto. Renaming or reshaping this field silently blinds all + * three; change them together. + */ private bundle: any; /** Memoized backing store for {@link collections}. */ private resolvedCollections?: any; diff --git a/packages/spec/src/api/error-code-ledger.zod.ts b/packages/spec/src/api/error-code-ledger.zod.ts index a108a1607e3..dce268ce9df 100644 --- a/packages/spec/src/api/error-code-ledger.zod.ts +++ b/packages/spec/src/api/error-code-ledger.zod.ts @@ -946,7 +946,7 @@ export const ERROR_CODE_LEDGER = { // two HTTP install doors call `installPackage`. Its own code rather than // `NAMESPACE_CONFLICT`: no name is taken — an object's one default slot is // held — and renaming fixes nothing; the remedy is `isDefault: false` on - // one page, or composing the deployment without the other package. Not a + // one page, or composing the deployment without the other package's page. Not a // synonym of any standard member: the manifest is well-formed, and the // refusal comes from what the deployment already holds. 'DEFAULT_RECORD_PAGE_CONFLICT', diff --git a/packages/spec/src/ui/page.zod.ts b/packages/spec/src/ui/page.zod.ts index e8a47b21d00..1f591113cd0 100644 --- a/packages/spec/src/ui/page.zod.ts +++ b/packages/spec/src/ui/page.zod.ts @@ -1871,8 +1871,8 @@ export const PageSchema = lazySchema(() => strictObject({ * objectstack#23015, record 6134056653.) * * To change which page opens: set `isDefault: false` on one of the two, or - * compose the deployment without the package that ships the other. Shipping - * a second default is not an override path. + * compose the deployment without the other package's page. Shipping a + * second default is not an override path. * * `isDefault: true` on a page that is not a record page bound to an object * is REFUSED, at the same doors (maintainer ruling A on objectstack#23610, From f7a1440e4d412bc7d33ce9ac042cb27b6f5d87c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 06:45:20 +0000 Subject: [PATCH 2/3] docs(runtime): rewrap the AppPlugin.bundle readers comment Claude-Session: https://claude.ai/code/session_01CN3bgHivgMGBHWTNUbYCDp Co-authored-by: Claude --- packages/runtime/src/app-plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/runtime/src/app-plugin.ts b/packages/runtime/src/app-plugin.ts index f234593240f..1345be20edc 100644 --- a/packages/runtime/src/app-plugin.ts +++ b/packages/runtime/src/app-plugin.ts @@ -157,8 +157,8 @@ export class AppPlugin implements Plugin { * `stack-organization-record-page.ts`) looks for an app's own * `sys_organization` record page; `@objectstack/cli`'s `serve.ts` looks for * declared translations, and its `graft-runtime-hooks.ts` finds an app's - * bundle by manifest id to graft hooks onto. Renaming or reshaping this field silently blinds all - * three; change them together. + * bundle by manifest id to graft hooks onto. Renaming or reshaping this + * field silently blinds all three; change them together. */ private bundle: any; /** Memoized backing store for {@link collections}. */ From be4b60867960ccccd38bf27ae717069e15312609 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 16 Oct 2026 06:52:54 +0000 Subject: [PATCH 3/3] chore: changeset for the default record page remedy wording Claude-Session: https://claude.ai/code/session_01CN3bgHivgMGBHWTNUbYCDp Co-authored-by: Claude --- .changeset/23553-default-record-page-remedy.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .changeset/23553-default-record-page-remedy.md diff --git a/.changeset/23553-default-record-page-remedy.md b/.changeset/23553-default-record-page-remedy.md new file mode 100644 index 00000000000..562f4a45950 --- /dev/null +++ b/.changeset/23553-default-record-page-remedy.md @@ -0,0 +1,17 @@ +--- +'@objectstack/spec': patch +'@objectstack/metadata-protocol': patch +'@objectstack/runtime': patch +--- + +docs: the default-record-page remedies name the other package's page, not the whole package + +Clause-②: no + +A second `isDefault: true` record page for one object is refused with `DEFAULT_RECORD_PAGE_CONFLICT`. The package registry's refusal already told the author to keep one default or "compose the deployment without the other package's page". Three sibling texts still told them to drop the whole package, though a deployment can keep a package and leave out the one page it ships: + +- `@objectstack/metadata-protocol`: the metadata save door's refusal now reads "a page a package ships stays the default unless the deployment is composed without that package's page." Only the remedy clause changed; `code`, `status`, `object` and `holders` are unchanged. +- `@objectstack/spec`: `PageSchema.isDefault`'s TSDoc and the error-code ledger's comment on `DEFAULT_RECORD_PAGE_CONFLICT` say the same. +- `@objectstack/runtime`: `AppPlugin`'s private `bundle` field now documents the three readers outside the class that read it structurally, so a rename is not made blind. + +No behaviour changes; nothing to migrate.