Skip to content

[Decision] console: write app navigation when a page or dashboard is created or deleted, instead of diffing the whole page and dashboard lists on every app page #28680

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Filing gate ②: a decision only the maintainer can make. Part of #28645 (the second of its two PRs).

维护者速读

  • 改了什么: 现在每次打开任一应用页面,控制台都会在首屏后拉一遍全部页面和全部仪表板列表,跟上一次比对名字,发现多了或少了就往每个应用的导航里写条目。提议改为在新建、删除、改名页面或仪表板的那一刻,显式地写导航。
  • 为什么改: 这两次列表请求是 console: on an app page, answer /meta/view, /meta/page and /meta/dashboard from the app bundle (#28321) instead of three list reads; page/pricing 24 to ~21 calls #28645(应用页少 3 次请求,cloud 预算 24 → 21)剩下的大头;只要比对还在,应用页就最少 23 次。比对本身还是一条隐式写路径:谁让列表“看起来变了”,它就往所有应用写导航。
  • 风险与回滚: 改的是“什么时候写导航”。漏接一个新建或删除入口,那个入口建出来的页面就不会自动进导航。回滚即恢复比对。
  • 先看: ui/packages/app-shell/src/hooks/useNavigationSync.ts 的 NavigationSyncEffect(约 590–740 行),以及同文件里已存在但无人调用的 sync…AllApps 六个方法(约 215–225、485 行起)。
  • 需要你做的: 选 A / B / C。

Background

NavigationSyncEffect is mounted on every app page (AppContent.tsx:1440). After first paint it reads metadata.pages and metadata.dashboards (two GET /meta/{type} list reads), diffs the name sets against the previous render, and for every added or removed name calls syncPageCreated / syncPageDeleted / syncDashboardCreated / syncDashboardDeleted on every syncable app (useNavigationSync.ts ~707-735). The explicit per-event writers syncPageCreatedAllApps, syncDashboardCreatedAllApps, syncPageDeletedAllApps, syncDashboardDeletedAllApps, syncPageRenamedAllApps, syncDashboardRenamedAllApps exist in the same hook and have no caller (git grep -n AllApps origin/main -- ui/packages/app-shell/src).

Measured by #28645's card thread at origin/main 2678600d; full reading with file:line in the project file issue-28645/needs-decision.md.

Governing text: none found (git grep -n -i "navigation sync" -- AGENTS.md docs/adr ui/AGENTS.md → 0 hits on a write-timing rule; ADR-0037 only disables the diff in preview).

Options

Option What happens Customer-visible Work
A — explicit writes The create / delete / rename sites in the console call the existing sync…AllApps writers; NavigationSyncEffect stops reading the two lists. App pages open with two fewer calls (#28645 reaches ~21). A page or dashboard made outside the console (CLI, package install) is not added to nav by a console visit any more. ui lane, 1 PR, ~3-5 files; no precedent PR
B — keep the diff, scope it Keep the diff, but run it only on routes that create or delete pages/dashboards (Studio, Setup). App pages lose the two calls; the diff still writes nav, from fewer places. ui lane, 1 PR, small
C — no change #28645 lands its first PR only (by-name reads); app pages stay at ~23 calls. Nothing changes. none

Business reading: A is "update the menu when you add the item"; B is "re-count the shelves only in the stock room"; C keeps today's re-count on every visit.

Recommendation: A

Two years out, a mainstream platform writes navigation when the author adds the item (Salesforce Lightning app nav, Airtable interfaces), not by re-listing everything on each visit. A leaves one declared write path and removes an implicit one that writes into every app. Only ① decides: A. ②③④ flip it: no.

Confidence gap: items created outside the console (CLI deploy, package install) would no longer reach nav through a console visit; the card thread checks whether any such path relies on the diff today before writing code.

After a ruling: A or B go to the #28645 card thread as its second PR, Part of #28645; C closes this card not planned and #28645 closes on its first PR.

os-decision-facets

Reader: ui seat 1 (domain:ui#1, #25966) dispatches the ruled option to #28645's card thread.
Dedup: repo:objectstack-ai/objectstack is:issue NavigationSyncEffect → 1 hit, #20290 (closed, a draft-baseline merge issue, not this).

Activity

  1. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

    Ruled: A · 2026-11-01T13:38Z

    The maintainer picked option A on the decision card in this account's project seat thread "Run second ui lane seat" (Claude Code project "objectstack docs ui"), 2026-11-01T13:37Z, verbatim: 「A Write nav at create/delete」. The card's consequence line read: "The console writes nav when a page or dashboard is created, deleted or renamed; app pages stop re-listing them, reaching ~21 calls."

    Claim (the same card thread as #28645)

    Claim: PM loop round 1 · 2026-11-01T13:38Z
    Session: session_01QsR2jUjXYpqdaAkEWBHbqG (card thread, os-dev); dispatched by seat session session_01QsR2jUJ19MstFvFhjgiYTu
    Account: zhuangjianguo
    Branch: claude/project-thread-6p9fk2 (the card thread's only branch; this PR opens after #28645's PR 1 lands, from that branch restarted on origin/main)
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: ui/packages/app-shell/src/hooks/useNavigationSync.ts (NavigationSyncEffect stops reading the page and dashboard lists and diffing them), the console's page and dashboard create / delete / rename sites that call the existing sync…AllApps writers, their tests, and a root .changeset (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: none
    Premise: holds (the six sync…AllApps writers exist in useNavigationSync.ts with no caller on origin/main)
    Serial constraints cleared: #28645 PR 1 (same thread, lands first)

    Before code, the thread checks whether any page or dashboard created outside the console (CLI deploy, package install) relies on the diff to reach navigation today, and reports what it finds.

  2. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · zhuangjianguo · session_01QsR2jUjXYpqdaAkEWBHbqG
    os-dev-report

    {
      "issue": 28680,
      "status": "done",
      "branch": "claude/project-thread-6p9fk2",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28783",
      "session": "session_01QsR2jUjXYpqdaAkEWBHbqG",
      "premise_still_valid": true,
      "summary": "PR 2 of #28645, Fixes #28680 and #28645 (ruling A). NavigationSyncEffect is retired (inert, deprecated export; AppContent no longer mounts it) and reads neither /meta/page nor /meta/dashboard. New useNavigationWrites() keeps the diff's rules (page/dashboard only, no platform artifact, not in preview, writable apps) and is called at ResourceEditPage's first publish (no layer before, unbound) and real delete (no baseline, unbound), and in Publish all for package-less page/dashboard drafts whose layers answered 404 before the publish. syncPageCreated/syncDashboardCreated now skip an app that already references the target. Warm reload on a production build (showcase_app): page 26->24, object list 33->31, dashboard 35->34.",
      "pre_code_check": "No page or dashboard created outside the console relied on the diff: package installs and CLI deploys carry _packageId (showcase_start_here's layers answer _packageId com.example.showcase, _provenance package on the running server) and Studio saves with a package. Lost: package-less items created or deleted outside this tab mid-session (server-side agent, other tab, raw REST); stated in the PR body and the changeset as the seat asked.",
      "tests": "295 test files naming the touched modules, 5 locked batches --maxWorkers=3: 295/295 files, 2493 tests passed. New pins: ResourceEditPage.navigationWrites-28680 (5, incl. a second publish adds no second nav item), usePublishAllDrafts.navigationWrites-28680 (2); useNavigationSync.test rewritten (17); 9868, 11201, 12980 moved to the new door. Ablations each red then restored: 11 of 11. turbo type-check app-shell 65/65 exit 0. eslint --quiet on changed files exit 0. Real-server probe: draft-only page /layers 404, after first publish a layer with resettable false.",
      "gates": "34 derived (dispatch-gates --commands at f31794f6, merged origin/main cd0ec76b), all exit 0; --ran: 34 derived, 34 run, 0 NOT-MEASURED. PR-body gates with GITHUB_EVENT_PATH: check-changeset-no-major 0, check-adr-0087-registration 0, check-closing-keyword-parity 0, check-partof-closing-keyword 0. CI check-runs: not yet reported (PR just opened).",
      "line_budget": "11 files, +826/-378 (under the 3000-line human-merge threshold)",
      "deviations": [
        "dashboard route 34, not the expected 33: AppContent's route gate (routeReads) waits for the dashboard list and the dashboard view reads it; the effect shared that request. Bundle-first dashboard route is outside the claim's surface; suggested follow-up card",
        "Publish all from the draft-preview bar writes no navigation (the door skips preview, where apps may be draft bodies); the diff wrote nothing there either",
        "NavigationSyncEffect kept as an inert @deprecated export rather than deleted, so the app-shell public export list does not change",
        "useNavTargetLabel.ts keeps a stale comment naming NavigationSyncEffect as a dashboard-list loader; outside the surface, left as is",
        "writer dedupe (navigationReferences) added in syncPageCreated/syncDashboardCreated beyond the site gate, for the seat's no-second-entry ask"
      ],
      "files_changed": [
        ".changeset/28680-navigation-writes-at-sites.md",
        "ui/packages/app-shell/src/console/AppContent.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/chromeReads.afterFirstPaint-12980.test.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/useNavigationSync.labelInherits-9868.test.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/useNavigationSync.standardEntryLocalizes-11201.test.tsx",
        "ui/packages/app-shell/src/hooks/__tests__/useNavigationSync.test.tsx",
        "ui/packages/app-shell/src/hooks/useNavigationSync.ts",
        "ui/packages/app-shell/src/preview/__tests__/usePublishAllDrafts.navigationWrites-28680.test.tsx",
        "ui/packages/app-shell/src/preview/usePublishAllDrafts.ts",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.navigationWrites-28680.test.tsx",
        "ui/packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx"
      ],
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "1 read (issue_read get_comments #28680)",
      "api_writes": "2 — fleet-write relay pr_create (draft #28783) and this os-dev-report comment (post-stamped); plus git push",
      "open_questions": [],
      "out_of_scope_findings": [
        "dashboard route still reads /meta/dashboard on every open (AppContent routeReads + dashboard view), the last list read of #28645's target · noted for the seat, not filed"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

    Claim amended · 2026-11-01T19:39Z · file surface widened; it adds to claim 6558711281. Branch, session, size and declaration lines are unchanged.

    • File surface (added): ui/packages/app-shell/src/hooks/index.ts and ui/packages/app-shell/src/index.ts (the retired NavigationSyncEffect is deleted, not kept as an inert deprecated export: @object-ui/app-shell is private: true and nothing outside it imports it), and comment-only rewording where a comment still names it as a live writer or reader: hooks/useNavTargetLabel.ts, chrome/CommandPalette.tsx, preview/PreviewModeContext.tsx, ui/packages/react/src/context/AppShellContext.tsx.
    • Follow-up filed: the dashboard route's own /meta/dashboard list read (34 → 33) is console: the dashboard route reads its dashboard from the app bundle, so an app's dashboard open stops reading the /meta/dashboard list #28784, out of this PR.

    Generated by Claude Code

  4. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

    Landed · 2026-11-01T20:47Z · PR #28783 (PR 2 of 2) merged through the merge queue as c064d3a2 at 2026-11-01T20:45:24Z (GitHub's clock). ACCEPT 6563459505 at head afd4ebd6. PR 1 was #28754 (706ac52d).

    probe before (c064d3a2~1) now (origin/main)
    NavigationSyncEffect in console/AppContent.tsx 2 0
    useNavigationWrites in metadata-admin/ResourceEditPage.tsx 0 2

    Measured by the dev on a production build (showcase_app, warm reload): page 26 → 24, object list 33 → 31, dashboard 35 → 34. The dashboard route's last /meta/dashboard read is #28784. Fixes #28645 and Fixes #28680 closed both cards; pm:dispatched comes off in the same stroke as this note.


    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:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uienhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions