You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
[finding] Studio 的导航消费端读了六种 app nav schema 拒绝的拼写,外加一个不存在的 view 变体 #4881
Triage (concentrated finding round): promoted finding → pm:queue. Lenient-consumer cleanup restoring the declared invariant (AGENTS.md #0.1): delete the five || legs reading schema-rejected bare spellings, the dead case 'view' branch, and flip the node.object ?? node.objectName order in StudioNavItemInspector. The card's own pin guidance applies: assert "spec rejects the spelling AND the consumer does not parse it" (borrow navSurface.test.ts's shape) — do not accept a pin that is green on producing nothing.
{
"issue": 4881,
"status": "done",
"branch": "claude/issue-4881-studio-nav-lenient-consumer",
"pr": "https://github.com/objectstack-ai/objectui/pull/5526",
"premise_still_valid": true,
"summary": "Deleted the four bare-spelling `||` legs in `resolveSurface` (page/object/dashboard/report), deleted `case 'view'` with its fifth leg (`node.viewName || node.view`), dropped the now-unread `page`/`object`/`dashboard`/`report`/`view`/`viewName` declarations from `NavNode`, and flipped `StudioNavItemInspector` to `node.objectName ?? node.object`. Two pins added, ablation-verified. DEVIATION, declared: the flip leaves the `?? node.object` fallback readable, so a draft carrying `object` ALONE still displays as bound — by the card's own logic that is the last rejected-spelling read in `views/studio-design/**`. A test asserting it should show UNBOUND was written, failed against the flip-only scope ZONE 1 fixed, and was removed rather than widening the card; filed unassigned as objectui#5518 for triage. Also corrected in place (same defect class, same file, one sentence): the doc comment over `StudioNavItemInspector` claimed the picker writes `{ object }` — it has always written `{ type: 'object', objectName }` and cleared `object`. `StudioNavItemInspector` is now exported for its test (file precedent: `DataPillar` and two others); the package index lists exports one by one, so nothing new reaches the public API.",
"spec_reverification_17_0_0_GA": {
"installed_version_measured": "@objectstack/spec 17.0.0 (node_modules/@objectstack/spec/package.json; pnpm-lock resolves ^17.0.0 -> 17.0.0)",
"card_measured": "17.0.0-rc.6",
"premise_holds": true,
"NavigationItemSchema.safeParse": {
"{type:'page',page:'home'}": "REJECT unrecognized_keys:['page'] + invalid_type@['pageName']",
"{type:'object',object:'crm_lead'}": "REJECT unrecognized_keys:['object'] + invalid_type@['objectName']",
"{type:'dashboard',dashboard:'sales'}": "REJECT unrecognized_keys:['dashboard'] + invalid_type@['dashboardName']",
"{type:'report',report:'pipeline'}": "REJECT unrecognized_keys:['report'] + invalid_type@['reportName']",
"{type:'object',objectName:'crm_lead',view:'all'}": "REJECT unrecognized_keys:['view']",
"{type:'view',viewName:'all'}": "REJECT invalid_union@['type'] — discriminator enumerates object|dashboard|page|url|report|action|component|separator|group, nine members, no 'view'",
"canonical page/object/dashboard/report": "PARSES",
"{type:'object',objectName:'crm_lead',viewName:'all'}": "PARSES — viewName is a key ON the object item, not a nav type"
},
"AppSchema.safeParse": {
"app with bare-object nav item": "REJECT unrecognized_keys:['object']@['navigation',0]",
"app with type:'view' nav item": "REJECT invalid_union@['navigation',0,'type']"
},
"structural_cross_check": "NavigationItemSchema.unwrap().def.options has length 9; discriminators ['object','dashboard','page','url','report','action','component','separator','group'] — measured, not asserted in the test (behavioural rejection is pinned instead, to avoid coupling a pin to zod internals)"
},
"sweep": {
"method": "repo-wide grep over packages/ and apps/ for `node|item|n|leaf|nav`.(page|object|dashboard|report|view), for `(pageName|objectName|dashboardName|reportName|viewName)\\s*(\\|\\||\\?\\?)`, and for `case '(page|object|dashboard|report|view)':`",
"in_scope_population_fixed": [
"navSurface.ts resolveSurface: 5 `||` legs (the 4 bare keys + node.view inside case 'view') — PM's 'five legs' and 'case view' are the same five, not five plus one",
"StudioDesignSurface.tsx StudioNavItemInspector: 1 read (node.object ?? node.objectName)"
],
"counter_probe_examined_not_touched": [
"packages/layout/src/NavigationRenderer.tsx resolveHref — canonical-only already; no bare-key read",
"views/metadata-admin/previews/AppNavCanvas.tsx navKind — cleaned in objectui#3275 with this card's own argument; its comment is the precedent cited in the new code",
"views/metadata-admin/inspectors/nav-target.ts inferNavItemType (4 legs) + AppNavInspector.tsx:292 — DELIBERATE legacy-normalisation ramp ('edit is the migration': classify a legacy node so the next edit clears the off-spec keys), different intent from an accidental lenient read; also the directory #5416 just rewrote. Examined, NOT touched, NOT filed — flagging for PM to rule on rather than filing noise",
"views/metadata-admin/anchors.ts / registry.ts anchorByField('object'), ResourceEditPage.tsx v?.objectName ?? v?.object ?? v?.object_name — other metadata contracts (validation/flow/chart anchors, `view` metadata), not the nav union",
"packages/app-shell/src/views/ObjectView.overrideMasquerade.test.ts:51 sv.objectName || sv.object — list-view masquerade fixture, different contract"
],
"no_stale_twin_of_navSurface_found": "the #4019 line-for-line copy left no second resolveSurface; the only `case 'view'` in the repo was this one (plugin-view/ObjectView.tsx:1286 is a form-title switch on a different domain)"
},
"pin_shape": {
"files": [
"packages/app-shell/src/views/studio-design/navSurface.test.ts (appended, 3 describe blocks)",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx (new)"
],
"shape": "per-variant PAIR on the SAME `type`: (a) canonical fixture PARSES under NavigationItemSchema AND resolveSurface returns the surface; (b) bare-spelling fixture is REJECTED with an unrecognized_keys issue naming that exact key AND resolveSurface returns null. Plus: `type:'view'` fails the discriminator AND resolves null; `viewName` on an object item PARSES and that leaf still binds as `object`; inspector: the both-keys draft is refused by AppSchema while the picker shows the canonical name, with BOTH names present as options; picker output parses under NavigationItemSchema once serialized.",
"why_not_green_on_producing_nothing": "a lone toBeNull() would have passed before the deletion too. Each null is paired with a non-null on the identical `type` where only the key spelling changed, so the null is attributable to the SPELLING rather than to the variant having no surface; and the ablation makes it falsifiable — 6 of the 25 tests go red on the pre-fix reads.",
"removed_assertion": "'shows nothing bound when the draft carries the rejected spelling ALONE' — asserted deletion of the inspector fallback, which ZONE 1 did not order; removed, filed as objectui#5518"
},
"tests": "All on 9cd3c5f2d (final commit), all heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, vitest --maxWorkers=1 throughout. (1) pnpm exec vitest run packages/app-shell/src/views/studio-design/navSurface.test.ts packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx --maxWorkers=1 => 'Test Files 2 passed (2)' / 'Tests 25 passed (25)', LEG1 EXIT=0. (2) ABLATION, same lock acquisition: `git show origin/main:navSurface.ts > navSurface.ts` plus a targeted flip of the inspector read back to `node.object ?? node.objectName` (test-only export KEPT, so the ablation cannot pass through an import error); restore by `trap 'git checkout HEAD -- ...' EXIT INT TERM`, and `git diff HEAD` empty afterwards. NO REBUILD NEEDED and none done — both tests import the implementation by RELATIVE SOURCE path and vitest.config.mts aliases every @object-ui/* to src, so no dist is in the resolution path (@objectstack/spec is a prebuilt external dep). MUTATION CONFIRMED ON DISK by counting the anchored text, not by an editor exit code: navSurface 'node.pageName || node.page' 1 (want 1), \"case 'view'\" 1 (want 1), canonical-only 'node.pageName ? { type' 0 (want 0); StudioDesignSurface 'node.object ?? node.objectName' 1 (want 1), 'node.objectName ?? node.object' 0 (want 0), 'export function StudioNavItemInspector' 1 (want 1). PREDICTED BEFORE RUNNING: 6 failures (4 bare-spelling pins + the view pin + the inspector picker pin). OBSERVED: 'Test Files 2 failed (2)' / 'Tests 6 failed | 19 passed (25)', LEG2 EXIT=1, exactly those six by name. Direction as predicted (turn red), no reversal. (3) pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build => BUILD EXIT=0; pnpm --filter @object-ui/app-shell type-check => TYPECHECK EXIT=0 (objectui spells the script with a hyphen; output echoed 'tsc --noEmit && tsc -p tsconfig.test.json', so it was not a zero-match no-op). Exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?), never through tail.",
"gates": {
"vitest (2 targeted files, maxWorkers=1)": "PASS — Tests 25 passed (25)",
"pnpm --filter @object-ui/app-shell type-check": "PASS — exit 0",
"pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build": "PASS — exit 0",
"pnpm check:control-bytes": "PASS — 'check-control-bytes: OK (scanned 4566 tracked text file(s); skipped 85 binary)'",
"pnpm check:phantom-deps": "PASS — 'Every in-scope import is declared by the package that publishes it.'",
"pnpm check:self-import": "PASS — 'No package names itself inside its own src/.'",
"pnpm check:esm-specifiers": "PASS — 'no un-ledgered package emits an extensionless relative specifier'",
"node scripts/check-changeset-presence.mjs": "PASS — '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/studio-nav-canonical-keys.md'",
"eslint (narrowed to the 4 changed files, --format json)": "PASS — 0 errors, 15 warnings, all pre-existing react-hooks/* or unused-var reports on lines this PR does not touch. NARROWING DECLARED: repo-wide `pnpm lint` (turbo run lint) is CI's; the narrowing is sound for this diff because eslint.config.js is NOT type-aware (no projectService, no parserOptions.project), so these files cannot move a verdict in a file they do not import.",
"CI": "in_progress — reported at draft-PR time per the standing rule; PM reads the real job conclusions",
"note_on_gate_derivation": "scripts/pm/dispatch-gates.mjs does not exist in objectui (confirmed); family derived by hand from .github/workflows/ci.yml + lint.yml + changeset-presence.yml. i18n gates not run: no t() call site added or changed, no locale pack touched."
},
"files_changed": [
"packages/app-shell/src/views/studio-design/navSurface.ts",
"packages/app-shell/src/views/studio-design/navSurface.test.ts",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx",
"packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx (new)",
".changeset/studio-nav-canonical-keys.md (new)"
],
"file_surface_respected": "yes — git diff origin/main...HEAD (three-dot) touches only packages/app-shell/src/views/studio-design/** plus the required changeset. No metadata-admin/**, no preview/**, no vite.config.ts, no packages/spec, no packages/types.",
"deviations": [
"ZONE 1 item 3 implemented as ordered (flip, not delete); the surviving `?? node.object` fallback is filed as objectui#5518 rather than removed here.",
"One bounded in-place fix beyond the enumerated three: the stale doc comment above StudioNavItemInspector claiming the picker writes `{ object }`. Same defect class, same file, mechanically pinned by the code below it, named in the PR body.",
"`export` added to StudioNavItemInspector so its pin does not have to drive the whole pillar (file precedent, not re-exported from the package index).",
"Eslint run narrowed to the changed files (declared above with the reason it is sound)."
],
"resource_symptoms": [
"Shared verify lock: one acquisition waited 461s (7m41s) behind another container's full `pnpm exec vitest run packages/app-shell --maxWorkers=2`; that locked command itself held 15s. Later acquisitions: 0s and 66s. Longest hold by me: 209s (build + type-check). No 10-minute foreground cap hit, no OOM.",
"pnpm install in the fresh worktree took 8.1s (store hardlinks), run outside the lock."
],
"platform_observations": [
"Issue-body sanitizer (measured on objectui#5518, this session): the trailing `---` plus a BARE `_Generated by [Claude Code](https://claude.ai/code)_` footer was stripped on creation; the session-URL form survived on the PR body.",
"PR creation APPENDED a second session-URL footer after mine (pull/5526) — cosmetic duplicate, left alone.",
"api.github.com/search/* is blocked by the agent proxy for this session (HTTP 403, 'sessions are bound to their configured repositories'); repos/* endpoints work, and the MCP search tool was used for the duplicate search instead.",
"Measured for the record: zod's strictObject counts an own key whose VALUE is undefined as unrecognized, so the inspector's clear-by-undefined patch (`object: undefined, path: undefined`) fails a direct in-memory parse while the SAVED (JSON-serialized) item parses. Nothing in the Studio parses that draft in memory (validateMetadataDraft has one caller, ResourceEditPage), so this is inert today — the write-side pin uses the serialized shape and says why."
],
"open_questions": [],
"out_of_scope_findings": [
"filed as #5518: after #4881's flip, StudioNavItemInspector still READS the schema-rejected `object` spelling as a fallback — the last one in views/studio-design/**; unassigned, labelled `finding`, no `pm:queue`",
"not filed, flagged for PM: views/metadata-admin/inspectors/nav-target.ts `inferNavItemType` reads the same four bare spellings, but as a documented legacy-normalisation ramp; if PM reads that as the same defect class it needs a card, and it belongs to whoever holds metadata-admin after #5416"
]
}
Activity
os-support-ai commentedon Aug 19, 2026
Triage (concentrated finding round): promoted
finding→pm:queue. Lenient-consumer cleanup restoring the declared invariant (AGENTS.md #0.1): delete the five||legs reading schema-rejected bare spellings, the deadcase 'view'branch, and flip thenode.object ?? node.objectNameorder inStudioNavItemInspector. The card's own pin guidance applies: assert "spec rejects the spelling AND the consumer does not parse it" (borrownavSurface.test.ts's shape) — do not accept a pin that is green on producing nothing.本评论来自分诊座位 Routine (session
session_018euHiF12oV8iSfYdjVioUF).Generated by Claude Code
claude commentedon Aug 21, 2026
{ "issue": 4881, "status": "done", "branch": "claude/issue-4881-studio-nav-lenient-consumer", "pr": "https://github.com/objectstack-ai/objectui/pull/5526", "premise_still_valid": true, "summary": "Deleted the four bare-spelling `||` legs in `resolveSurface` (page/object/dashboard/report), deleted `case 'view'` with its fifth leg (`node.viewName || node.view`), dropped the now-unread `page`/`object`/`dashboard`/`report`/`view`/`viewName` declarations from `NavNode`, and flipped `StudioNavItemInspector` to `node.objectName ?? node.object`. Two pins added, ablation-verified. DEVIATION, declared: the flip leaves the `?? node.object` fallback readable, so a draft carrying `object` ALONE still displays as bound — by the card's own logic that is the last rejected-spelling read in `views/studio-design/**`. A test asserting it should show UNBOUND was written, failed against the flip-only scope ZONE 1 fixed, and was removed rather than widening the card; filed unassigned as objectui#5518 for triage. Also corrected in place (same defect class, same file, one sentence): the doc comment over `StudioNavItemInspector` claimed the picker writes `{ object }` — it has always written `{ type: 'object', objectName }` and cleared `object`. `StudioNavItemInspector` is now exported for its test (file precedent: `DataPillar` and two others); the package index lists exports one by one, so nothing new reaches the public API.", "spec_reverification_17_0_0_GA": { "installed_version_measured": "@objectstack/spec 17.0.0 (node_modules/@objectstack/spec/package.json; pnpm-lock resolves ^17.0.0 -> 17.0.0)", "card_measured": "17.0.0-rc.6", "premise_holds": true, "NavigationItemSchema.safeParse": { "{type:'page',page:'home'}": "REJECT unrecognized_keys:['page'] + invalid_type@['pageName']", "{type:'object',object:'crm_lead'}": "REJECT unrecognized_keys:['object'] + invalid_type@['objectName']", "{type:'dashboard',dashboard:'sales'}": "REJECT unrecognized_keys:['dashboard'] + invalid_type@['dashboardName']", "{type:'report',report:'pipeline'}": "REJECT unrecognized_keys:['report'] + invalid_type@['reportName']", "{type:'object',objectName:'crm_lead',view:'all'}": "REJECT unrecognized_keys:['view']", "{type:'view',viewName:'all'}": "REJECT invalid_union@['type'] — discriminator enumerates object|dashboard|page|url|report|action|component|separator|group, nine members, no 'view'", "canonical page/object/dashboard/report": "PARSES", "{type:'object',objectName:'crm_lead',viewName:'all'}": "PARSES — viewName is a key ON the object item, not a nav type" }, "AppSchema.safeParse": { "app with bare-object nav item": "REJECT unrecognized_keys:['object']@['navigation',0]", "app with type:'view' nav item": "REJECT invalid_union@['navigation',0,'type']" }, "structural_cross_check": "NavigationItemSchema.unwrap().def.options has length 9; discriminators ['object','dashboard','page','url','report','action','component','separator','group'] — measured, not asserted in the test (behavioural rejection is pinned instead, to avoid coupling a pin to zod internals)" }, "sweep": { "method": "repo-wide grep over packages/ and apps/ for `node|item|n|leaf|nav`.(page|object|dashboard|report|view), for `(pageName|objectName|dashboardName|reportName|viewName)\\s*(\\|\\||\\?\\?)`, and for `case '(page|object|dashboard|report|view)':`", "in_scope_population_fixed": [ "navSurface.ts resolveSurface: 5 `||` legs (the 4 bare keys + node.view inside case 'view') — PM's 'five legs' and 'case view' are the same five, not five plus one", "StudioDesignSurface.tsx StudioNavItemInspector: 1 read (node.object ?? node.objectName)" ], "counter_probe_examined_not_touched": [ "packages/layout/src/NavigationRenderer.tsx resolveHref — canonical-only already; no bare-key read", "views/metadata-admin/previews/AppNavCanvas.tsx navKind — cleaned in objectui#3275 with this card's own argument; its comment is the precedent cited in the new code", "views/metadata-admin/inspectors/nav-target.ts inferNavItemType (4 legs) + AppNavInspector.tsx:292 — DELIBERATE legacy-normalisation ramp ('edit is the migration': classify a legacy node so the next edit clears the off-spec keys), different intent from an accidental lenient read; also the directory #5416 just rewrote. Examined, NOT touched, NOT filed — flagging for PM to rule on rather than filing noise", "views/metadata-admin/anchors.ts / registry.ts anchorByField('object'), ResourceEditPage.tsx v?.objectName ?? v?.object ?? v?.object_name — other metadata contracts (validation/flow/chart anchors, `view` metadata), not the nav union", "packages/app-shell/src/views/ObjectView.overrideMasquerade.test.ts:51 sv.objectName || sv.object — list-view masquerade fixture, different contract" ], "no_stale_twin_of_navSurface_found": "the #4019 line-for-line copy left no second resolveSurface; the only `case 'view'` in the repo was this one (plugin-view/ObjectView.tsx:1286 is a form-title switch on a different domain)" }, "pin_shape": { "files": [ "packages/app-shell/src/views/studio-design/navSurface.test.ts (appended, 3 describe blocks)", "packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx (new)" ], "shape": "per-variant PAIR on the SAME `type`: (a) canonical fixture PARSES under NavigationItemSchema AND resolveSurface returns the surface; (b) bare-spelling fixture is REJECTED with an unrecognized_keys issue naming that exact key AND resolveSurface returns null. Plus: `type:'view'` fails the discriminator AND resolves null; `viewName` on an object item PARSES and that leaf still binds as `object`; inspector: the both-keys draft is refused by AppSchema while the picker shows the canonical name, with BOTH names present as options; picker output parses under NavigationItemSchema once serialized.", "why_not_green_on_producing_nothing": "a lone toBeNull() would have passed before the deletion too. Each null is paired with a non-null on the identical `type` where only the key spelling changed, so the null is attributable to the SPELLING rather than to the variant having no surface; and the ablation makes it falsifiable — 6 of the 25 tests go red on the pre-fix reads.", "removed_assertion": "'shows nothing bound when the draft carries the rejected spelling ALONE' — asserted deletion of the inspector fallback, which ZONE 1 did not order; removed, filed as objectui#5518" }, "tests": "All on 9cd3c5f2d (final commit), all heavy runs through /home/user/objectstack/scripts/pm/os-verify-lock.sh, vitest --maxWorkers=1 throughout. (1) pnpm exec vitest run packages/app-shell/src/views/studio-design/navSurface.test.ts packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx --maxWorkers=1 => 'Test Files 2 passed (2)' / 'Tests 25 passed (25)', LEG1 EXIT=0. (2) ABLATION, same lock acquisition: `git show origin/main:navSurface.ts > navSurface.ts` plus a targeted flip of the inspector read back to `node.object ?? node.objectName` (test-only export KEPT, so the ablation cannot pass through an import error); restore by `trap 'git checkout HEAD -- ...' EXIT INT TERM`, and `git diff HEAD` empty afterwards. NO REBUILD NEEDED and none done — both tests import the implementation by RELATIVE SOURCE path and vitest.config.mts aliases every @object-ui/* to src, so no dist is in the resolution path (@objectstack/spec is a prebuilt external dep). MUTATION CONFIRMED ON DISK by counting the anchored text, not by an editor exit code: navSurface 'node.pageName || node.page' 1 (want 1), \"case 'view'\" 1 (want 1), canonical-only 'node.pageName ? { type' 0 (want 0); StudioDesignSurface 'node.object ?? node.objectName' 1 (want 1), 'node.objectName ?? node.object' 0 (want 0), 'export function StudioNavItemInspector' 1 (want 1). PREDICTED BEFORE RUNNING: 6 failures (4 bare-spelling pins + the view pin + the inspector picker pin). OBSERVED: 'Test Files 2 failed (2)' / 'Tests 6 failed | 19 passed (25)', LEG2 EXIT=1, exactly those six by name. Direction as predicted (turn red), no reversal. (3) pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build => BUILD EXIT=0; pnpm --filter @object-ui/app-shell type-check => TYPECHECK EXIT=0 (objectui spells the script with a hyphen; output echoed 'tsc --noEmit && tsc -p tsconfig.test.json', so it was not a zero-match no-op). Exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?), never through tail.", "gates": { "vitest (2 targeted files, maxWorkers=1)": "PASS — Tests 25 passed (25)", "pnpm --filter @object-ui/app-shell type-check": "PASS — exit 0", "pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build": "PASS — exit 0", "pnpm check:control-bytes": "PASS — 'check-control-bytes: OK (scanned 4566 tracked text file(s); skipped 85 binary)'", "pnpm check:phantom-deps": "PASS — 'Every in-scope import is declared by the package that publishes it.'", "pnpm check:self-import": "PASS — 'No package names itself inside its own src/.'", "pnpm check:esm-specifiers": "PASS — 'no un-ledgered package emits an extensionless relative specifier'", "node scripts/check-changeset-presence.mjs": "PASS — '4 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/studio-nav-canonical-keys.md'", "eslint (narrowed to the 4 changed files, --format json)": "PASS — 0 errors, 15 warnings, all pre-existing react-hooks/* or unused-var reports on lines this PR does not touch. NARROWING DECLARED: repo-wide `pnpm lint` (turbo run lint) is CI's; the narrowing is sound for this diff because eslint.config.js is NOT type-aware (no projectService, no parserOptions.project), so these files cannot move a verdict in a file they do not import.", "CI": "in_progress — reported at draft-PR time per the standing rule; PM reads the real job conclusions", "note_on_gate_derivation": "scripts/pm/dispatch-gates.mjs does not exist in objectui (confirmed); family derived by hand from .github/workflows/ci.yml + lint.yml + changeset-presence.yml. i18n gates not run: no t() call site added or changed, no locale pack touched." }, "files_changed": [ "packages/app-shell/src/views/studio-design/navSurface.ts", "packages/app-shell/src/views/studio-design/navSurface.test.ts", "packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx", "packages/app-shell/src/views/studio-design/StudioDesignSurface.navItemInspector.test.tsx (new)", ".changeset/studio-nav-canonical-keys.md (new)" ], "file_surface_respected": "yes — git diff origin/main...HEAD (three-dot) touches only packages/app-shell/src/views/studio-design/** plus the required changeset. No metadata-admin/**, no preview/**, no vite.config.ts, no packages/spec, no packages/types.", "deviations": [ "ZONE 1 item 3 implemented as ordered (flip, not delete); the surviving `?? node.object` fallback is filed as objectui#5518 rather than removed here.", "One bounded in-place fix beyond the enumerated three: the stale doc comment above StudioNavItemInspector claiming the picker writes `{ object }`. Same defect class, same file, mechanically pinned by the code below it, named in the PR body.", "`export` added to StudioNavItemInspector so its pin does not have to drive the whole pillar (file precedent, not re-exported from the package index).", "Eslint run narrowed to the changed files (declared above with the reason it is sound)." ], "resource_symptoms": [ "Shared verify lock: one acquisition waited 461s (7m41s) behind another container's full `pnpm exec vitest run packages/app-shell --maxWorkers=2`; that locked command itself held 15s. Later acquisitions: 0s and 66s. Longest hold by me: 209s (build + type-check). No 10-minute foreground cap hit, no OOM.", "pnpm install in the fresh worktree took 8.1s (store hardlinks), run outside the lock." ], "platform_observations": [ "Issue-body sanitizer (measured on objectui#5518, this session): the trailing `---` plus a BARE `_Generated by [Claude Code](https://claude.ai/code)_` footer was stripped on creation; the session-URL form survived on the PR body.", "PR creation APPENDED a second session-URL footer after mine (pull/5526) — cosmetic duplicate, left alone.", "api.github.com/search/* is blocked by the agent proxy for this session (HTTP 403, 'sessions are bound to their configured repositories'); repos/* endpoints work, and the MCP search tool was used for the duplicate search instead.", "Measured for the record: zod's strictObject counts an own key whose VALUE is undefined as unrecognized, so the inspector's clear-by-undefined patch (`object: undefined, path: undefined`) fails a direct in-memory parse while the SAVED (JSON-serialized) item parses. Nothing in the Studio parses that draft in memory (validateMetadataDraft has one caller, ResourceEditPage), so this is inert today — the write-side pin uses the serialized shape and says why." ], "open_questions": [], "out_of_scope_findings": [ "filed as #5518: after #4881's flip, StudioNavItemInspector still READS the schema-rejected `object` spelling as a fallback — the last one in views/studio-design/**; unassigned, labelled `finding`, no `pm:queue`", "not filed, flagged for PM: views/metadata-admin/inspectors/nav-target.ts `inferNavItemType` reads the same four bare spellings, but as a documented legacy-normalisation ramp; if PM reads that as the same defect class it needs a card, and it belongs to whoever holds metadata-admin after #5416" ] }Generated by Claude Code
docs: cite landing commits where pending changesets and source commen…