The list host's record-URL builder answered the listed object only, so a
lookup cell or a record-pointer cell naming another object's record drew a
name that could not be opened. It now answers any routable object through
the record page's own builder (appRecordPath over routableObjectNames,
exported by RelatedRecordActionsBridge), as a document href carrying the
router basename, with in-app navigation on a plain click and no from-trail
(a list page has no parent record). The listed object keeps its own route.
Claude-Session: https://claude.ai/code/session_01CrvmqMe8FLB3kXU3NHq25T
Co-authored-by: Claude <noreply@anthropic.com>
Fixes #12673
Clause-②: no
Ruling
6216810282, letter A: the console list host publishes the same any-object record links the record page holds. Lookup cells andreferenceViacells in every console list now link to the target record. A plain click opens it in the console; Ctrl-click, ⌘-click or a middle click opens a new tab.First step: measurements (base
15d8ca6)ObjectViewandRecordDetailViewwith the sameallObjectsprop, andObjectViewInneralready readsappNamefrom the route params. The bridge needs exactly those two inputs. The premise holds.stopPropagation, and data-table's row handler, which ignores targets inside an anchor. The primary link column masks the host for its children, so no anchor nests in an anchor (the objectui#11817 rule; live nested-anchor count on the opportunities list is 0).What changed
RelatedRecordActionsBridgeexports its existing record-path builder as two pure functions:routableObjectNames(objects)andappRecordPath(appName, routable, objectName, recordId). The bridge's ownrecordPathnow calls them and still appends its?from=trail. The record page behaves the same, and a CONTROL test pins it.listRecordActionsValuetakes an optionalroutesargument (appName, objects, navigate, routerRootHref). With it, a record of ANOTHER routable object gets the record page's route/apps/APP/OBJECT/record/ID. It is published as a document href through the existingdocumentHrefjoin, so a new tab or a copied link carries the console's base path. A plain click navigates by router path. No?from=entry is added, because a list page has no parent record. The listed object keeps its own route and its own record navigation, which carries the list as the record's origin. A non-routable object, a missing app segment or an empty id answers no href, so the cell renders plain text.ObjectViewpasses the routes: the route'sappName, itsobjectsprop,useNavigate()anduseHref('/').RecordPointerCelland the lookup cell'sReferencedRecordLinkalready draw an anchor whenever the host answers.@object-ui/app-shellpackage entry:src/index.tsandviews/index.tsexport onlyObjectViewandConsoleObjectViewPropsby name, andpackage.jsonexports only..Live evidence (HotCRM, 1280 px)
Screenshots are on the evidence branch
claude/issue-12673-evidence(commitcb648c3c), which is never merged here:Link
hrefs read from the DOM at capture, three rows each:/apps/setup/crm_opportunity/record/hIpgtKHxyIJ3DC3k/apps/setup/crm_opportunity/record/SlLJR9-9feAptafn/apps/setup/crm_opportunity/record/MoY9r6pUTH95OuQP/apps/crm_enterprise/crm_account/record/uaNdkA6LmpFLgdYU/apps/crm_enterprise/crm_account/record/C6-rE8KonvJsu_6h/apps/crm_enterprise/crm_account/record/2wJu02KNnNzivci5Clicked live:
/apps/crm_enterprise/crm_account/record/uaNdkA6LmpFLgdYU(heading 'Vertex Analytics')./apps/setup/crm_opportunity/record/hIpgtKHxyIJ3DC3k(heading 'Initech Cloud Migration')./apps/crm_enterprise/crm_opportunity/record/SlLJR9-9feAptafn.Tests (on
1a00c21)__tests__/listCrossObjectCells-12673.test.tsx(8): realObjectGridand cell renderers under the reallistRecordActionsValue, fed by the router'snavigateanduseHref, basename/_console. React-router'sLinkis the href oracle. Covers:referenceViacell (href and click);ObjectView.listCrossObjectHost-12673.test.tsx(6): the realObjectViewpublishes the builder. The list href path equals the record page's href path (one builder). CONTROL: the record page keeps its?from=trail and its null answer for an unroutable object.ObjectView.listRecordHref.test.ts: 7 new builder cases. The old 'no other object' case now pins the no-routes path.git diff HEADempty.stopPropagationalone stays green, because the row handler's anchor guard catches it too. Removing both guards turns the lookup pin red.@object-ui/app-shellsuite as 12 sequential shards of the same file set (a single run outlives this container's command cap): 1461 files (1 skipped), 13774 tests passed, 9 skipped, 0 failed.type-checkexit 0 (after building the dependency closure); eslint on touched files exit 0 (0 errors);buildexit 0 (dist completeness ok);check:i18n-keys,check:i18n-drift,check:new-line-citationsandcheck-changeset-presenceall exit 0.Acceptance notes
pageRecordActionsValue(custom pages) still holds its own copy of the same route builder, and thedocumentHrefdocblock still names two hosts. Both are outside this change's file surface and are left as they are.Session:
https://claude.ai/code/session_01CrvmqMe8FLB3kXU3NHq25TGenerated by Claude Code