Clients no longer rely on pure polling for live updates: new GET /api/events streams full game-state snapshots as Server-Sent Events — the current snapshot immediately on connect, then one after every sim tick (~1 s), so the UI updates push-driven even while idle. Polling remains a fully functional automatic fallback (5 s silence watchdog + stream-error trigger) and /api/state behavior is unchanged.
app/api/events/route.ts — SSE endpoint: snapshot-first-then-subscribe ordering, 15 s : ping heartbeat, X-Accel-Buffering: no, cleanup on cancel; broadcast pushes wrapped in data: <json>\n\n frames.
lib/sim/engine.ts — broadcast hook in tickNow() after each locked tick, serializing via the same getStateSnapshot() as /api/state; failures logged, never fatal.
Process-wide singletons on globalThis (SSE subscriber set, sql.js DB promise, tick lock) because Next.js gives instrumentation.ts and route handlers separate module instances — without this, background-tick pushes would never reach route-handler subscribers.
lib/useGameState.ts — EventSource primary with polling fallback; native reconnect, no custom logic.
Tests: unit lib/sse.test.ts (3) + app/api/events/route.test.ts (1); e2e e2e/events.spec.ts (+1) — node-side fetch asserts content-type, first event parses as a snapshot, and ≥1 more event arrives with an advanced clock.
Closes #129
Clients no longer rely on pure polling for live updates: new `GET /api/events` streams full game-state snapshots as Server-Sent Events — the current snapshot immediately on connect, then one after every sim tick (~1 s), so the UI updates push-driven even while idle. Polling remains a fully functional automatic fallback (5 s silence watchdog + stream-error trigger) and `/api/state` behavior is unchanged.
- `lib/sse.ts` — tiny in-process fan-out registry (`subscribe`/`broadcast`, per-subscriber try/catch).
- `app/api/events/route.ts` — SSE endpoint: snapshot-first-then-subscribe ordering, 15 s `: ping` heartbeat, `X-Accel-Buffering: no`, cleanup on cancel; broadcast pushes wrapped in `data: <json>\n\n` frames.
- `lib/sim/engine.ts` — broadcast hook in `tickNow()` after each locked tick, serializing via the same `getStateSnapshot()` as `/api/state`; failures logged, never fatal.
- Process-wide singletons on `globalThis` (SSE subscriber set, sql.js DB promise, tick lock) because Next.js gives instrumentation.ts and route handlers separate module instances — without this, background-tick pushes would never reach route-handler subscribers.
- `lib/useGameState.ts` — EventSource primary with polling fallback; native reconnect, no custom logic.
- OpenAPI spec gains `GET /api/events`; exact-endpoint-list assertions move twelve → thirteen (`lib/openapi.test.ts`, `e2e/api.spec.ts`).
- Tests: unit `lib/sse.test.ts` (3) + `app/api/events/route.test.ts` (1); e2e `e2e/events.spec.ts` (+1) — node-side fetch asserts content-type, first event parses as a snapshot, and ≥1 more event arrives with an advanced clock.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #129
Clients no longer rely on pure polling for live updates: new
GET /api/eventsstreams full game-state snapshots as Server-Sent Events — the current snapshot immediately on connect, then one after every sim tick (~1 s), so the UI updates push-driven even while idle. Polling remains a fully functional automatic fallback (5 s silence watchdog + stream-error trigger) and/api/statebehavior is unchanged.lib/sse.ts— tiny in-process fan-out registry (subscribe/broadcast, per-subscriber try/catch).app/api/events/route.ts— SSE endpoint: snapshot-first-then-subscribe ordering, 15 s: pingheartbeat,X-Accel-Buffering: no, cleanup on cancel; broadcast pushes wrapped indata: <json>\n\nframes.lib/sim/engine.ts— broadcast hook intickNow()after each locked tick, serializing via the samegetStateSnapshot()as/api/state; failures logged, never fatal.globalThis(SSE subscriber set, sql.js DB promise, tick lock) because Next.js gives instrumentation.ts and route handlers separate module instances — without this, background-tick pushes would never reach route-handler subscribers.lib/useGameState.ts— EventSource primary with polling fallback; native reconnect, no custom logic.GET /api/events; exact-endpoint-list assertions move twelve → thirteen (lib/openapi.test.ts,e2e/api.spec.ts).lib/sse.test.ts(3) +app/api/events/route.test.ts(1); e2ee2e/events.spec.ts(+1) — node-side fetch asserts content-type, first event parses as a snapshot, and ≥1 more event arrives with an advanced clock.