feat(service,host): a task's pages and calls follow over the live layer
Merged
CH
6d ago+829+829+012345678901234567890123456789-47-47-01234567890123456789
What was asked
Move an open task's pages and calls onto the central live layer (liveRows and the sealed live procedures), the same way #4116 moved the board. The panel then reads them again when their rows move, instead of when a call's activity or end suggests they might have. The board half and the txid confirmation from earlier versions of this PR are dropped.
Service
packages/service/src/tasks/task-record-live.tshas two deterministic statements, one per part:- pages: each page's newest version number.
- calls: one row per call that has words, with its
closedAt,closeReason,usage,model, last segmentseq, and the count of spoken rows the voice wrote for it. - Each statement joins
memberthrough the task's workspace and is held totaskSeenBy, asboardLiveStatementis.
live.taskRecord(GET /api/live/task-record?id=&userId=) sits besidelive.taskBoardincontract/live.tsandlive-procedures.ts:sameUserguards it.- It is not found for a task the account doesn't reach.
- It is sealed or polled (held past
after) as every live read is.
client.recordLive(taskId, userId, poll, after).
Host
task-record-follow.tsdecodes the deliveries and answers the parts each one moved. A move of the pages reads the pages; a move of an ended call's row reads the transcript. A standing call's words read nothing. The first delivery moves every part.- In
compose-tasks.ts,followBoard's seal, poll, backoff and signed-out handling become onefollowLive.followBoardand the newfollowRecordboth use it, andopenRecordandrecordFollowmirror the board's. showActivityno longer reads the pages.callEndedno longer reads the transcript, but it still reads the work and wakes the conversation, once now and once afterTRANSCRIPT_SETTLE. The calls mark doesn't move with the turns and journals the Work tab reads, and main's conversation read still parks.- There is no act-port procedure. The pages and the transcript are
TasksViewfields, which reach every window, web tabs included, through the app-state push.
Tests
task-record-live.test.ts: each part moves exactly when its held read (listPages,readTranscript) would answer something new, and a stranger reads no rows.live-procedures.test.ts: the record is sealed a part each for the owner, not found for a stranger, and signed out for another account.live-statements.test.ts: both statements call only immutable functions and seal through Neon's Drizzle adapter.compose-tasks.test.ts: the pages are read on their mark and the transcript on an ended call's mark; nothing is read on the call's activity; the call's end reads the work twice; and the subscriptions go when the task is closed.
Checks run locally
pnpm check:affected is optional now, so I ran its pieces:
- typecheck for features, service and host
- biome and oxlint on the changed files
- discarded-effect, knip, and
repository-checks.sh - vitest for the files above, plus
board-follow,live-followandtask-client