Skip to content

perf(console): write app navigation at page and dashboard create and delete sites - #28783

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6p9fk2
Nov 1, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-6p9fk2

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Steedos · project thread

Fixes #28680
Fixes #28645

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

NavigationSyncEffect read the whole /meta/page and /meta/dashboard lists on every app page, after the first paint, only to diff their name sets and add or remove navigation entries. Maintainer ruling A on #28680 retires it. The console's sites now write navigation themselves, through a new door in useNavigationSync.ts:

  • useNavigationWrites() returns created(type, name, item?) and deleted(type, name, item?). They call the existing sync…CreatedAllApps / sync…DeletedAllApps writers and keep the diff's rules: only a page or a dashboard; never a platform artifact (isPlatformArtifact: a sys_ name, or _packageId / _provenance: 'package'); nothing in draft preview (ADR-0037, where apps may be draft bodies); only into apps that may be written (isNavigationSyncableApp). Entries are written label-less, as before (objectui#9868).
  • The metadata editor's Publish (ResourceEditPage doPublish): when the item held no layer before the publish (its draft was its only layer, /layers 404) and the editor is not bound to a package, the page or dashboard is created and joins navigation. A later publish of the same item is an update and writes nothing.
  • The metadata editor's Delete (runReset with no baseline): an unbound real delete removes the item from navigation. A reset to a packaged baseline writes nothing.
  • Publish all (usePublishAllDrafts): before publishing each package-less page or dashboard draft, it asks the item's layers. The ones that had none are created and join navigation after the batch. A failed layers read is not taken for a create.
  • A create no longer duplicates an entry. syncPageCreated / syncDashboardCreated skip an app whose navigation already references the target at any depth (navigationReferences). addNavigationItem itself still appends.
  • NavigationSyncEffect stays exported, @deprecated, and renders nothing. AppContent no longer mounts it.

No rename site exists in the console today, so the sync…RenamedAllApps writers keep no caller.

Who relied on the diff (the claim's pre-code check)

Nothing created outside the console. The diff dropped every platform artifact, and package installs and CLI deploys carry _packageId (checked on the running showcase: showcase_start_here's layers answer _packageId: 'com.example.showcase', _provenance: 'package'). Studio always saves with a package. The diff acted only on package-less pages or dashboards that appeared or vanished in this tab's lists mid-session, and its baseline re-seeded on every load.

What no longer reaches navigation: a package-less page or dashboard created or deleted outside this tab (a server-side agent, another tab, a raw REST call). It now reaches navigation only through its own writer. The changeset says the same.

Tests

  • New ResourceEditPage.navigationWrites-28680.test.tsx (5 cases, the real page): the first publish adds the page to the app once and a second publish adds no second entry; publishing an item that already had a layer writes nothing; a package-bound publish writes nothing; a real delete removes the entry; a reset leaves navigation alone.
  • New usePublishAllDrafts.navigationWrites-28680.test.tsx (2 cases, real MetadataClient): new package-less page and dashboard join; an updated page, a packaged page and a flow do not; layers are asked before each publish; a failed layers read writes nothing.
  • useNavigationSync.test.tsx: the diff's cases are replaced by useNavigationWrites cases (platform artifacts in both directions, other types, draft preview, locked apps, a second create of a referenced page or dashboard), navigationReferences, and a pin that NavigationSyncEffect reads neither list and writes nothing.
  • labelInherits-9868, standardEntryLocalizes-11201 and chromeReads.afterFirstPaint-12980 now drive the new door, or pin that the retired effect reads nothing.
  • Ablations, each one red: always-first-publish, publish ignoring the package binding, no create write, no delete write, delete on reset too, publish-all without the layers check, publish-all without the write, writer without dedupe, no preview skip, no platform skip, no type filter (11 of 11).

Acceptance notes

Measured on a production console build (pnpm build, pnpm console:build, pnpm dev -- --fresh), showcase_app, admin, API calls of a warm reload, two runs each with the same count:

Route Base 2678600d PR 1 (#28754) This PR Difference from PR 1
page/showcase_start_here 26 26 24 /meta/page −1, /meta/dashboard −1
dashboard/showcase_ops_dashboard 35 35 34 /meta/page −1
showcase_task (object list) 33 33 31 /meta/page −1, /meta/dashboard −1

On the running server, a page saved as a draft answers /layers 404 and its first publish turns it into a layer with resettable: false, which is the editor's create and delete verdict this PR reads. The probe page was deleted afterwards.

Deviations:

  • The dashboard route is 34, not the expected 33. Its own route gate in AppContent waits for the dashboard list (routeReads), and the dashboard view reads its definition from it. The effect shared that one request, so removing the effect saves only /meta/page there. Reading the dashboard route bundle-first, as PR 1 did for the page route, would remove it. That is AppContent's route gate and the dashboard view, outside this claim's surface, so it is left for a follow-up card.
  • Publish all from the draft-preview bar writes no navigation. The door writes nothing in preview, where apps may be draft bodies. The diff wrote nothing there either.
  • AppContent.tsx changes only to drop the mount. useNavTargetLabel.ts keeps a comment that says the console's NavigationSyncEffect loads the dashboard list; it is outside the surface and left as it is.

Verification

Derived gates (34)

Run at f31794f6 (this PR merged with origin/main at cd0ec76b). dispatch-gates --ran: 34 derived, 34 run, 0 NOT-MEASURED.

node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-sdui-manifest.mjs :: exit 0
node scripts/check-sdui-manifest.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --dir ui exec vitest run scripts/ :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-changeset-deadline-census :: exit 0
pnpm check:published-files :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
Tests and type-check
  • 295 test files naming the touched modules (useNavigationSync, ResourceEditPage, usePublishAllDrafts, AppContent, DraftPreviewBar, HomePage), 5 locked batches at --maxWorkers=3: 295 passed, 2493 tests. The full app-shell suite is left to CI's Test UI shards.
  • pnpm turbo run type-check --filter=@object-ui/app-shell: 65/65 tasks, exit 0.
  • eslint --quiet over the changed files: exit 0.
  • Merged origin/main at cd0ec76b with no overlap on touched files and no parallel fix found.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (run as eslint --quiet over the changed ui files: 0 errors; the whole-workspace lint is left to CI)
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (not touched)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (except the 维护者速读 block the dispatch rules require)
  • The Before Submitting list in CONTRIBUTING.md is done.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QsR2jUjXYpqdaAkEWBHbqG

…delete sites

NavigationSyncEffect read the whole /meta/page and /meta/dashboard lists on
every app page to diff their name sets (maintainer ruling A on #28680
retires it). The metadata editor's first Publish and real Delete of a
package-less page or dashboard, and Publish all's creates, now write app
navigation through useNavigationWrites, which keeps the diff's rules:
platform artifacts never write, write-protected apps are skipped, nothing
is written in draft preview. A create no longer duplicates an entry an app
already holds. NavigationSyncEffect stays exported as an inert, deprecated
component.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUjXYpqdaAkEWBHbqG
…mments naming it a live writer

@object-ui/app-shell is private and nothing outside it imported the effect,
so the inert export goes: useNavigationSync.ts, both index files, the
AppContent test's stale mock and the effect's pins. The comments that named
it as a live writer (useNavTargetLabel, CommandPalette, PreviewModeContext,
the react package's getTypeStatus doc, two test fixtures) now name the
create sites.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsR2jUjXYpqdaAkEWBHbqG
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

席位核对 · ACCEPT · 2026-11-01T20:28Z

Head-sha: afd4ebd6

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

Labels

documentation Improvements or additions to documentation size/xl tooling

Projects

None yet

1 participant