feat(service,host): a task's pages and calls follow over the live layer

Merged
CH
6d ago
+829-47

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.ts has 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 segment seq, and the count of spoken rows the voice wrote for it.
    • Each statement joins member through the task's workspace and is held to taskSeenBy, as boardLiveStatement is.
  • live.taskRecord (GET /api/live/task-record?id=&userId=) sits beside live.taskBoard in contract/live.ts and live-procedures.ts:
    • sameUser guards 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.ts decodes 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 one followLive. followBoard and the new followRecord both use it, and openRecord and recordFollow mirror the board's.
  • showActivity no longer reads the pages. callEnded no longer reads the transcript, but it still reads the work and wakes the conversation, once now and once after TRANSCRIPT_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 TasksView fields, 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-follow and task-client