* chore(debug): add /debug-realtime page + bare SSE endpoint Tijdelijke debug-tooling voor M8-acceptance op Vercel preview. - app/api/debug/realtime-stream/route.ts — geen auth, geen filtering; dropt elke pg_notify-event op scrum4me_changes rauw door als SSE - app/debug-realtime/page.tsx — open zonder login op de root, toont binnenkomende events in een simpele <table> Doel: isoleren of de SSE + Postgres LISTEN-pipe op Vercel überhaupt events laat zien, los van iron-session, productfilter of solo-store. Als ook deze niets binnen krijgt: probleem zit in pg connection of Vercel function lifecycle. Als deze wel events toont: probleem zit hoger in de stack (filter, store, hook). VERWIJDEREN voordat de PR uit draft gaat. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(debug): extend /debug-realtime with stats, emit-button and filters Bouwt de basale luister-tabel uit met diagnostische tooling om de SSE+LISTEN-pipe stress-vrij te kunnen valideren. Toegevoegd: - POST /api/debug/emit-test-notify — vuurt een handmatige pg_notify op scrum4me_changes met een synthetic payload (debug:true) zonder een echte DB-UPDATE te doen. Isoleert de SSE-route van Prisma/triggers. - DebugRealtimeClient: stats-grid (status, reconnects, total events, since last event met >30s rood-warning, largest gap, first-event- time), emit-button, reset-stats, filters op type en entity (incl. "debug only"). - Type/entity kolom in de tabel met kleuring per type. Geen impact op productie- of solo-flow. Tijdelijke testtooling; verwijderen wanneer we deze pagina niet meer nodig hebben. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(debug): add Layer 2 — mini Zustand-store + dispatch toggles Test of SSE-event → store → render-pipeline werkt buiten de Solo Paneel context. Mirrort het patroon van solo-store maar minimaal. - debug-store.ts: kleine Zustand-store met tasks + applyEvent + applyCount/skipCount-tellers - store-panel.tsx: rendert store-state in een tabel met statuskleuring - client.tsx: drie layer-toggles (dispatch / flushSync / startView- Transition) + lift dispatch in onmessage. Zo kunnen we elke combinatie isoleren Bevestigd: alle drie de toggles werken op het bare /debug-realtime endpoint. Volgende laag is Server Action revalidation. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
// Mini Zustand-store voor de debug-pagina. Mirrort het patroon van
|
|
// stores/solo-store.ts maar dan minimaal: alleen tasks-record + applyEvent.
|
|
// Doel: testen of de SSE-event → store → component-render keten werkt
|
|
// zonder de complexiteit van de echte Solo Paneel.
|
|
|
|
import { create } from 'zustand'
|
|
|
|
export interface DebugTask {
|
|
id: string
|
|
status: string
|
|
title: string
|
|
story_id: string
|
|
updated_at: string
|
|
}
|
|
|
|
export interface DebugRealtimeEvent {
|
|
op: 'I' | 'U' | 'D'
|
|
entity: 'task' | 'story'
|
|
id: string
|
|
story_id?: string
|
|
task_status?: string
|
|
task_title?: string
|
|
debug?: boolean
|
|
emitted_at?: string
|
|
[key: string]: unknown
|
|
}
|
|
|
|
interface DebugStore {
|
|
tasks: Record<string, DebugTask>
|
|
applyCount: number
|
|
skipCount: number
|
|
applyEvent: (event: DebugRealtimeEvent) => void
|
|
reset: () => void
|
|
}
|
|
|
|
export const useDebugStore = create<DebugStore>((set, get) => ({
|
|
tasks: {},
|
|
applyCount: 0,
|
|
skipCount: 0,
|
|
|
|
applyEvent: (event) => {
|
|
if (event.entity !== 'task') {
|
|
set((s) => ({ skipCount: s.skipCount + 1 }))
|
|
return
|
|
}
|
|
if (event.op === 'D') {
|
|
set((s) => {
|
|
const next = { ...s.tasks }
|
|
delete next[event.id]
|
|
return { tasks: next, applyCount: s.applyCount + 1 }
|
|
})
|
|
return
|
|
}
|
|
// INSERT/UPDATE — schrijf altijd, ongeacht of de task al bestond
|
|
set((s) => ({
|
|
tasks: {
|
|
...s.tasks,
|
|
[event.id]: {
|
|
id: event.id,
|
|
status: event.task_status ?? get().tasks[event.id]?.status ?? '?',
|
|
title: event.task_title ?? get().tasks[event.id]?.title ?? '(no title)',
|
|
story_id: event.story_id ?? get().tasks[event.id]?.story_id ?? '?',
|
|
updated_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
applyCount: s.applyCount + 1,
|
|
}))
|
|
},
|
|
|
|
reset: () => set({ tasks: {}, applyCount: 0, skipCount: 0 }),
|
|
}))
|