feat(host,desktop): the Issues list is pushed to the panels that draw it from Neon Realtime, read on no clock

Draft
CH
10d ago
+1352-121

Second of two PRs making the Issues list live over Neon Realtime, stacked on #3902 (which stacks on #3287).

What

  • Host: issue-follow.ts follows /api/live/issues with liveRows (the shared socket from #3287), builds the list with issuesOfLiveRows, and publishes it. If a seal goes unanswered or a subscription stops, it publishes the failure (if there is one) and reseals after 5 s. There's no fallback and nothing reads on a clock. compose-issues.ts adds issues.follow / issues.unfollow (a FiberMap keyed by workspace, gated like the other calls) and emits issues.changed. The signed query never leaves the host.
  • Main: issue-follow-acts.ts counts each panel's follows and sends changes on app:issues only to the panels following that workspace. It works the same way as the automations follow (#3297).
  • Window: useIssues follows the list while the page is open. The useMinute() re-read and the re-read after each create or change are gone. The first read and Try again still use issues.list. The issue page now shows whichever is newer: the followed list's copy or its own read, so useIssue updates live without new plumbing.
  • The "gone quiet" filter still compares against now on the client, unchanged. The /api/issues route is unchanged.

Tests

  • compose-issues.test.ts: live rows get pushed newest-first with no extra reads, and unfollowing unsubscribes. An unanswered seal is published as a failure and asked for again after the pause, then nothing more once unfollowed. Nothing is followed behind a closed gate.
  • issues-page.test.ts: the list is followed while the page is open and drawn from pushes (a failed push keeps the list), then let go on unmount. An issue's page shows a teammate's change carried by the followed list.
  • New goldens under LUKE_UPDATE_FIXTURES=1: issue-view-issueFollowSchema.json and issue-view-issuesFollowedSchema.json. Both are new schemas only.

After the restack

  • Web tab follows live. The follow-counting rows now live in @luke/host/act-router (issue-follow-acts.ts) and are generic over the sender, as the inbox follows (#3264/#3306) are. Desktop main and the web host both use them, and the web host pushes each change through the in-process bridge's onIssues.
  • Sign-out and account switch. The issues composer has a reset that clears its follows, and the gate closing runs it beside the tasks', terminals' and agents' resets. The previous account's subscription closes and pushes nothing more. The test is in compose-issues.test.ts.
  • txid writes, as Neon's mutations guide does it. createIssue and changeIssue return pg_current_xact_id()::text in the write's own RETURNING, and the answer carries it as txid. Before answering the window, the host waits for it with the followed subscription's awaitTxId, with a 10 s timeout, so the push already holds the write when the window lands it. A write that changed nothing carries no txid and isn't waited on, nor is a write in a workspace no window follows. Golden diffs (LUKE_UPDATE_FIXTURES=1): issue-wire-issueAnswerSchema.json and issue-view-issueViewSchema.json gain the optional txid, and nothing else changes.
  • Same-user seals (after #3287's 1afaa1bea). The follow names the account it started for on every seal and renewal. A seal or renewal refused as signed out ends the follow for good, and a follow that broke is logged before it restarts. Every workspace main still follows is followed again when the account signs back in, so an Issues page left open goes live again without a remount.
  • Immutability. The issues live statement joins live-statements.test.ts, which holds every sealed statement to mutableFunctionsOf(...) = [].

Checks run locally

lint, oxlint, knip, discarded-effect, repository checks, typecheck of gateway/features/host/panel/desktop, and the host, panel, and desktop test suites (excluding app-menu.test.ts).

  • issue-follow-acts.test.ts: two panels follow and the host follows once; one unfollows and the host follow stays while changes reach only the remaining panel; the last unfollows and it ends. A page that's gone releases every follow it held.

Made with Luke