diff --git a/.changeset/nav-contribution-group-mis-aim-describe.md b/.changeset/nav-contribution-group-mis-aim-describe.md new file mode 100644 index 0000000000..43745fdfe3 --- /dev/null +++ b/.changeset/nav-contribution-group-mis-aim-describe.md @@ -0,0 +1,11 @@ +--- +"@objectstack/spec": patch +--- + +`navigationContributions[].group` now documents the mis-aimed case, not only the omitted one (#14925) + +The `describe()` on that key said what happens when `group` is **omitted** and nothing about what happens when it is **present and names no group the target app declares** — which is the case that actually bites. A contributing package cannot see the target app's group ids at authoring time (the target app belongs to another package), so a wrong id is undetectable by reading the contributor's own source; and the platform **relocates** the items to the app's top level rather than refusing them, so the menu renders, a smoke test passes, and the information architecture has silently changed. + +The description now names that third case: it is **not refused**, the items are appended at the app top level anyway, and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by **both** `os build` and `os validate` at compile time, in each command's `--json` payload under the existing `warnings` key. + +Prose only. `group` remains `SnakeCaseIdentifierSchema.optional()`, the accept set is unchanged and nothing is refused that was not refused before; the recorded authorable key surface (`authorable-surface.json`) and the schema manifest (`json-schema.manifest.json`) are byte-identical. What moves is the string an author reads: the generated reference rows in `content/docs/references/ui/app.mdx` and `content/docs/references/kernel/manifest.mdx`, and the `description` on the published JSON Schemas that embed `NavigationContribution` (its own schema, the bundled `objectstack.json`, and 22 `json-schema/api/*` and `json-schema/kernel/*` package envelopes). diff --git a/content/docs/references/kernel/manifest.mdx b/content/docs/references/kernel/manifest.mdx index 5fa552a07a..ab9d0adc2d 100644 --- a/content/docs/references/kernel/manifest.mdx +++ b/content/docs/references/kernel/manifest.mdx @@ -104,7 +104,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **app** | `string` | ✅ | Target app name to contribute navigation into (e.g. "setup") | -| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level | +| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time. | | **priority** | `integer` | optional (default: `200`) | Merge priority within the target group — lower applied first (matches object extender priority) | | **items** | `({ id: string; label: string \| Record; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group | diff --git a/content/docs/references/ui/app.mdx b/content/docs/references/ui/app.mdx index 16ac1a9440..01638a6f8e 100644 --- a/content/docs/references/ui/app.mdx +++ b/content/docs/references/ui/app.mdx @@ -566,7 +566,7 @@ A navigation contribution: a package injecting nav items into an app it does not | Property | Type | Required | Description | | :--- | :--- | :--- | :--- | | **app** | `string` | ✅ | Target app name to contribute navigation into (e.g. "setup") | -| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level | +| **group** | `string` | optional | Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time. | | **priority** | `integer` | optional (default: `200`) | Merge priority within the target group — lower applied first (matches object extender priority) | | **items** | `({ id: string; label: string \| Record; icon?: string; order?: number; … } \| { type: 'separator'; id?: string; order?: number } \| … +7 more)[]` | ✅ | Navigation items contributed into the target app/group | diff --git a/packages/spec/src/ui/app.zod.ts b/packages/spec/src/ui/app.zod.ts index 7804ae2c07..e901ac0934 100644 --- a/packages/spec/src/ui/app.zod.ts +++ b/packages/spec/src/ui/app.zod.ts @@ -761,7 +761,7 @@ export const NavigationContributionSchema = lazySchema(() => strictObject( }, { app: SnakeCaseIdentifierSchema.describe('Target app name to contribute navigation into (e.g. "setup")'), - group: SnakeCaseIdentifierSchema.optional().describe('Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level'), + group: SnakeCaseIdentifierSchema.optional().describe('Target group nav-item id to append into (e.g. "group_integrations"); omit to append at the app top level. Naming a group the target app does not declare is not refused: the items are appended at the app top level anyway and a `nav_contribution_group_missing` diagnostic is emitted — by the runtime at `warn`, and by `os build` and `os validate` at compile time.'), priority: z.number().int().min(0).default(200).describe('Merge priority within the target group — lower applied first (matches object extender priority)'), items: z.array(NavigationItemSchema).describe('Navigation items contributed into the target app/group'), }).describe('A navigation contribution: a package injecting nav items into an app it does not own (ADR-0029 D7)'));