feat(service): the service's held reads and waits on a row are woken by Neon Realtime
This is part of making Luke as realtime as possible on Neon Realtime and removing hand-rolled freshness logic. It is stacked on #3232 (which is stacked on #3217), and its base is set to #3232's branch.
The desktop-side PRs move the desktop off the held long-polls. This one is the service side. The held routes that released desktops still poll, and the service's own waits on a row, now wait on Realtime instead of re-reading the store every 500 ms or 250 ms.
What changes
live/live-wake.ts:LiveWake.- It's a
Context.Referencewith no polling default: unprovided, every watch dies. The web runtime providesliveWakeFromEnvironment, using the sameNEON_REALTIME_URL/NEON_REALTIME_SECRETasLiveSealing; missing config is logged as an error and every watch fails loudly. Tests provide a scripted wake (testing/live-wake.ts). - Each function instance holds one Realtime direct server, following Neon's "Subscribe in trusted backend code" guide, and closes it with the runtime.
- A wait subscribes to its statements as raw updates for its length and unsubscribes after. Any update wakes it; a missed update costs a look on the safety beat.
- Realtime only signals; every answer is still read by the service's own statement.
- It's a
http/held-read.ts:lookedUntil(look, hold, live)looks, then waits for the next change of the watched statements or a 5 s safety beat. It takes no poll.heldUntilMovedis built on it.
- Callers:
- The account, plan, and agent inbox held reads (
inbox-page.ts) watch the live statements from #3217 and #3232. - Plan comments (
plan-comments-app.ts),look_at_board(board-look.ts), andrun_on_mac's wait for a result (mac-command.tsawaitedResult) watch small statements of their own. These carry no words, image, or command output:planCommentStampsLiveStatement,boardImageLiveStatement,macCommandSettledLiveStatement.
- The account, plan, and agent inbox held reads (
Transcripts, the planning conversation, and the Mac's own held read are left to their own stacks, which other agents are working on.
Tests
live-wake.test.tschecks four things:- with no wake provided, a wait fails rather than looking on a clock;
- when watched, it looks again on a change and on the safety beat;
- the wake over a scripted direct server subscribes per statement, wakes on updates (keeping one that no wait took yet), unsubscribes when the scope closes, and leaves a wait on its safety beat when a subscription is refused.
- PGlite tests for each new statement:
- the comment statement equals
planCommentStampsfor a member, a teammate, and an outsider; - the board statement moves on a drawing and on a scene save, carries no image, and is empty for a stranger;
- the command statement says settled once the command settles, and reads nothing of another account's.
- the comment statement equals
pnpm check:affected: typecheck, lint, knip, and discarded-effect pass, and all tests pass exceptapps/desktop/src/main/app-menu.test.ts, which can't load Electron in the sandbox. The web bundle tests pass.
Realtime is on in production and its variables are in Vercel, so the service's waits follow over Realtime alone, with no polling fallback.
Made with Luke
Summary by cubic
The service's held reads and waits on a row are now woken by Neon Realtime instead of re-reading the store on a poll, so waits like the inbox, plan comments, look_at_board, and run_on_mac's result arrive when the rows change rather than on a clock. Deployments without Realtime, and any watch that breaks or is refused, keep polling as before.
- Adds
LiveWake, a runtime-provided context that subscribes each wait's statements over Neon Realtime's direct server for the length of the wait; every update wakes it and an error hands it back to its poll. - Adds
lookedUntilinheld-read.ts, which looks, then waits on the watched statements or a 5-second safety beat;heldUntilMovedis built on it. - Adds small live statements (
planCommentStampsLiveStatement,boardImageLiveStatement,macCommandSettledLiveStatement) that carry no words, image, or command output, so only freshness signals travel through Realtime and every answer is still read by the service's own statement.