fix(react/plugins): the record-page URL follows the record source, not the top-level key - #7648
Merged
Merged
Conversation
…t the top-level key
`useNavigationOverlay` builds `/{objectName}/record/{id}` out of whatever it is
handed. `ObjectTree` and `ObjectCalendar` handed it the bare top-level
`schema.objectName` while resolving their own rows through the objectui#6939
record-source ladder, so a row fetched through `data.object` navigated to a
record that the URL's own object does not contain.
`ObjectCalendar` is where that was plainest: on ONE click it resolved the detail
drawer through the ladder and the URL through the top-level key. The URL now
reuses the same `schemaObjectName` that keys the record query, so query, drawer
and URL agree by construction.
Both converted sites keep a site-local `?? schema.objectName` tail for the
off-contract `data: { provider: 'object' }` carrying no `object`, so nothing
outside the divergence moves. The hook's signature is unchanged.
The hook's `@example` stops prescribing `objectName: schema.objectName` — that
prose is why there were copies to convert — and a source pin now holds it shut.
`ObjectKanban` is deliberately not converted: it has no data config and its
`data` is a raw row array, so `schema.objectName` already IS its record source.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Contributor
✅ Console Performance Budget
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
Size Limits
|
This was referenced Sep 4, 2026
os-sam
marked this pull request as ready for review
September 4, 2026 14:48
os-sam
deleted the
claude/issue-7638-navigation-overlay-record-source
branch
September 4, 2026 15:04
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #7638
Deliberately
Part ofand not a closing keyword: three of the card's four sites areconverted here, and the fourth (
ObjectKanban) is reported below as a falsified premisethat needs a determination rather than code. The card should stay open for it.
Implements the
domain:uiPM ruling incomment 5541635459
(2026-09-04): the navigation URL follows the record source, ruled as a correctness
question rather than a product one. All evidence below was measured on
4659d6a6.What changed
useNavigationOverlaybuilds/{objectName}/record/{id}out of whatever it is handed.ObjectTreeandObjectCalendarhanded it the bare top-levelschema.objectNamewhileresolving their own rows through the objectui#6939 record-source ladder (
data, thenstaticData, thenobjectName). Under objectui#6939objectNameis that ladder's thirdrung and not a parallel "page object" concept, so a block has exactly one record source —
and a row fetched through
data.objectwhose click built/{schema.objectName}/record/{id}named a record that the URL's own object does not contain.
packages/plugin-tree/src/ObjectTree.tsxobjectName: schema.objectNameresolveRecordSourceObjectName(schema, dataConfig) ?? schema.objectNamepackages/plugin-calendar/src/ObjectCalendar.tsxobjectName: schema.objectNameschemaObjectName ?? schema.objectNamepackages/react/src/hooks/useNavigationOverlay.tsObjectCalendaris where the divergence was plainest, and is the evidence it was a copyrather than a design: on one click it resolved the detail drawer through the ladder
(
:969) and the URL through the top-level key (:625). The URL now reuses the veryschemaObjectName(:309) that already keys the record query and its$expandderivation, so query, drawer and URL agree by construction.
The hook's signature is unchanged — it still takes an
objectName; only what callershand it moved.
The one judgement call, flagged for review
Both converted sites keep a site-local
?? schema.objectNametail. It differs from thebare reader on exactly one input: the off-contract
data: { provider: 'object' }carryingno
object, whichViewDataSchemadeclares required and which the shared readerdeliberately answers
undefinedfor rather than coercing.Keeping the tail makes the diff exactly behaviour-neutral outside the divergence it
closes, and matches both
ObjectTree's neighbouringheaderObjectNameand theconservatism objectui#7627 applied when it published the reader. The argument against it
is real and worth a reviewer's eye: on that one shape the URL again disagrees with the
drawer, which is the class of divergence this card exists to close. Dropping the tails is
a one-line change at each site plus one test expectation, if the reviewer prefers it.
Finding 1 — the card's premise for
ObjectKanbanis falsifiedMeasured on
1ec291c0,ObjectKanbanhas no data config at all:getDataConfigoccurrences inObjectKanban.tsx: 0. The five hand-copied producers(finding(plugins):
getDataConfig— the three-rung record-source ladder — is hand-copied in five plugin components #7632) live in calendar, gantt, tree, map and grid — kanban is not among them.KanbanSchema.datais declareddata?: any[]— a raw row array, not aViewDatabinding. Its record chain is
externalDatathenboundData(schema.bind) thenschema.datathendataSource.find(schema.objectName, ...).schema.objectName.So there is no divergence in kanban to close: its
useNavigationOverlay(:699), itsdrawer (
:973) and itsderiveRecordPageHref(:995) all already read the sameschema.objectName, which genuinely IS its record source.resolveRecordSourceObjectName(schema, null)returnsschema.objectName, so convertingit is a strict identity that would add an import and falsely advertise a ladder that is
not there.
Giving kanban a real ladder would mean adding a sixth
getDataConfigproducer (#7632'sterritory, explicitly fenced off) and re-typing
KanbanSchema.datafromany[]to aViewDataunion — a published-surface change. That is left for a determination ratherthan taken unilaterally; see the Clause-② note.
Finding 2 — the objectui#7617 gate does not cover the doc comment
Card #7638 and the dispatch ruling both state the
useNavigationOverlay:198prose is"the exact defect class the gate landed by PR #7617 exists to catch". Measured: it is not.
PR #7617 modified
scripts/check-spec-symbol-derivation.mjs(pnpm check:spec-symbols),whose rule 4 judges citations of
@objectstack/specat member granularity. The prosehere cites no spec symbol; it prescribes a local call-site spelling.
Measured with a lit control, because "the gate is silent" and "the gate never reads this
file" are different facts:
useNavigationOverlay.tsNavigationConfigSchema.zzzNotARealMembercitation planted in that same doc block:190The control fires, so the gate does scan the file and simply has nothing to say about
this defect class — correct for that gate, and the reason the prose was unprotected. A
narrow source-text pin now holds it shut:
packages/react/src/hooks/__tests__/useNavigationOverlay.docExampleRecordSource-7638.test.ts.Clause-② determination: no (independently reached)
No schema key is added, removed or re-typed; no export is added or withdrawn; nothing
changes what a contract accepts or rejects. This consumes an already-published helper and
edits two call sites, one doc comment and three test files.
One reading does move the published surface — the one in which
ObjectKanbanis alsoconverted, since that requires re-typing
KanbanSchema.data. That reading is notexercised by this PR, so
needs:contract-reviewis applied to the card (where theresidual scope lives) and not to this PR, whose diff contains no such move. Flagged rather
than silently deferred.
Evidence
Ablation — predicted before running, three legs, each mutating one file back to its
pre-fix
1ec291c0content. Mutation proven on disk by blob-hash movement plus anchoredgrep counts (injected spelling 0, pre-fix spelling 1); restore against
HEADby explicitgit checkout HEAD -- ABSOLUTE_PATHunder anEXIT INT TERMtrap, proven by blob-hashequality to the
HEADblob and an emptygit diff HEAD(empty hash read as failure, notas "nothing to compare").
ObjectTreecall siteObjectCalendarcall siteuseNavigationOverlaydoc proseFailure identities matched the predictions too, not just the counts: the two behavioural
legs failed on exactly "navigates to the object the ROWS came from" and "builds a routed
URL for a data-only block", while the lit control and the off-contract/value cases stayed
green as predicted.
Lit controls. Every measurement carries one. Both behavioural suites open with a
no-
data-block case that must read non-zero — awindow.openthat never fires would makeevery "did not navigate to the decoy" assertion vacuously true. The doc pin asserts its
doc block is non-empty and really documents this hook. One control did read zero on
the first attempt (a lint probe whose text tripped no configured rule); that reading was
discarded and the control rebuilt on
@typescript-eslint/no-explicit-any, a rule observedfiring 80+ times on these files, before the lint result was accepted.
Tests — 36 files / 294 tests green across
plugin-tree,plugin-calendar,react/hooks/useNavigationOverlayandcore/utils/record-source;type-checkgreen forall three packages, with
--listFilesconfirming each new test file is actually in itstype-check program (1 hit each) rather than excluded and silently unmeasured.
Gates.
check:control-bytes,check:spec-symbols,check:self-import,check:phantom-deps,check:element-data-source-declarationandscripts/__tests__/unit-registry-absence-collision.test.tsall green.check:readme-exportsis NOT MEASURED, not red: it needs every package'sdistandsays so itself — 311 of its 314 lines read "run
pnpm buildfirst", and its ownself-instrumentation floors fired (
keysCompared: found 0, floor is 20) with the verdict"the population COLLAPSED -- this run proves nothing". After building the three packages
this PR touches, findings naming them went 7 to 0; the remainder concerns 19 packages
this PR does not touch and is satisfied by the full build CI performs.
Lint — a declared narrowing, not a full-tree run. Receiving population read from
ESLint's own config via
isPathIgnored(7 changed files: 6 lintable, 1 ignored — thechangeset markdown); file count read from ESLint's own result array (6). No type-aware
linting is configured in
eslint.config.js(noprojectService, noparserOptions.project), so a file's verdict depends only on its own text plus config andthis diff cannot move any untouched file's verdict. Result: 0 errors; the 90 warnings
are pre-existing, confirmed by a BASE-to-HEAD delta holding each file's text against the
same path —
ObjectTree.tsx24 to 24,ObjectCalendar.tsx66 to 66,useNavigationOverlay.ts0 to 0, delta +0 everywhere.🤖 Generated with Claude Code
https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3
Generated by Claude Code