Summary
Clicking a row in the grid/list when the default navigation mode is Page does nothing. This has been reported and patched multiple times but the underlying root cause was never addressed. The bug is a stale closure chain caused by unmemoized objects/callbacks passed into useNavigationOverlay.
Screenshot

Root Cause Analysis (3 Compounding Issues)
Issue 1: { mode: 'page' } fallback creates a new object every render
In apps/console/src/components/ObjectView.tsx line 423:
const detailNavigation: ViewNavigationConfig = activeView?.navigation ?? objectDef.navigation ?? { mode: 'page' };
{ mode: 'page' } is a new object literal on every render. This causes the navigation option passed to useNavigationOverlay to change reference every render, which in turn causes its internal handleClick useCallback to be recreated every render.
Issue 2: onNavigate callback is not memoized
In apps/console/src/components/ObjectView.tsx lines 428-445, the onNavigate is an inline arrow function:
const navOverlay = useNavigationOverlay({
navigation: detailNavigation,
objectName: objectDef.name,
onNavigate: (recordId: string | number, action?: string) => {
// inline — new function identity every render
if (action === 'view' || !action || action === 'page') {
navigate(`record/${String(recordId)}`);
}
},
});
Because both navigation AND onNavigate have unstable identities, React may batch-skip the render where handleClick picks up the fresh onNavigate, resulting in a stale (or initial undefined) closure being called.
Issue 3: Cascade instability through renderListView
navOverlay (returned from useMemo) gets a new reference every render because its deps (handleClick, etc.) changed. This propagates through renderListView's useCallback deps, causing the <ListView> to re-render with potentially stale intermediate closures during React's batched updates.
Call Chain Trace
User clicks grid row
→ ObjectGrid.onRowClick
→ PluginObjectView.handleRowClick (line 397)
→ Console's onRowClick lambda (line 840)
→ navOverlay.handleClick
→ useNavigationOverlay.handleClick (line 120)
→ mode === 'page' → calls onNavigate(recordId, 'view')
→ ⚠️ onNavigate may be stale/undefined due to unmemoized deps
Proposed Fix
Memoize the navigation config and onNavigate callback in apps/console/src/components/ObjectView.tsx:
// 1. Memoize the fallback navigation config
const detailNavigation: ViewNavigationConfig = useMemo(
() => activeView?.navigation ?? objectDef.navigation ?? { mode: 'page' },
[activeView?.navigation, objectDef.navigation]
);
// 2. Memoize the onNavigate callback
const handleNavOverlayNavigate = useCallback(
(recordId: string | number, action?: string) => {
if (action === 'new_window') {
const basePath = window.location.pathname.replace(/\/view\/.*$/, '');
window.open(`${basePath}/record/${String(recordId)}`, '_blank');
return;
}
if (action === 'view' || !action || action === 'page') {
if (viewId) {
navigate(`../../record/${String(recordId)}`, { relative: 'path' });
} else {
navigate(`record/${String(recordId)}`);
}
}
},
[navigate, viewId]
);
// 3. Use memoized values
const navOverlay = useNavigationOverlay({
navigation: detailNavigation,
objectName: objectDef.name,
onNavigate: handleNavOverlayNavigate,
});
Files Affected
| File |
Change |
apps/console/src/components/ObjectView.tsx |
Memoize detailNavigation with useMemo, extract onNavigate into useCallback |
packages/react/src/__tests__/useNavigationOverlay.test.ts |
Add test for stale closure scenario |
ROADMAP.md |
Document fix |
Acceptance Criteria
Labels
bug, P0, navigation
Summary
Clicking a row in the grid/list when the default navigation mode is
Pagedoes nothing. This has been reported and patched multiple times but the underlying root cause was never addressed. The bug is a stale closure chain caused by unmemoized objects/callbacks passed intouseNavigationOverlay.Screenshot
Root Cause Analysis (3 Compounding Issues)
Issue 1:
{ mode: 'page' }fallback creates a new object every renderIn
apps/console/src/components/ObjectView.tsxline 423:{ mode: 'page' }is a new object literal on every render. This causes thenavigationoption passed touseNavigationOverlayto change reference every render, which in turn causes its internalhandleClickuseCallbackto be recreated every render.Issue 2:
onNavigatecallback is not memoizedIn
apps/console/src/components/ObjectView.tsxlines 428-445, theonNavigateis an inline arrow function:Because both
navigationANDonNavigatehave unstable identities, React may batch-skip the render wherehandleClickpicks up the freshonNavigate, resulting in a stale (or initial undefined) closure being called.Issue 3: Cascade instability through
renderListViewnavOverlay(returned fromuseMemo) gets a new reference every render because its deps (handleClick, etc.) changed. This propagates throughrenderListView'suseCallbackdeps, causing the<ListView>to re-render with potentially stale intermediate closures during React's batched updates.Call Chain Trace
Proposed Fix
Memoize the navigation config and
onNavigatecallback inapps/console/src/components/ObjectView.tsx:Files Affected
apps/console/src/components/ObjectView.tsxdetailNavigationwithuseMemo, extractonNavigateintouseCallbackpackages/react/src/__tests__/useNavigationOverlay.test.tsROADMAP.mdAcceptance Criteria
navigation.mode: 'page'(default) navigates to record detail pagenavigation.mode: 'drawer'opens drawer overlaynavigation.mode: 'modal'opens modal overlaynavigation.mode: 'none'does nothinguseNavigationOverlaytests pass (29 tests)ObjectViewtests pass (32 tests)handleClickuses latestonNavigateafter re-renderROADMAP.mdwith root cause and fixLabels
bug,P0,navigation