Skip to content

docs(components): navigation-overlay's file-header @example reads the shared record source (objectui#7787) - #7797

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-7787-navigation-overlay-example
Sep 5, 2026
Merged

os-sam merged 1 commit into
mainfrom
claude/issue-7787-navigation-overlay-example

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #7787

navigation-overlay.tsx's file-header block documents NavigationOverlay, and its @example still taught objectName: schema.objectName — the exact spelling PR #7648 removed from useNavigationOverlay's own doc block. That earlier change never reached this file: different file, different package, a block documenting a different symbol. With the three call sites (plugin-calendar / plugin-grid / plugin-tree) already going through the shared reader, this example was the last place in packages/ still teaching the retired spelling — and an example is what the next call site is copied from.

Two edits, in one PR, because neither is acceptable alone: edit 1 alone leaves the ledger naming a file whose defect is gone (the gate reddens on exactly that), and edit 2 alone silently erases the gate's only true positive.

Edit 1 — the prose (comment only)

packages/components/src/custom/navigation-overlay.tsx, file-header block:

+ * What the hook is handed as `objectName` is the block's RECORD SOURCE — the
+ * object the clicked rows actually came from — read through the ONE shared
+ * reader `resolveRecordSourceObjectName` from `@object-ui/core`
+ * (objectui#7627), and never a bare top-level `schema.objectName` in its place
+ * (objectui#7638). `useNavigationOverlay`'s own doc block carries that rule in
+ * full, including the caller with NO data config whose `schema.objectName`
+ * already IS its record source; this example follows that block rather than
+ * restating it, because a ruling written out twice is a ruling one of whose
+ * copies rots — which is exactly what happened to this example (objectui#7787).
   *
   * @example
   * ```tsx
+ * import {
+ *   resolveRecordSourceConfig,
+ *   resolveRecordSourceObjectName,
+ * } from '@object-ui/core';
   * import { useNavigationOverlay } from '@object-ui/react';
   * import { NavigationOverlay } from '@object-ui/components';
   *
+ * const dataConfig = useMemo(() => resolveRecordSourceConfig(schema), [schema]);
   * const nav = useNavigationOverlay({
   *   navigation: schema.navigation,
- *   objectName: schema.objectName,
+ *   objectName: resolveRecordSourceObjectName(schema, dataConfig),
   * });

Two wording decisions, both made by reading rather than inventing — the defect class here is one ruling written out twice with one copy rotting, so the example defers to useNavigationOverlay's doc block instead of carrying a second explanation of the ladder:

  • the sentence follows the hook's own doc block, including its carve-out for a caller with no data config;
  • dataConfig is spelled resolveRecordSourceConfig(schema) because that is what the live call sites write (ObjectCalendar.tsx:242, ObjectTree.tsx:359), and it is importable from the same @object-ui/core as the reader. The hook's doc block spells this slot getDataConfig(schema), which is a file-local helper in plugin-map / plugin-grid and is exported by nothing — teaching it from @object-ui/components would prescribe an import that does not exist.

Edit 2 — the ledger row

scripts/check-doc-example-shared-reader.mjs — the whole KNOWN_HAND_SPELLINGS entry naming this file is deleted, leaving the drained form the repo already uses elsewhere for an emptied ledger (check-entry-guard.mjs:287):

-export const KNOWN_HAND_SPELLINGS = new Map([
-  [
-    'packages/components/src/custom/navigation-overlay.tsx::useNavigationOverlay::objectName',
-    "objectui#7787. ...",
-  ],
-]);
+export const KNOWN_HAND_SPELLINGS = new Map([]);

One declared addition to the dispatched file surface

The dispatch named two files. A third had to move, and it is a mechanical consequence of edit 2 rather than a choice — reported here rather than made quietly:

scripts/__tests__/check-doc-example-shared-reader.test.ts pinned the ledger row by name, in both directions:

it('carries objectui#7787 as its one waived instance, and nothing else', ...)
  expect([...KNOWN_HAND_SPELLINGS.keys()]).toEqual([ ...navigation-overlay.tsx... ]);
  expect(result.raw.map((f) => f.key)).toContain( ...navigation-overlay.tsx... );

result there is analyze(repoRoot) over this repository, so both assertions describe the pre-fix state. Measured, on the two mandated edits alone: 1 failed | 21 passed, AssertionError: expected [] to deeply equal [ Array(1) ]. The pin is flipped to assert the drained state in the same two directions — the ledger is empty, and the instance is gone from the pre-waiver result.raw rather than merely waived out of it. That the gate still reaches this pair is already asserted a few tests above, where useNavigationOverlay.objectName is named among the compared pairs.

The same file's ## Rollout prose in the gate script gained one sentence recording that the row is out, for the same reason this card exists.

No judging logic changed — analyze, canonical, the four narrowing conditions and every comparison in the gate are untouched. No executable line under packages/*/src changed — the packages/ diff is a doc comment. Clause-② no holds; its void condition was not reached.

Acceptance — the two readings

Run at dde05b618 (the shipping commit), working tree clean:

1. Zero findings.

$ pnpm check:doc-example-readers          # exit 0
OK  79 documented symbol(s), 3944 call site(s), 11 slot(s) where a call site
delegates to a shared reader — no @example hand-spells one.

2. KNOWN_HAND_SPELLINGS is empty.

$ node --input-type=module -e "import { KNOWN_HAND_SPELLINGS } from './scripts/check-doc-example-shared-reader.mjs'; ..."
KNOWN_HAND_SPELLINGS.size = 0
keys = []

With the ledger empty, findings and raw coincide, so the zero above is a zero before waiving, not after it.

Lit control — the two greens are readings, not a gate that never arrived

The example was temporarily put back to objectName: schema.objectName with the ledger left empty, on top of the committed fix so that HEAD carries the implementation:

HEAD_BLOB=17c27b2ef7b458358b143f640adeef088f49c610
PRE_MUTATION_BLOB=17c27b2ef7b458358b143f640adeef088f49c610
MUTATED_BLOB=4a86c61e2d79a69706bcb57ee0124b1f4037f19b
injected-text count=1  removed-text count=0
LEDGER_SIZE_DURING_CONTROL=0
GATE_EXIT_UNDER_MUTATION=1

The mutation was confirmed on disk before anything was read — by the injected/removed text counts and by a non-empty blob hash that differs from the HEAD blob (an empty hash was treated as failure, not as "nothing to compare"). The gate then went red and named the file:

x  1 doc comment(s) prescribe a spelling a shared reader already owns:

      packages/components/src/custom/navigation-overlay.tsx  useNavigationOverlay({ objectName: schema.objectName })
          3 call site(s) pass this slot through resolveRecordSourceObjectName() (packages/core/src/utils/record-source.ts)
          Call sites: packages/plugin-calendar/src/ObjectCalendar.tsx, packages/plugin-grid/src/ObjectGrid.tsx, packages/plugin-tree/src/ObjectTree.tsx

Restored with git checkout HEAD -- ABSOLUTE_PATH under trap ... EXIT INT TERM, with the absolute path resolved from git rev-parse --show-toplevel. The proof is state, not that the trap fired:

POST_RESTORE_BLOB=17c27b2ef7b458358b143f640adeef088f49c610   # equals HEAD_BLOB
git diff HEAD  ->  empty
RESTORE_PROOF=blob-hash-equal AND git-diff-HEAD-empty

Changeset — the gate's verdict, verbatim

$ node scripts/check-changeset-presence.mjs          # exit 0
Compared the working tree with 445d94c36 (merge-base with origin/main): 3 file(s) changed, 1 of them published source of a package the release covers, 0 of them a manifest whose published contract moved, 0 under a package changesets ignores, 1 changeset(s) added.
✅  1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/7787-navigation-overlay-doc-example.md.
    Every one of them has an EMPTY frontmatter — declared as releasing nothing, which
    is the explicit exemption and a complete answer to this gate.

Before the changeset was added the same gate read ❌ 1 source file(s) of 1 released package(s) changed, and this change adds no changeset (exit 1), so the pass above is a reading too. The changeset is the empty-frontmatter form and declares that nothing ships. node scripts/check-changeset-no-major.mjs — ✅ No changeset declares a major bump. The skip-changeset label is deliberately not applied: in this repo that label reads nothing and exempts nothing.

Everything else that was run, at dde05b618

Command Result
pnpm check:doc-example-readers exit 0 — zero findings
npx vitest run scripts/__tests__/check-doc-example-shared-reader.test.ts 22 passed (22), 1 file
node scripts/check-changeset-presence.mjs exit 0
node scripts/check-changeset-no-major.mjs exit 0
pnpm check:control-bytes exit 0 — 6354 tracked text files scanned
pnpm check:doc-fences exit 0
pnpm check:spec-symbols exit 0
pnpm check:phantom-deps exit 0
npx turbo run type-check --filter @object-ui/components --concurrency=2 exit 0 — 9 successful, 9 total

The gate family was derived by hand from this repo's own package.json and .github/workflows/ci.yml; scripts/pm/dispatch-gates.mjs lives only in the sibling objectstack tree and answers about that tree, so it was not used here.

Lint, as a declared narrowing. pnpm lint is turbo run lint across every package and belongs to CI. What ran here is npx eslint --no-inline-config over the three changed source files: exit 0, 3 files linted (count read from --format json, not assumed), 0 errors, 4 warnings — all four pre-existing. Linting the same file at origin/main returns the same 4 warnings under the same rules, shifted by exactly the 15 lines this header grew (215/239/365/366 becomes 230/254/380/381). No type-aware linting is configured (no project / projectService / parserOptions in eslint.config.js), so this diff cannot move the verdict on any file it does not touch. The .changeset/*.md file is not in eslint's population.

Unrelated reds: none were observed in anything run above. scripts/__tests__/check-readme-exports.test.ts — the half-built-tree red belonging to card #7460, which is in flight elsewhere — was not run, and nothing here touches its file. scripts/__tests__/check-doc-links.test.ts (the 15s shared-box timeout flake) was also not run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3


Generated by Claude Code

… shared record source (objectui#7787)

`packages/components/src/custom/navigation-overlay.tsx` opens with a file-header
block documenting `NavigationOverlay`, and its `@example` still taught
`objectName: schema.objectName` — the exact spelling PR #7648 removed from
`useNavigationOverlay`'s own doc block. That fix never reached this file:
different file, different package, a block documenting a different symbol. With
the three call sites (plugin-calendar / plugin-grid / plugin-tree) already going
through the shared reader, this example was the last place in `packages/` still
teaching the retired spelling, and an example is what the next call site is
copied from.

The example now resolves the record source through
`resolveRecordSourceObjectName(schema, dataConfig)` from `@object-ui/core`, with
`dataConfig` spelled as the live call sites spell it
(`resolveRecordSourceConfig(schema)`), and defers to `useNavigationOverlay`'s own
doc block for the rule rather than restating it — a ruling written out twice is a
ruling one of whose copies rots, which is this card's whole defect class.

`KNOWN_HAND_SPELLINGS` in `scripts/check-doc-example-shared-reader.mjs` carried
this file as its one row; it is drained in the same change, as that ledger
requires. Its pin in `scripts/__tests__/check-doc-example-shared-reader.test.ts`
asserted the row's presence and the raw finding, so it is flipped to pin the
drained state in both directions (ledger empty, and the instance gone from the
pre-waiver `result.raw` rather than merely waived out of it).

No executable line changes; the diff under `packages/` is a doc comment.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3187.4 KB 3191.4 KB
Main entry chunk (gzip) 143.2 KB 350 KB
Entry file index-CnaboxA9.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) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
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) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 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) 510.60KB 116.20KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
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) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
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) 15.75KB 3.80KB
plugin-calendar (index.js) 47.87KB 13.31KB
plugin-charts (index.js) 70.92KB 19.75KB
plugin-chatbot (index.js) 196.19KB 46.37KB
plugin-dashboard (index.js) 132.88KB 34.69KB
plugin-designer (index.js) 212.86KB 43.19KB
plugin-detail (index.js) 250.55KB 64.06KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.87KB 32.66KB
plugin-gantt (index.js) 167.26KB 41.00KB
plugin-grid (index.js) 209.29KB 56.78KB
plugin-kanban (index.js) 52.71KB 14.55KB
plugin-list (index.js) 113.28KB 27.59KB
plugin-map (index.js) 20.44KB 6.78KB
plugin-markdown (index.js) 13.93KB 4.81KB
plugin-report (index.js) 43.59KB 11.97KB
plugin-timeline (index.js) 30.84KB 8.85KB
plugin-tree (index.js) 9.20KB 3.19KB
plugin-view (index.js) 85.24KB 20.94KB
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.66KB 3.50KB
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) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
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) 0.20KB 0.18KB
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) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
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) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
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

Merged via the queue into main with commit 9ae1e9d Sep 5, 2026
34 checks passed
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
…hared record source (objectui#7798) (objectstack-ai#7809)

Two doc blocks prescribed, in a copyable `@example`, a line no reader can write:

    const dataConfig = useMemo(() => getDataConfig(schema), [schema]);

`packages/react/src/hooks/useNavigationOverlay.ts` (the `useNavigationOverlay`
block) and `packages/core/src/utils/record-source.ts` (the
`resolveRecordSourceObjectName` block) both spelled it that way, and
`getDataConfig` is exported by nothing — measured, zero rows for
`export function|const|{ getDataConfig` under `packages/`. It survives only as a
file-local wrapper in `plugin-map/src/ObjectMap.tsx` and
`plugin-grid/src/ObjectGrid.tsx`, so outside those two files there is no module
to import it from.

Both wrappers already delegate their ladder to `resolveRecordSourceConfig`,
keeping only the array shorthand above the shared call, so all live sites now
obtain this value the same way — `plugin-calendar` and `plugin-tree` call the
shared producer directly. The examples follow those live call sites, which is
the judgement PR objectstack-ai#7797 applied to `navigation-overlay.tsx`: follow the live call
sites, not another doc block. `useNavigationOverlay`'s example also names the
import, so the line it teaches is copyable as written; `record-source.ts` needs
none, and its example is now byte-identical to the `resolveRecordSourceConfig`
example seventy lines below it in the same file.

`scripts/check-doc-example-shared-reader.mjs` cannot see this class by
construction — its condition (a) requires the example to call a symbol the
repository exports, and `getDataConfig` is exported by nothing, so the pair is
never formed. That gate is deliberately narrow and is left untouched; the fact
is recorded on the card.

No executable line changes: every added and removed diff line under `packages/`
is a JSDoc continuation line, and both revisions of both files transpile to
byte-identical output at `removeComments: true`.


Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(components): navigation-overlay.tsx's file-header @example still teaches the objectName: schema.objectName spelling PR #7648 retired

1 participant