Stream each task's transcript over its own SSE; keep session note order

This commit is contained in:
Ian Gulliver
2026-10-14 23:53:17 -07:00
parent eabfc10c8d
commit 2b4d92e7d5
18 changed files with 304 additions and 191 deletions
+32 -28
View File
@@ -1,4 +1,4 @@
import { get } from "./api.js";
import { get, subscribe } from "./api.js";
export const state = {
me: undefined,
projects: [],
@@ -71,7 +71,9 @@ export async function loadInbox() {
state.inbox = await get("/api/inbox");
notify("inbox");
}
let liveDuringLoad;
let closeTaskStream;
let taskBuffer = [];
let taskFlush = 0;
const visitsKey = "byre.visits";
export function lastVisits() {
return JSON.parse(localStorage.getItem(visitsKey) ?? "{}");
@@ -82,31 +84,44 @@ function markVisited(taskId) {
}
localStorage.setItem(visitsKey, JSON.stringify({ ...lastVisits(), [taskId]: Date.now() }));
}
export async function selectTask(taskId) {
export function selectTask(taskId) {
markVisited(state.selectedTaskId);
markVisited(taskId);
closeTaskStream?.();
closeTaskStream = undefined;
clearTimeout(taskFlush);
taskFlush = 0;
taskBuffer = [];
state.selectedTaskId = taskId;
state.transcript = [];
state.lastTranscriptSeq = 0;
liveDuringLoad = undefined;
notify("selection");
if (!taskId) {
return;
}
const held = [];
liveDuringLoad = held;
for (;;) {
const page = await get("/api/tasks/{taskId}/events", { path: { taskId }, query: { after: state.lastTranscriptSeq, limit: 1000 } });
if (state.selectedTaskId !== taskId || liveDuringLoad !== held) {
return;
}
appendTranscript(page);
if (page.length < 1000) {
break;
}
closeTaskStream = subscribe(0, taskId, queueTaskEvent, () => undefined);
}
function queueTaskEvent(ev) {
taskBuffer.push(ev);
if (!taskFlush) {
taskFlush = setTimeout(flushTaskEvents, 0);
}
liveDuringLoad = undefined;
appendTranscript(held);
}
function flushTaskEvents() {
const events = taskBuffer;
taskBuffer = [];
taskFlush = 0;
let stored = [];
for (const ev of events) {
if (ev.seq !== 0) {
stored.push(ev);
continue;
}
appendTranscript(stored);
stored = [];
notify("stream", ev);
}
appendTranscript(stored);
}
function appendTranscript(events) {
const fresh = events.filter((ev) => ev.seq > state.lastTranscriptSeq);
@@ -119,20 +134,9 @@ function appendTranscript(events) {
}
export function handleEvent(ev) {
if (ev.seq === 0) {
if (ev.task_id && ev.task_id === state.selectedTaskId && !liveDuringLoad) {
notify("stream", ev);
}
notify("event", ev);
return;
}
if (ev.task_id && ev.task_id === state.selectedTaskId) {
if (liveDuringLoad) {
liveDuringLoad.push(ev);
}
else {
appendTranscript([ev]);
}
}
switch (ev.kind) {
case "user.updated":
if (ev.payload["login"] === state.me?.login) {