Skip to content

Console: creating a record page adds it as a top-level navigation entry to every app, and the write into a sealed managed app fails with a red "Failed to update navigation" toast #29182

Closed

Description

@objectstack-fleet

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 → 403 NOT_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

  1. Showcase boot, signed in as admin. Optionally install a scratch package with its own app so you can see both outcomes.
  2. Open /_console/studio/~all/metadata/page/new. Enter Label "R3 Nav Probe Page", Page type record, Object showcase_task. Save, then Publish.
  3. Network: POST /api/v1/meta/page/r3_nav_probe_page/publish → 200, then the app PUTs above.
  4. Expected: a record page is not added to any app's navigation. No write is attempted into an app the server seals.
  5. 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), calls syncPageCreatedAllApps(name) for every created page, whatever the page's own type.
  • isNavigationSyncableApp (around L205) skips only apps whose _lock / protection.lock is full or no-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

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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.

    • Read fresh. The fix lands in ui/packages/app-shell/src/hooks/useNavigationSync.ts: skip pages whose type needs a record, and treat a server-sealed managed-package app as not syncable in isNavigationSyncableApp. This is a user-visible behaviour defect (stray nav entries plus a red error toast) observed beside the clauses, not a FAIL, so it is p2. It names a location and a 2/2 repro, so pm:queue.

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 session session_01QsR2jR7ZFkpqmm4U8S1f9y
    Account: zhuangjianguo
    Branch: claude/project-thread-kw6y7h (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29182
    Domain: domain:ui
    Seat: domain:ui#4
    File surface: ui/packages/app-shell/src/hooks/useNavigationSync.ts (the created-page sync and isNavigationSyncableApp), 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: default
    Clause-②: 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 page
    Thread-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

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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 on main (14208f2c):

    • ResourceEditPage passes the published body, which carries the page type.
    • usePublishAllDrafts ("Publish all") passes no body, because it holds only the _drafts header rows.

    To cover both, useNavigationSync.ts reads 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 gains getItem, and one new case checks that a record page published through "Publish all" writes no app. usePublishAllDrafts.ts itself is unchanged.


    Generated by Claude Code

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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."
      ]
    }
  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#4 · zhuangjianguo · session_01QsR2jR7ZFkpqmm4U8S1f9y

    Landed · 2026-11-02T12:23Z · PR #29294 merged through the merge queue as 2f7977cd. Record: ACCEPT 6576056330 at 3c9ee893 (Clause-②: no, no contract review owed).

    probe before (2f7977cd~1) now (origin/main)
    export function isNavigablePage in ui/packages/app-shell/src/hooks/useNavigationSync.ts (a record page joins no app) 0 1
    if (a._provenance === 'package') return false; in the same file (a sealed managed app is not synced) 0 1

    The card closed through Fixes. pm:dispatched comes off with this note. Ruling A of #28680 stands for navigable pages.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:uipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions