Repository navigation
fix(app-shell,react): lookup links on record pages and custom pages carry the console base path (objectui#12233) - #12239
Merged
objectstack-fleet[bot] merged 3 commits intoOct 12, 2026
Conversation
On a console mounted under a router basename such as /_console, the
record page's lookup anchors (a lead's converted account and contact,
its owner) carried the bare route /apps/APP/OBJECT/record/ID. A left
click worked because the anchor hands it to navigate, which applies the
basename; a new tab, a middle-click or a copied link resolved the bare
route against the origin and hit ENDPOINT_NOT_FOUND.
RelatedRecordActionsBridge now keeps one router path (recordPath) and
derives both ways of reaching a record from it: openRecord navigates by
the path, and recordHref returns the href react-router renders for it,
read from the router's own root href (useHref('/')). A root-mounted
console keeps the bare href.
Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd
Co-authored-by: Claude <noreply@anthropic.com>
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
|
…ssue-12233-lookup-link-base-path
…asename too
Round 2 of objectui#12233. pageRecordActionsValue, the record navigator
PageView publishes to the blocks on a custom page, returned the bare
router path as recordHref, the same drop the record page's bridge had.
It now takes the router's root href from its caller (PageView reads
useHref('/')): recordHref returns the document href, and openRecord
keeps navigating by the router path.
documentHref moves to one internal module, views/documentHref.ts, that
both producers import; no barrel re-exports it.
The RelatedRecordActionsValue.recordHref doc in @object-ui/react now says
the value is a document href carrying the host router's basename. Doc
only; the type is unchanged.
The PageView tests that mock react-router-dom with a factory gain a
useHref entry, since PageView now reads it.
Claude-Session: https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEd
Co-authored-by: Claude <noreply@anthropic.com>
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
|
objectstack-fleet
Bot
deleted the
claude/issue-12233-lookup-link-base-path
branch
October 12, 2026 12:30
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.
Fixes #12233
Clause-②: no
Item 4 of the list card objectui#12211. The parent stays open for its other items.
What was wrong
A console can be mounted under a router basename. Hosted HotCRM serves it at
/_console. On a record page, a valued lookup renders as a real anchor throughReferencedRecordLinkin@object-ui/fields, which takes its href from the host'sRelatedRecordActionsValue.recordHref. On the record page that host isRelatedRecordActionsBridge, whichRecordDetailViewmounts around the whole page. Its builder returned the bare router path/apps/APP/OBJECT/record/ID?from=….openRecord, which callsnavigate, andnavigateadds the basename.ENDPOINT_NOT_FOUND.Premise check, on objectui
origin/main6eca5b5a7BrowserRouter basenamecomes from the injected base tag (resolveBasenameinapps/console/src/App.tsx), so a hosted console's router lives at/_console. Confirmed.ReferencedRecordLinkis the only anchor in@object-ui/fieldsthat draws a record link.LookupCellRendereruses it for every valued lookup. The owner link is a lookup tosys_userthrough the same renderer, andUserCellRendererdraws no link.?from=trail that the measured href carries, so the bridge is the producer.RecordDetailViewhas two other record-route literals: the favourite entry and the recent-item entry. Both are router paths, and their readers usenavigateand routerLinks, so they are correct as they are. This PR leavesRecordDetailView.tsxuntouched.listRecordActionsValueinObjectView.tsx) already returns a document path, because it derives fromwindow.location.pathname, which includes the basename. The two producers disagreed. Applying the basename in the consumers would have doubled it on the list page, so the fix belongs in the producer.The fix
Round 2 (head
cc0777aa) extends round 1 to the second producer of the same defect, under surface amendment6123360584on objectui#12233. Both hosts that publishRelatedRecordActionsValue.recordHrefnow share one join. These are the record page'sRelatedRecordActionsBridgeand the custom page'spageRecordActionsValue. The join isdocumentHref(rootHref, routerPath), in the internal modulepackages/app-shell/src/views/documentHref.ts:recordPath).openRecordnavigates by that router path, as before, and the router adds the basename.recordHrefreturnsdocumentHref(useHref('/'), path): the router's own root href, which carries the basename, joined to the path. It equals the href react-router'sLinkrenders for that route. A console mounted at the root keeps the bare href.PageViewreadsuseHref('/')beside its other router hooks and passes it topageRecordActionsValue, which takes it as a new fourth argument.No widget changes. No
/_consoleorBASE_URLliteral is added anywhere.Published contract
RelatedRecordActionsValue.recordHrefis a member of a published type in@object-ui/react. Its type is unchanged. Round 2 adds one sentence to its doc: the value is a document href and carries the host router's basename, whileopenRecordnavigates by the router path. Both producers now meet that doc.RelatedRecordActionsBridge,pageRecordActionsValueanddocumentHrefare not reachable from the@object-ui/app-shellentry. Itsexportsare.and./styles.css, and the barrels name none of the three;PageViewin the same barrels is the positive control.pageRecordActionsValue. That is the basis forClause-②: no.@object-ui/app-shellpatchand@object-ui/reactpatch(doc only).Tests
packages/app-shell/src/views/__tests__/RelatedRecordActionsBridge.basenameHref-12233.test.tsx(round 1). It mounts the real bridge under aMemoryRouter, with the realLookupCellRendererfor the three measured links (account, contact, and owner onsys_user):/_console, each href equals react-router'sLinkhref and keeps?from=;packages/app-shell/src/views/__tests__/PageView.lookupHrefBasename-12233.test.tsx(round 2). It mounts the realPageViewunder a realMemoryRouter, with the realLookupCellRenderer. It checks basename/_consoleagainst a react-routerLinkoracle and the left-click destination, with a no-basename control.PageView.recordNavigator-11293.test.tsxgains a basename case, and its builder calls pass'/'explicitly; no assertion is weakened.PageViewtests gain auseHrefentry returning'/'in theirreact-router-domfactory mock:PageView.test.tsx,PageView.context-9673,PageView.editInStudioNeedsAuthoring-10899andpage-kind-writing-end-9718. Without it they fail; see B2.Reverse checks, each from a committed fix through
ablation-replace. In each, the anchor hit once and the blob changed, and the restore was proven: the blob equals HEAD andgit diff HEADis empty.3a5e7950): the bridge's href goes back to the bare router path. Result:Tests 2 failed | 1 passed (3),expected '/apps/hotcrm/crm_account/record/A-1' to be '/_console/apps/hotcrm/crm_account/record/A-1'.3a5e7950): the naive fix, where a left click navigates with the prefixed href. Result:Tests 1 failed | 2 passed (3), with a doubled basename.cc0777aa):pageRecordActionsValue's href goes back to the bare path. Result:Tests 2 failed | 6 passed (8), Expected/_console/apps/crm/account/record/A-1, Received/apps/crm/account/record/A-1; the controls stay green.cc0777aa):useHrefdropped fromPageView.test.tsx's mock. Result:Error: [vitest] No "useHref" export is defined on the "react-router-dom" mock.Gates, at HEAD
cc0777aaturbo run build --filter='@object-ui/app-shell^...' --concurrency=2(dependency closure)Tasks: 28 successful, 28 totalpnpm --filter @object-ui/app-shell type-checkpnpm --filter @object-ui/react type-checkpnpm --filter @object-ui/app-shell lint0 errors; no warning on a changed linepnpm --filter @object-ui/react lint0 errors; the one warning inRelatedRecordActionsContext.tsxis on an existing export, not the doc linespnpm exec vitest runover the narrowed set belowTest Files 317 passed (317),Tests 3340 passed (3340)node scripts/check-changeset-no-major.mjsNo changeset declares a major bump.node scripts/check-changeset-presence.mjs12 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)pnpm check:changeset-claims,check:control-bytes,check:new-line-citations,check:pending-changeset-literals,check:test-path-roots,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:phantom-deps,check:unreferenced-sourcesDeclared narrowing of the test run. The run covers every
packages/app-shellandapps/consoletest whose source names any of these: the bridge,RecordDetailView,recordHref,openRecord,LookupCellRenderer,ReferencedRecordLink,AppContent,ConsoleShell,useRelatedRecordActions,RelatedRecordActionsProvider,PageView,pageRecordActionsordocumentHref. It also covers such tests elsewhere that import@object-ui/app-shell, all 109@object-ui/reacttest files, andpackages/fields/src/__tests__/lookupCellRecordLink.test.tsx. The full@object-ui/app-shellsuite is left to CI'sTestshards, which are green on this head.Acceptance notes
PageViewunder areact-router-dommock with nouseHref. Whichever PR lands second needs auseHrefentry returning'/'in that mock. The seat has handed this to objectui#12198's dev.RecordDetailView's favourite and recent-item record routes are router paths read throughnavigateand routerLinks, so they are correct as they are and untouched.Session:
https://claude.ai/code/session_01G5TSaEwbEQsKMjhzTB4rEdGenerated by Claude Code