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>
This commit is contained in:
Janpeter Visser 2026-04-27 10:02:22 +02:00
parent 27fb650eba
commit 83f63b2ebb
3 changed files with 241 additions and 0 deletions

View file

@ -0,0 +1,104 @@
'use client'
import { useEffect, useRef, useState } from 'react'
interface Row {
receivedAt: string
type: 'ready' | 'message' | 'error'
raw: string
}
const MAX_ROWS = 200
export function DebugRealtimeClient() {
const [rows, setRows] = useState<Row[]>([])
const [status, setStatus] = useState<'connecting' | 'open' | 'closed' | 'error'>('connecting')
const sourceRef = useRef<EventSource | null>(null)
useEffect(() => {
const source = new EventSource('/api/debug/realtime-stream')
sourceRef.current = source
const append = (row: Row) => {
setRows((prev) => [row, ...prev].slice(0, MAX_ROWS))
}
source.addEventListener('ready', (e) => {
setStatus('open')
const data = (e as MessageEvent).data ?? ''
append({ receivedAt: new Date().toISOString(), type: 'ready', raw: data })
})
source.addEventListener('error', (e) => {
setStatus('error')
const data = (e as MessageEvent).data ?? '(no data)'
append({ receivedAt: new Date().toISOString(), type: 'error', raw: data })
})
source.onmessage = (e) => {
append({ receivedAt: new Date().toISOString(), type: 'message', raw: e.data ?? '' })
}
source.onerror = () => {
setStatus('error')
}
return () => {
source.close()
sourceRef.current = null
}
}, [])
function statusColor() {
switch (status) {
case 'open':
return 'green'
case 'error':
return 'red'
case 'closed':
return 'gray'
default:
return 'orange'
}
}
return (
<div style={{ marginTop: 16 }}>
<div style={{ marginBottom: 12 }}>
Status:{' '}
<span style={{ color: statusColor(), fontWeight: 'bold' }}>{status}</span> · totaal{' '}
{rows.length} entries
</div>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 12 }}>
<thead>
<tr style={{ background: '#f0f0f0', textAlign: 'left' }}>
<th style={{ padding: 6, border: '1px solid #ddd', width: 220 }}>received_at</th>
<th style={{ padding: 6, border: '1px solid #ddd', width: 100 }}>type</th>
<th style={{ padding: 6, border: '1px solid #ddd' }}>payload</th>
</tr>
</thead>
<tbody>
{rows.length === 0 ? (
<tr>
<td colSpan={3} style={{ padding: 12, textAlign: 'center', color: '#888' }}>
Wachten op events trigger een mutatie via UI of script.
</td>
</tr>
) : (
rows.map((row, idx) => (
<tr key={`${row.receivedAt}-${idx}`}>
<td style={{ padding: 6, border: '1px solid #ddd', whiteSpace: 'nowrap' }}>
{row.receivedAt}
</td>
<td style={{ padding: 6, border: '1px solid #ddd' }}>{row.type}</td>
<td style={{ padding: 6, border: '1px solid #ddd', wordBreak: 'break-all' }}>
<code>{row.raw}</code>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
)
}

View file

@ -0,0 +1,23 @@
// TIJDELIJKE debug-pagina voor M8-acceptance.
// Geen auth, geen styling — toont alle inkomende pg_notify-events op
// `scrum4me_changes` in een tabel zodat we kunnen zien of de SSE + LISTEN-
// pipe überhaupt events doorstroomt op Vercel.
//
// VERWIJDEREN VOOR M8 OUT-OF-DRAFT.
import { DebugRealtimeClient } from './client'
export const dynamic = 'force-dynamic'
export default function DebugRealtimePage() {
return (
<div style={{ fontFamily: 'monospace', padding: 16 }}>
<h1 style={{ fontSize: 18, fontWeight: 'bold' }}>Realtime debug scrum4me_changes</h1>
<p style={{ fontSize: 13, color: '#666' }}>
Live SSE-stream rechtstreeks van Postgres LISTEN op channel{' '}
<code>scrum4me_changes</code>. Geen auth, geen filtering. Verwijderen na M8 acceptance.
</p>
<DebugRealtimeClient />
</div>
)
}