Skip to content

fix(console): the docs portal renders the book resolver's answer; the Uncategorized pre-filter is retired (objectui#11340) - #11617

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11340-book-tree-prefilter
Oct 4, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-11340-book-tree-prefilter

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #11340

Clause-②: no

What changed

The portal now renders the book resolver's answer, with no pre-filter in front of it. This follows the triage ruling in comment 5925851232: one authority for book membership (ADR-0046 §6.4).

No export, prop, type member or i18n key of any @object-ui/* package is added or removed.

Landing site, and why the route differs from the suggested one

The landing site is the expected one: book-nav.ts, BookPage.tsx, DocPage.tsx and the tests beside them.

Mechanism assumption 2 is half true. The portal does call its resolver with two arguments. But that resolver is the console's local port: the book-nav.ts header calls it "a faithful local port of the framework's resolveBookTree". It is not the spec's export. The port already defaults its scope to book.packageId, so passing the book's package at each call site, the suggested route, changes nothing.

The hazard is real, by another mechanism. Upstream's fix never reached the port, so the port's orphan pass was unscoped. Deleting the pre-filter alone widens Uncategorized to every package's docs (column (b) below). The fix ports upstream's orphan scope into the local resolver, then deletes the pre-filter.

Why port it rather than call the spec's export. The port and the spec read a doc's package differently.

  • The port reads it through pkgOf: the _packageId stamp, else the prefix of the doc's name.
  • The spec reads the stamp only, and treats an unstamped doc as inside every scope.

The port also marks the synthetic group (synthetic), which countBookDocs and findBookContainingDoc read. Calling the spec's export would also change what unstamped docs do in include, a visible change the ruling does not cover. Ported with pkgOf, an unstamped doc behaves as it did under the pre-filter.

Measured: before and after

Probe at base fd060f0767, with the installed @objectstack/spec 17.6.0. Every doc carries a packageId, except in the last row. The columns:

  • (a) the portal at base: pre-filter, then port;
  • (b) the port alone, with no pre-filter, at base;
  • (c) the spec's resolveBookTree, with the book's package as its third argument.
Shape (a) portal at base (b) port alone (c) spec 17.6.0 This PR
Control: crm book, group start includes crm_intro; docs crm_intro, crm_stray (crm), ops_keys (ops) Uncategorized: crm_stray Uncategorized: crm_stray, ops_keys Uncategorized: crm_stray = (c)
Corner 1: group ext with package: b; unplaced b_note (b) and c_note (c) Uncategorized: b_note b_note, c_note b_note = (c), unchanged
Corner 2: a group whose pages are crm_intro, ops_keys; ops_keys is labelled Keys ops_keys, no label ops_keys labelled Keys; ops_other in Uncategorized ops_keys labelled Keys = (c)
A book with no package; groups all (include: '*') and ext (package: b) all: b_1 all: a_1, b_1 all: a_1, b_1 = (c)
Another package's doc whose group names a pages group that has no ... in Uncategorized in Uncategorized absent = (c)
An unstamped doc ops_unstamped beside a crm book absent in Uncategorized in Uncategorized absent, as (a); see Acceptance notes
  • Corner 1 is not a visible change. The card's premise, that the pre-filter drops a group package's unplaced docs, is false: bookPackages included each group's package.
  • Corner 2 and the next two rows are visible changes. The changeset names all of them, and Corner 1 as unchanged.

Tests

  • book-nav.uncategorizedScope-11340.test.ts (new). The control pair, Corner 1, Corner 2, a package-less book, and the two further shapes. Every case also asserts that the portal's full tree equals the spec's resolveBookTree over the same docs, scoped by the book's package. That covers every group, Uncategorized included.
  • docs-portal.uncategorizedScope-11340.test.tsx (new). The rendered sidebar, through the real route table (DocsLayout, DocPage, BookSidebar). It pins the control pair, Corner 1, and Corner 2, whose link is named by the doc's own label.
  • book-nav.explicitGroup-11245.test.ts, re-judged. portalTree is now resolveBookTree(b, all). Everything it pins about explicit group placement stands. Its controls are now held by the resolver's own orphan scope: ablation A below turns them red.
  • book-nav.test.ts, re-judged. Its package-scope case pinned the old unscoped orphan: b_x sat in the Uncategorized group of a book that draws from package a alone. It now pins the resolver's answer, which has no Uncategorized group. The "drops nothing" case is renamed to say it concerns a book that declares no package.

Runs on the merge commit 81136e7 (origin/main b403bb36f merged in):

  • The five book-nav and docs-portal files: 37 passed on a36dbd7, before the merge, and again inside the full console run below.
  • pnpm exec vitest run apps/console/ (the root spelling): 147 test files passed, 1761 tests passed, exit 0.
  • pnpm --filter @object-ui/console type-check after a turbo build of @object-ui/console^...: exit 0. The build was 34 of 34 tasks. --listFiles shows all seven touched files in the run.
  • pnpm --filter @object-ui/console lint: exit 0, 0 errors. The warnings in DocPage.tsx are on lines this PR does not touch.

Reverse verification. The base implementation of book-nav.ts, BookPage.tsx and DocPage.tsx was put back from fd060f0767, with the new pins kept. Result: 4 failed, 5 passed.

  • Red: the unit control, unit Corner 1 and unit pages-group case, and the rendered Corner 2, whose link was named ops_keys rather than Keys.
  • Still green: the rendered control and rendered Corner 1. That is the expected direction: the pre-filter already produced those two answers on base.
  • Restore: each blob equals HEAD, and git diff HEAD is empty.

Ablations on the committed fix a36dbd7, through objectstack's scripts/ablation-replace.mjs. In each, the anchor went from 1 hit to 0, the blob changed, the file was restored to equal HEAD, and git diff HEAD was empty.

  • A: the orphan-scope filter line deleted. 8 failed, 29 passed across the five files. Red: both new controls, unit Corner 1, the pages-group case, both 11245 controls, the 11245 group-package case, and the book-nav.test.ts package-scope case.
  • B: group packages dropped from the orphan scope. 3 failed: unit Corner 1, rendered Corner 1, and the book-nav.test.ts package-scope case.

Gates, run locally, each exit 0:

  • node scripts/check-changeset-presence.mjs and node scripts/check-changeset-no-major.mjs;
  • pnpm check:changeset-claims and pnpm check:pending-changeset-literals;
  • pnpm check:control-bytes and pnpm check:new-line-citations;
  • pnpm check:test-path-roots, pnpm check:vi-mock-specifiers, pnpm check:vi-mock-inherit and pnpm check:vi-mock-override-shape;
  • pnpm check:installed-pin-claims and pnpm check:unreferenced-sources.

node scripts/check-governed-queue-guard.mjs --test answers NOT GOVERNED for all eight paths. CI runs the full farm.

Acceptance notes

  1. Unstamped docs (observation; not filed; no carrier). The port reads a doc's package with pkgOf, which falls back to the name's prefix. The spec treats a doc with no _packageId as inside every scope, in include and now in the orphan pass. The include half of that divergence predates this PR. This PR keeps the portal's reading for orphans, so nothing changes for unstamped docs. Whether runtime-saved docs reach the portal without _packageId was not measured here.
  2. Doc editor hint (observation; not filed; no carrier). The doc editor preview in packages/app-shell says "Not in any book section yet — book trees list it under Uncategorized." Since spec 17.6.0, a doc whose package has no book is in no book's Uncategorized group. This is read from the code and was not measured at the editor.
  3. Gate spelling. The dispatch spelled the typecheck gate pnpm --filter @object-ui/console typecheck. The console's script is type-check, and a filtered run that matches no script exits 0. This PR ran type-check.

Generated by Claude Code

claude added 2 commits October 4, 2026 14:29
… Uncategorized pre-filter is retired

The console's port of `resolveBookTree` now scopes its synthetic Uncategorized
group to the book's own packages (the book's, and each group's `package`),
asked through `include`'s own scope test, as the framework's resolver does
since objectstack#20980. `scopeDocsToBook` and `bookPackages` are deleted;
`BookPage`, `DocPage`, `countBookDocs` and `findBookContainingDoc` resolve over
every doc they read.

Pins: the control pair, corner 1 (a group's `package`), corner 2 (a
`pages`-pinned doc of another package), and the two further shapes whose
portal answer moves to the resolver's, each checked against the spec's
`resolveBookTree`; and the rendered sidebar through the real route table.

Claude-Session: https://claude.ai/code/session_015W8GBu6sBiqus2L2xjMsAL
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 331 chunks) 3313.9 KB 3330.4 KB
Main entry chunk (gzip) 151.4 KB 350 KB
Entry file index-BH1unbXw.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 17.22KB 6.37KB
app-shell (runtime-config.js) 22.52KB 7.86KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.11KB 3.87KB
auth (ActiveOrganizationStorage.js) 27.95KB 10.04KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.22KB 10.61KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.17KB 5.40KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.72KB 2.24KB
auth (SocialSignInButtons.js) 9.70KB 3.93KB
auth (UserMenu.js) 3.39KB 1.21KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.70KB 10.94KB
auth (createAuthenticatedFetch.js) 8.54KB 3.46KB
auth (index.js) 3.63KB 1.64KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 27.11KB 7.97KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.28KB 2.60KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.50KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 572.97KB 137.34KB
core (index.js) 10.00KB 3.96KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 232.57KB 64.51KB
fields (index.js) 262.75KB 66.62KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 2.59KB 1.22KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 8.87KB 3.64KB
i18n (index.js) 5.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 39.35KB 12.88KB
i18n (translateFn.js) 0.20KB 0.18KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 35.66KB 9.49KB
i18n (useSafeTranslation.js) 7.14KB 2.92KB
layout (index.js) 39.47KB 11.25KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.86KB 5.00KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.52KB 2.26KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.33KB 3.07KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 16.04KB 3.92KB
plugin-calendar (index.js) 53.17KB 15.46KB
plugin-charts (index.js) 84.26KB 23.05KB
plugin-chatbot (index.js) 198.22KB 46.97KB
plugin-dashboard (index.js) 143.54KB 38.79KB
plugin-designer (index.js) 231.41KB 48.84KB
plugin-detail (index.js) 245.74KB 64.59KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 176.65KB 45.50KB
plugin-gantt (index.js) 179.16KB 45.06KB
plugin-grid (index.js) 235.92KB 64.87KB
plugin-kanban (index.js) 50.06KB 15.74KB
plugin-list (index.js) 116.72KB 29.10KB
plugin-map (index.js) 25.60KB 8.62KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 44.12KB 12.29KB
plugin-timeline (index.js) 38.80KB 11.71KB
plugin-tree (index.js) 14.51KB 5.15KB
plugin-view (index.js) 90.23KB 22.73KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.81KB 3.58KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 120.63KB 39.56KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.50KB 2.06KB
react (schema-input.js) 4.31KB 2.07KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (body-dialect.js) 4.50KB 1.99KB
sdui-parser (codegen.js) 9.45KB 3.76KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 6.06KB 2.68KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.84KB 1.90KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 21.42KB 7.05KB
types (ai.js) 4.39KB 2.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 4.12KB 1.61KB
types (authoring-nodes.js) 0.20KB 0.19KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (cloud.js) 0.20KB 0.18KB
types (complex.js) 4.44KB 2.07KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (dashboard-widget-layout.js) 2.06KB 0.96KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 1.13KB 0.65KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 5.36KB 2.51KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 5.00KB 2.39KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 2.52KB 1.31KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 4.99KB 1.96KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 19.93KB 7.25KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

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

Projects

None yet

2 participants