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) <noreply@anthropic.com>
This commit is contained in:
parent
bf464bfc31
commit
1e48eed459
5 changed files with 12 additions and 20 deletions
|
|
@ -92,7 +92,7 @@ export default async function AppLayout({ children }: { children: React.ReactNod
|
|||
{children}
|
||||
</main>
|
||||
<StatusBar />
|
||||
<SoloRealtimeBridge />
|
||||
<SoloRealtimeBridge productId={activeProduct?.id ?? null} />
|
||||
<NotificationsBridge userId={session.userId} />
|
||||
<Suspense>
|
||||
<AlertToast />
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ export function NavBar({
|
|||
|
||||
{/* Rechts: solo-status + notifications + account-menu */}
|
||||
<div className="flex items-center gap-2 flex-1 justify-end">
|
||||
<SoloNavStatusIndicators />
|
||||
<SoloNavStatusIndicators hasActiveProduct={!!activeProduct} />
|
||||
<NotificationsBell currentUserId={userId} isDemo={isDemo} />
|
||||
<UserMenu userId={userId} username={username} email={email} roles={roles} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex items-center gap-3 px-2">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue