F2 SLICE-3: ThreadController on the Comments API + thread mutations (#4)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 06:53:13 -07:00
parent e5817eef0a
commit 1bc7a369b7
5 changed files with 447 additions and 18 deletions
+48
View File
@@ -0,0 +1,48 @@
/**
* vscode-free mutations on the Artifact (spec §6.5). Keeping thread logic here
* (not in the vscode controller) keeps it unit-testable; ThreadController is a
* thin adapter that calls these and persists via CoauthorStore.
*/
import {
newId,
type Artifact,
type Fingerprint,
type Provenance,
type ThreadStatus,
} from "./model";
export interface NewMessage {
author: Provenance;
body: string;
}
function message(m: NewMessage) {
return { id: newId("m"), author: m.author, body: m.body, createdAt: new Date().toISOString() };
}
/** Lazily-created anchor + thread for a new region discussion (PUC-1). */
export function addThread(
artifact: Artifact,
fingerprint: Fingerprint,
first: NewMessage,
): { threadId: string; anchorId: string } {
const anchorId = newId("a");
const threadId = newId("t");
artifact.anchors[anchorId] = { fingerprint };
artifact.threads.push({ id: threadId, anchorId, status: "open", messages: [message(first)] });
return { threadId, anchorId };
}
/** Append a reply (PUC-2). Throws if the thread is unknown. */
export function appendMessage(artifact: Artifact, threadId: string, m: NewMessage): void {
const t = artifact.threads.find((x) => x.id === threadId);
if (!t) throw new Error(`unknown thread id: ${threadId}`);
t.messages.push(message(m));
}
/** Resolve/reopen a thread (PUC-2). Throws if the thread is unknown. */
export function setStatus(artifact: Artifact, threadId: string, status: ThreadStatus): void {
const t = artifact.threads.find((x) => x.id === threadId);
if (!t) throw new Error(`unknown thread id: ${threadId}`);
t.status = status;
}