fix: use createPortal for DetailPanel instead of context+useEffect
Some checks failed
CI / build (push) Successful in 1m21s
CI / cleanup-branch (push) Has been skipped
CI / docker (push) Successful in 53s
CI / deploy-feature (push) Has been cancelled
CI / deploy (push) Has been cancelled

The previous approach used useEffect+context to hoist DetailPanel
content to the AppShell level, but the dependency-free useEffect
caused a re-render loop that broke sidebar navigation.

Replace with createPortal: pages render DetailPanel inline in their
JSX but portal it to a target div (#detail-panel-portal) at the
AppShell level. No state lifting, no re-render loops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-03-24 18:38:59 +01:00
parent 5de792744e
commit f3241e904f
4 changed files with 106 additions and 192 deletions

View File

@@ -5,7 +5,6 @@ import { useRouteCatalog } from '../api/queries/catalog';
import { useAgents } from '../api/queries/agents';
import { useAuthStore } from '../auth/auth-store';
import { useMemo, useCallback } from 'react';
import { DetailPanelProvider, useDetailPanelSlot } from './DetailPanelContext';
function healthToColor(health: string): string {
switch (health) {
@@ -69,7 +68,6 @@ function LayoutContent() {
const { data: agents } = useAgents();
const { username, logout } = useAuthStore();
const { open: paletteOpen, setOpen: setPaletteOpen } = useCommandPalette();
const { detail } = useDetailPanelSlot();
const sidebarApps: SidebarApp[] = useMemo(() => {
if (!catalog) return [];
@@ -124,7 +122,6 @@ function LayoutContent() {
apps={sidebarApps}
/>
}
detail={detail}
>
<TopBar
breadcrumb={breadcrumb}
@@ -140,6 +137,8 @@ function LayoutContent() {
<main style={{ flex: 1, overflow: 'auto', padding: '1.5rem' }}>
<Outlet />
</main>
{/* Portal target for DetailPanel — pages use createPortal to render here */}
<div id="detail-panel-portal" />
</AppShell>
);
}
@@ -149,9 +148,7 @@ export function LayoutShell() {
<ToastProvider>
<CommandPaletteProvider>
<GlobalFilterProvider>
<DetailPanelProvider>
<LayoutContent />
</DetailPanelProvider>
<LayoutContent />
</GlobalFilterProvider>
</CommandPaletteProvider>
</ToastProvider>