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+1352+0123456789012345678901234567890123456789-121-121-012345678901234567890123456789
Second of two PRs making the Issues list live over Neon Realtime, stacked on #3902 (which stacks on #3287).
What
- Host:
issue-follow.tsfollows/api/live/issueswithliveRows(the shared socket from #3287), builds the list withissuesOfLiveRows, 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.tsaddsissues.follow/issues.unfollow(aFiberMapkeyed by workspace, gated like the other calls) and emitsissues.changed. The signed query never leaves the host. - Main:
issue-follow-acts.tscounts each panel's follows and sends changes onapp:issuesonly to the panels following that workspace. It works the same way as the automations follow (#3297). - Window:
useIssuesfollows the list while the page is open. TheuseMinute()re-read and the re-read after each create or change are gone. The first read and Try again still useissues.list. The issue page now shows whichever is newer: the followed list's copy or its own read, souseIssueupdates live without new plumbing. - The "gone quiet" filter still compares against now on the client, unchanged. The
/api/issuesroute 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.jsonandissue-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'sonIssues. - Sign-out and account switch. The issues composer has a
resetthat 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 incompose-issues.test.ts. - txid writes, as Neon's mutations guide does it.
createIssueandchangeIssuereturnpg_current_xact_id()::textin the write's ownRETURNING, and the answer carries it astxid. Before answering the window, the host waits for it with the followed subscription'sawaitTxId, 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.jsonandissue-view-issueViewSchema.jsongain the optionaltxid, 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 tomutableFunctionsOf(...) = [].
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