From 1e48eed459204535a597e7d9b301ec66b02d8c15 Mon Sep 17 00:00:00 2001 From: janpeter visser Date: Fri, 1 May 2026 10:22:13 +0200 Subject: [PATCH] feat: open SoloRealtimeBridge globaal voor active product SoloRealtimeBridge gated nu op active-product i.p.v. /solo-pad. Live-dot en worker-presence werken daardoor op alle (app)-pagina's (Producten/PB/Sprint/Solo/Todo's). Buiten /solo is de solo-store leeg en zijn task-events no-ops, dus de stream gedraagt zich automatisch als lichte presence-stream tot SoloBoard mount. - realtime-bridge: productId-prop i.p.v. usePathname - (app)/layout: activeProduct?.id doorgegeven aan bridge - nav-status-indicators: pathname-check vervangen door hasActiveProduct prop - nav-bar: hasActiveProduct={!!activeProduct} doorgegeven - architecture-doc: realtime connection lifecycle bijgewerkt Co-Authored-By: Claude Opus 4.7 (1M context) --- app/(app)/layout.tsx | 2 +- components/shared/nav-bar.tsx | 2 +- components/solo/nav-status-indicators.tsx | 8 ++------ components/solo/realtime-bridge.tsx | 18 +++++++----------- docs/scrum4me-architecture.md | 2 +- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/app/(app)/layout.tsx b/app/(app)/layout.tsx index fa41d4a..384828b 100644 --- a/app/(app)/layout.tsx +++ b/app/(app)/layout.tsx @@ -92,7 +92,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod {children} - + diff --git a/components/shared/nav-bar.tsx b/components/shared/nav-bar.tsx index d6dba25..f039e67 100644 --- a/components/shared/nav-bar.tsx +++ b/components/shared/nav-bar.tsx @@ -183,7 +183,7 @@ export function NavBar({ {/* Rechts: solo-status + notifications + account-menu */}
- +
diff --git a/components/solo/nav-status-indicators.tsx b/components/solo/nav-status-indicators.tsx index 7ee614e..e370540 100644 --- a/components/solo/nav-status-indicators.tsx +++ b/components/solo/nav-status-indicators.tsx @@ -1,13 +1,10 @@ 'use client' -import { usePathname } from 'next/navigation' import { useSoloStore } from '@/stores/solo-store' import type { RealtimeStatus } from '@/stores/solo-store' import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip' import { cn } from '@/lib/utils' -const SOLO_PATH_RE = /^\/products\/[^/]+\/solo$/ - function RealtimeIndicator({ status, showConnectingIndicator, @@ -43,13 +40,12 @@ function RealtimeIndicator({ ) } -export function SoloNavStatusIndicators() { - const pathname = usePathname() +export function SoloNavStatusIndicators({ hasActiveProduct }: { hasActiveProduct: boolean }) { const realtimeStatus = useSoloStore((s) => s.realtimeStatus) const showConnectingIndicator = useSoloStore((s) => s.showConnectingIndicator) const connectedWorkers = useSoloStore((s) => s.connectedWorkers) - if (!pathname || !SOLO_PATH_RE.test(pathname)) return null + if (!hasActiveProduct) return null return (
diff --git a/components/solo/realtime-bridge.tsx b/components/solo/realtime-bridge.tsx index bd04f69..37cc14b 100644 --- a/components/solo/realtime-bridge.tsx +++ b/components/solo/realtime-bridge.tsx @@ -1,21 +1,17 @@ // SoloRealtimeBridge — mount in de (app)-layout zodat de SSE-verbinding -// blijft staan over Server Action-refreshes van de Solo-page heen. +// blijft staan over Server Action-refreshes heen. // -// Leest het huidige product-id uit de URL (`/products/[id]/solo`). -// Wanneer de gebruiker niet op het Solo Paneel zit, wordt de stream -// gesloten — geen onnodige verbinding open houden. +// Stream opent zodra er een actief product is (ongeacht het pad), zodat +// de Live-status-dot en worker-presence-indicator in de NavBar overal +// werken. Buiten /solo is de solo-store leeg en zijn task-events no-ops +// (zie stores/solo-store.ts handleRealtimeEvent), dus de stream gedraagt +// zich automatisch als lichte presence-stream tot SoloBoard mount. 'use client' -import { usePathname } from 'next/navigation' import { useSoloRealtime } from '@/lib/realtime/use-solo-realtime' -const SOLO_PATH_RE = /^\/products\/([^/]+)\/solo$/ - -export function SoloRealtimeBridge() { - const pathname = usePathname() - const match = pathname?.match(SOLO_PATH_RE) - const productId = match?.[1] ?? null +export function SoloRealtimeBridge({ productId }: { productId: string | null }) { useSoloRealtime(productId) return null } diff --git a/docs/scrum4me-architecture.md b/docs/scrum4me-architecture.md index 4d0a2ce..bc1234d 100644 --- a/docs/scrum4me-architecture.md +++ b/docs/scrum4me-architecture.md @@ -986,7 +986,7 @@ Niet-matchende events worden server-side gedropt zodat de browser geen irrelevan ### Connection lifecycle -- **Open**: `EventSource('/api/realtime/solo?product_id=...')` zodra de gebruiker op `/solo` is. +- **Open**: `EventSource('/api/realtime/solo?product_id=...')` zodra de gebruiker een actief product heeft. `SoloRealtimeBridge` mount in `(app)/layout` en krijgt het `productId` via prop, zodat de stream over de hele app open staat — niet alleen op `/solo`. Zo kunnen de Live-status-dot en worker-presence-indicator in de NavBar overal werken. Buiten `/solo` is de solo-store leeg en zijn binnenkomende task-events no-ops (`stores/solo-store.ts handleRealtimeEvent` skipt onbekende ids), dus de stream gedraagt zich automatisch als lichte presence-stream tot `SoloBoard` mount. - **Reconnect**: exponential backoff bij `onerror` (1s → 30s, reset bij `ready` event). - **Pause op tab-hidden**: `document.visibilityState === 'hidden'` sluit de stream actief. Bij `visible` wordt opnieuw verbonden. Dit voorkomt dat inactieve tabs DB-connecties open houden. - **Hard close**: server sluit zelf na 240s (Vercel `maxDuration` is 300s); client herconnect transparant.