Skip to content

Commit 660a9b2

Browse files
fix(spec): BlueprintNavItemSchema.label says an absent label is inherited at render time, not defaulted (#20851)
Fixes #20841 Clause-②: no ## What changed `BlueprintNavItemSchema.label`'s describe (`packages/spec/src/ai/solution-blueprint.zod.ts:189`) read "Nav entry label (defaults to the target label/name)". It now reads: > Nav entry label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time (a renamed target shows its new name); present ⇒ rendered verbatim, so never copy the target's label in as a default. - The key is untouched: `z.string().optional()`. No schema, export or type change; the schema accepts and refuses the same blueprints. - Regenerated from the describe: `content/docs/references/ai/solution-blueprint.mdx` (the same row renders twice on that page). `check:generated` named only `check:docs` as stale; the JSON-schema tree is gitignored (`packages/spec/json-schema/`), so there is no tracked JSON copy to regenerate. - One `patch` changeset: `.changeset/20841-blueprint-nav-label-inherits.md`. ## Why restated, not referenced The runtime field's describe is an inline string literal on the module-local `BaseNavItemSchema` in `packages/spec/src/ui/app.zod.ts`: no exported constant to import. Sharing it would mean exporting a new constant from `app.zod.ts` (the card forbids widening that file) or reading `.description` off a lazy runtime schema when the blueprint module loads, which couples the blueprint schema to the UI schema's internals. Its text also opens with "Display proper label" and spends most of its length on which view or object label is inherited, which is not what a blueprint author needs. So the meaning is restated in one sentence in the runtime field's own terms (absent ⇒ inherits the current label at render time; present ⇒ verbatim). `app.zod.ts` is not touched. ## Evidence - Every changed generated hunk is that sentence: both changed rows in the mdx equal the row prefix `` | **label** | `string` | optional | `` + the describe string as evaluated out of the `.ts` source + ` |`, byte for byte (checked by script); the `.ts` diff is that one line. Diffstat: 3 files, +21 / -3. - `pnpm --filter @objectstack/spec check:generated` reported exactly one stale artifact (`content/docs/references/**`) after the edit, and exited 0 on the committed tree. - `pnpm --filter @objectstack/spec test`: 578 files, 17066 tests passed. `pnpm --filter @objectstack/spec typecheck`: exit 0. - Derived gates, `dispatch-gates --commands` at head `64e5e460e`, reconciled with `--ran`: 102 derived, 101 run with exit 0, 1 NOT MEASURED: `pnpm check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: it reads `dist/` of every published package, 83 of them unbuilt here, and was not run after a full build). The change is a string inside a `.describe()`, which cannot change whether a `require` entry point loads; CI owns that gate. Five other gates first exited 3 for unbuilt lint, client-react and objectql closures; those were built and the five re-run: all exit 0. ## Acceptance notes - The design model's generation contract is the strict mirror, `StrictNavItem.label` in the same file: "Nav entry label, or null". It never carried the "defaults" wording, so it was not the trap and is not edited here; it also does not say that null means inherit. Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent a94f3ba commit 660a9b2

3 files changed

Lines changed: 21 additions & 3 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `BlueprintNavItemSchema.label` says an absent label is inherited at render time, not defaulted by the expander
6+
7+
Clause-②: no
8+
9+
The `label` describe on a blueprint nav item read "defaults to the target label/name". An
10+
expander or an AI author that follows "defaults" copies the target's label into the entry, and
11+
the entry then stops following a rename of that target. The runtime nav entry's `label` has
12+
meant something else since it became optional: absent, the entry inherits the CURRENT label of
13+
what it opens at render time; present, it renders verbatim. The blueprint describe now says
14+
exactly that, and tells the author not to copy the target's label in as a default.
15+
16+
Describe text only: the key stays `z.string().optional()`, so the schema accepts and refuses
17+
the same blueprints. The reference page `content/docs/references/ai/solution-blueprint.mdx`
18+
is regenerated from it.

‎content/docs/references/ai/solution-blueprint.mdx‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ const result = BlueprintAppSchema.parse(data);
5353
| :--- | :--- | :--- | :--- |
5454
| **type** | `Enum<'object' \| 'dashboard'>` | optional (default: `"object"`) | What this nav entry opens |
5555
| **target** | `string` | ✅ | Object or dashboard machine name to surface (snake_case) |
56-
| **label** | `string` | optional | Nav entry label (defaults to the target label/name) |
56+
| **label** | `string` | optional | Nav entry label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time (a renamed target shows its new name); present ⇒ rendered verbatim, so never copy the target's label in as a default. |
5757
| **icon** | `string` | optional | Lucide icon name for the nav entry |
5858
| **viewName** | `string` | optional | For type:"object" only — the `views[].name` this entry opens (e.g. "ticket_status_board"). Omit for the object's default list. SET it whenever this blueprint authors a kanban/calendar/gallery/gantt view the menu should reach: give the object ONE entry per view (a 「工单列表」 entry with no viewName plus a 「工单看板」 entry with viewName:"ticket_status_board"). Without it every entry on the same target opens the SAME default list, and a label/icon saying otherwise is decoration. |
5959

@@ -187,7 +187,7 @@ const result = BlueprintAppSchema.parse(data);
187187
| :--- | :--- | :--- | :--- |
188188
| **type** | `Enum<'object' \| 'dashboard'>` | optional (default: `"object"`) | What this nav entry opens |
189189
| **target** | `string` | ✅ | Object or dashboard machine name to surface (snake_case) |
190-
| **label** | `string` | optional | Nav entry label (defaults to the target label/name) |
190+
| **label** | `string` | optional | Nav entry label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time (a renamed target shows its new name); present ⇒ rendered verbatim, so never copy the target's label in as a default. |
191191
| **icon** | `string` | optional | Lucide icon name for the nav entry |
192192
| **viewName** | `string` | optional | For type:"object" only — the `views[].name` this entry opens (e.g. "ticket_status_board"). Omit for the object's default list. SET it whenever this blueprint authors a kanban/calendar/gallery/gantt view the menu should reach: give the object ONE entry per view (a 「工单列表」 entry with no viewName plus a 「工单看板」 entry with viewName:"ticket_status_board"). Without it every entry on the same target opens the SAME default list, and a label/icon saying otherwise is decoration. |
193193

‎packages/spec/src/ai/solution-blueprint.zod.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ export type BlueprintDashboard = z.input<typeof BlueprintDashboardSchema>;
186186
export const BlueprintNavItemSchema = lazySchema(() => z.object({
187187
type: z.enum(['object', 'dashboard']).default('object').describe('What this nav entry opens'),
188188
target: z.string().regex(SNAKE_CASE).describe('Object or dashboard machine name to surface (snake_case)'),
189-
label: z.string().optional().describe('Nav entry label (defaults to the target label/name)'),
189+
label: z.string().optional().describe('Nav entry label. Optional: absent ⇒ the entry inherits the CURRENT label of what it opens at render time (a renamed target shows its new name); present ⇒ rendered verbatim, so never copy the target\'s label in as a default.'),
190190
icon: z.string().optional().describe('Lucide icon name for the nav entry'),
191191
viewName: z.string().optional()
192192
.describe('For type:"object" only — the `views[].name` this entry opens (e.g. "ticket_status_board"). Omit for the object\'s default list. SET it whenever this blueprint authors a kanban/calendar/gallery/gantt view the menu should reach: give the object ONE entry per view (a 「工单列表」 entry with no viewName plus a 「工单看板」 entry with viewName:"ticket_status_board"). Without it every entry on the same target opens the SAME default list, and a label/icon saying otherwise is decoration.'),

0 commit comments

Comments
 (0)