Skip to content

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

Closed
charleslpan wants to merge 9 commits into
luke/issues-live-servicefrom
luke/issues-live-follow
Closed

charleslpan wants to merge 9 commits into
luke/issues-live-servicefrom
luke/issues-live-follow

Conversation

@charleslpan

@charleslpan charleslpan commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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 feat(host): every read the host follows over Realtime shares one socket (P1) #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 (feat(host,panel): the automations list and an automation's runs are pushed to the pages that draw them #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 (feat(host): a window follows a plan's or an agent's inbox as a stream of pushed changes #3264/feat(panel,host): the windows' inbox reads become pushed follows, and the held inbox reads are retired #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 feat(host): every read the host follows over Realtime shares one socket (P1) #3287's 1afaa1b). 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

View guided diff Turn on auto-fix

@vercel

vercel Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
luke-web Ready Ready Preview Oct 20, 2026 3:20am UTC

Request Review

charleslpan and others added 8 commits October 20, 2026 02:46
… it from Neon Realtime, read on no clock

The host follows a workspace's sealed issues statement with `liveRows` and
emits `ISSUES_CHANGED`; main hands each change to the panels following it on
`app:issues`, counting their follows as the automations follow does. The
window follows the list while its page stands and drops the minute re-read
and the re-read after every write; an issue's page draws the followed list's
copy where it is newer, so it is live too.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>

# Conflicts:
#	apps/desktop/src/main/ipc/register-desktop-ipc.ts
#	packages/host/src/acts.ts
#	packages/host/src/bridge.ts
#	packages/host/src/compose-issues.test.ts
#	packages/panel/src/desktop/issues-page.test.ts
The in-process bridge and the web host answer the new issues follow acts
and app:issues channel as a tab does every push: nothing, since it reads
the list once.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
Main's command palette tests mount the Issues list, which now follows its
workspace's issues and listens on app:issues, so their fake bridges answer
onIssues with nothing pushed.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
… change, as a window is

The follow-counting rows move into `@luke/host/act-router`, generic over the
sender; desktop main spreads them as before, and the web host spreads them
too and says each change through the in-process bridge's `onIssues`.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
The issues composer gains a `reset` that clears its follows, which the gate
closing runs beside the tasks', terminals', and coding agents', so the
account that was signed in is subscribed to no more and pushes no more of
its issues to the windows.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
…t waits for it on the followed list

Filing and changing an issue read `pg_current_xact_id()::text` in the write's
own `RETURNING`, and the answer carries it as `txid`. Before it answers the
window, the host waits for that transaction on the workspace's followed
subscription with `awaitTxId`, bounded at five seconds, so the push already
holds the write when the window lands it, as Neon's optimistic mutations
guide does it. A write that changed nothing carries no txid and is not
waited on, nor is one of a workspace no window follows. The issues live
statement joins the live statements held to calling only immutable
functions.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
… for good once signed out

Each seal and renewal of a workspace's issues names the account signed in
as the follow started, as #3287's follows do; a seal or renewal refused as
signed out ends the follow rather than asking again, and a follow that
broke says why in the log before it starts again.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
…, and a write waits ten seconds for its txid

The issues composer keeps the workspaces main follows across a sign-out,
and the gate opening follows them again under the account now signed in,
so a page that stayed open is live again without a remount; a workspace
let go meanwhile is not. A write's answer waits up to ten seconds for its
transaction, as the other follows and Neon's guide do.

Co-authored-by: luke-agent[bot] <339870089+luke-agent[bot]@users.noreply.github.com>
@luke-agent

luke-agent Bot commented Oct 20, 2026

Copy link
Copy Markdown
Contributor

Closing as superseded: #4016 (merged Oct 20) removed Luke's own workspace issues from main, so there's no Issues list left to follow over Realtime. The branch is kept.

@luke-agent luke-agent Bot closed this Oct 20, 2026

This branch was successfully deployed

1 active deployment
Preview — 22c8e267 Deployed Oct 20, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant