Description
domain:devx#1 · os-warren · session_01JQwxbXSZs9PBUc2H8z6mtB
Filing gate: ① product defect with a named landing point and a reproduction (reach: Studio page creation and publish, network trace, 2 runs).
Business reason: an admin creates a record page (a page that renders one record of an object) in Studio. The console then writes it into the navigation of every app, including apps of other packages. A record page is not navigable on its own, so each app gains a menu entry that opens a page with no record. On the stock showcase, the write into the sealed managed app is refused 403, and the author sees a red error toast on every record page they create. This came with ruling A on #28680 (write navigation at create time, PR #28783). The ruling chose when to write; it did not ask for record pages or sealed apps to be written.
Reader who acts: the ui lane (ui/packages/app-shell/src/hooks/useNavigationSync.ts). Do not sync a page whose type needs a record (record, and any other non-navigable type). Treat an app the server seals as a managed-package item as not syncable, as isNavigationSyncableApp already does for _lock full / no-overlay.
Dedupe: "Failed to update navigation" 0 hits; useNavigationSync 8 (#28680 [Decision] closed, the ruling; #28645 closed, its parent; #19049, #20290, #23254 closed, other shapes); navigation sync record page 117 (run records and phone-width cards). No card covers it.
QA-source: #29172 · studio-authoring.record-page-roundtrip · observed beside the clauses
What happens
Creating and publishing a record page sends, after the publish, one app PUT per app. Each appends {type:'page', pageName:'NAME'} to that app's navigation:
PUT /api/v1/meta/app/rthree_app(a scratch package's app) → 200. The record page is now a top-level entry there.PUT /api/v1/meta/app/showcase_app→ 403NOT_OVERRIDABLE("provided by a managed package and is sealed").
Toasts: "Navigation updated: added page …" and "Failed to update navigation".
Measured at framework 7fa9873f (main, 2026-11-02) on a stock showcase boot with one scratch package installed, Console built from the same tree.
Reproduction
- Showcase boot, signed in as admin. Optionally install a scratch package with its own app so you can see both outcomes.
- Open
/_console/studio/~all/metadata/page/new. Enter Label "R3 Nav Probe Page", Page typerecord, Objectshowcase_task. Save, then Publish. - Network:
POST /api/v1/meta/page/r3_nav_probe_page/publish→ 200, then the app PUTs above. - Expected: a record page is not added to any app's navigation. No write is attempted into an app the server seals.
- Actual: it is appended to every writable app's navigation, and the sealed app's refusal surfaces as an error toast.
Reproduced 2/2 (invoice_page_r3a on showcase_contact, r3_nav_probe_page on showcase_task).
Mechanism (read at 7fa9873)
ui/packages/app-shell/src/hooks/useNavigationSync.ts,useNavigationWrites().created(around L655), callssyncPageCreatedAllApps(name)for every createdpage, whatever the page's owntype.isNavigationSyncableApp(around L205) skips only apps whose_lock/protection.lockisfullorno-overlay. It does not skip an app the server refuses as a sealed managed-package item (NOT_OVERRIDABLE). Its own comment says such attempts "surfaced as a red 'Failed to update navigation' toast" and should not be made.
Generated by Claude Code
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: studio-authoring.record-page-roundtrip
Triage:
domain:ui· p2 ·pm:queue· 2026-11-02T09:02Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim: PM loop round 11 · 2026-11-02T09:42Z
Session:
session_01QsR2jUbgX5dtoeHsDmfa9Z(card thread, os-dev); dispatched by seat sessionsession_01QsR2jR7ZFkpqmm4U8S1f9yAccount:
zhuangjianguoBranch:
claude/project-thread-kw6y7h(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-29182Domain:
domain:uiSeat:
domain:ui#4File surface:
ui/packages/app-shell/src/hooks/useNavigationSync.ts(the created-page sync andisNavigationSyncableApp), its tests, and a changeset (stop on breach; explain in the report)Container & model: S,
mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: defaultClause-②: no
Metadata-protocol: none
Responsibility:
creating a record page in Studio adds it as a top-level navigation entry to every app, and the write into a sealed managed app fails with a red toast | the hook already skips locked apps and already knows the page it created | every admin who creates a record pageThread-read: 6573165152
Premise: holds (at
7fa9873f, QA measured 2/2: one app PUT per app after publish, and 403 NOT_OVERRIDABLE for the sealed showcase app)Serial constraints cleared: none in flight on this file (every open PR checked)
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Claim amended · 2026-11-02T09:47Z · the file surface widens by one test file; the branch, session and declaration lines are unchanged.
Clause-②: no
Metadata-protocol: none
The navigation door
created()has two callers onmain(14208f2c):ResourceEditPagepasses the published body, which carries the pagetype.usePublishAllDrafts("Publish all") passes no body, because it holds only the_draftsheader rows.To cover both,
useNavigationSync.tsreads the page by name (client.meta.getItem('page', name)) when no body is held.File surface (added):
ui/packages/app-shell/src/preview/__tests__/usePublishAllDrafts.navigationWrites-28680.test.tsx. Its adapter mock gainsgetItem, and one new case checks that a record page published through "Publish all" writes no app.usePublishAllDrafts.tsitself is unchanged.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jUbgX5dtoeHsDmfa9Z
os-dev-report
{ "issue": 29182, "status": "done", "branch": "claude/project-thread-kw6y7h", "pr": "https://github.com/objectstack-ai/objectstack/pull/29294", "head": "3c9ee893", "session": "session_01QsR2jUbgX5dtoeHsDmfa9Z", "premise_still_valid": true, "summary": "useNavigationWrites().created now writes a page into app navigation only when isNavigablePage(body) holds: a record page, or a body with no type (PageSchema default 'record'), writes no app. The page type comes from the body the create holds (ResourceEditPage's lay.effective); Publish all holds none, so the door reads the page by name once (client.meta.getItem), and a failed read skips that page's write and toasts navigationSync.updateFailed (seat ruling, claim amended). isNavigationSyncableApp also skips an app whose read carries _provenance 'package': measured on the stock showcase, showcase_app carries it with no _lock and answers 403 NOT_OVERRIDABLE, while a package-less app (no stamp) and a runtime package's app (_packageId only) take the write. The read carries a marker, so no 403 branch was added. Non-navigable page types found: record only (nav-target.ts isStaticPageOption docblock; page.zod.ts checkPageRecordBindingPlacement docblock; default-record-page.ts PAGE_TYPE_DEFAULT).", "tests": "head 92a066d9: touched 2 files 30/30; 9 files naming useNavigationSync/useNavigationWrites/isNavigationSyncableApp 77/77; 76 files naming usePublishAllDrafts/publishAll + ResourceEditPage* 419/419; turbo type-check @object-ui/app-shell 65/65. Merged head 3c9ee893 (36 main commits, none on touched files, app-shell closure rebuilt 65/65): 33 related files 277/277. Ablations via ablation-replace.mjs, committed first, each restored to HEAD blob 06b76092f89a with empty git diff HEAD: record-page guard -> 3 red; _provenance line -> 2 red; failed-read branch -> 1 red.", "gates": "dispatch-gates --commands (34 families) at 92a066d9: all 34 exit 0; dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED, 0 UNRUN. vitest run scripts/: 169 passed, 2 skipped. Re-run at 3c9ee893: check-sdui-manifest, check-changeset-no-major, check-closing-keyword-parity, check-adr-0087-registration, check:nul-bytes, check-partof-closing-keyword: all exit 0. PR check-runs: in_progress at report time.", "line_budget": "4 code/test files + 1 changeset; under 3000 changed lines (about 300)", "files_changed": [ "ui/packages/app-shell/src/hooks/useNavigationSync.ts", "ui/packages/app-shell/src/hooks/__tests__/useNavigationSync.test.tsx", "ui/packages/app-shell/src/hooks/__tests__/useNavigationSync.labelInherits-9868.test.tsx", "ui/packages/app-shell/src/preview/__tests__/usePublishAllDrafts.navigationWrites-28680.test.tsx", ".changeset/29182-record-page-no-nav-sync.md" ], "deviations": [ "useNavigationSync.labelInherits-9868.test.tsx edited (a useNavigationSync test, read as inside 'its tests'): its page body gains type 'app' because a type-less body is now a record page.", "A failed by-name read also shows the existing updateFailed toast, beside skipping the write, so the skip is not silent.", "Commit trailers: harness-required Co-Authored-By form replaced by the repo's model-free pair, per AGENTS.md." ], "dogfood_battery": "n/a — no ! in the PR title and Clause-② no without (narrowing)", "mcp_calls": "0", "api_writes": "2 — fleet-write pr_create (#29294); this post-stamped report comment on #29182", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in PR Acceptance notes, not filed: apps that an earlier record-page create wrote keep their stray entries; this PR stops new ones only." ] }objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Seat review · 2026-11-02T11:31Z · PR #29294 at
3c9ee893: ACCEPT (on the PR;Clause-②: no, no contract review owed). It goes to the merge queue now.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y
Landed · 2026-11-02T12:23Z · PR #29294 merged through the merge queue as
2f7977cd. Record: ACCEPT 6576056330 at3c9ee893(Clause-②: no, no contract review owed).2f7977cd~1)origin/main)export function isNavigablePageinui/packages/app-shell/src/hooks/useNavigationSync.ts(arecordpage joins no app)if (a._provenance === 'package') return false;in the same file (a sealed managed app is not synced)The card closed through
Fixes.pm:dispatchedcomes off with this note. Ruling A of #28680 stands for navigable pages.Generated by Claude Code