F3 SLICE-3/4 review fixes: counted self-writes, persist deletion-to-empty, render all visible editors (#6)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-10 10:12:07 -07:00
parent 4b27acfcae
commit 20b709f794
4 changed files with 71 additions and 15 deletions
+25 -7
View File
@@ -11,19 +11,33 @@ import * as path from "node:path";
import { emptyArtifact, serializeArtifact, type Artifact } from "./model";
export class CoauthorStore {
/** sidecar paths this store just wrote, so the shared watcher can ignore them. */
private readonly selfWrites = new Set<string>();
/**
* Pending-write counts keyed by absolute sidecar path. A Map (not Set) because
* one doc save can trigger TWO store.update() calls (ThreadController then
* AttributionController), both writing the same sidecar — a Set would let the
* second watcher event leak through as "external" (comment-UI flicker).
*/
private readonly selfWrites = new Map<string, number>();
/** @param rootDir absolute workspace-folder root that owns the `.threads/` tree. */
constructor(private readonly rootDir: string) {}
/**
* Delete-and-report: true iff `fsPath` was a sidecar we just wrote via
* `update`. The shared watcher (extension.ts) calls this to suppress
* self-write events before fanning out to the controllers.
* Decrement-and-report: true iff `fsPath` was a sidecar this store just
* wrote via `update`. Each successful `update` increments the counter once;
* each call here decrements it (deleting the key at zero). The shared watcher
* (extension.ts) calls this to suppress self-write events before fanning out
* to the controllers.
*/
consumeSelfWrite(fsPath: string): boolean {
return this.selfWrites.delete(fsPath);
const count = this.selfWrites.get(fsPath);
if (!count) return false;
if (count === 1) {
this.selfWrites.delete(fsPath);
} else {
this.selfWrites.set(fsPath, count - 1);
}
return true;
}
/** `.threads/<repo-relative-docPath>.json` (spec §6.3). */
@@ -61,8 +75,12 @@ export class CoauthorStore {
for (const id of Object.keys(artifact.anchors)) {
if (!referenced.has(id)) delete artifact.anchors[id];
}
this.selfWrites.add(this.sidecarPath(docPath));
this.save(docPath, artifact);
// Increment AFTER the synchronous write: fs.writeFileSync completes before
// this line executes, and watcher events are delivered async, so marking
// post-write is race-free for suppression purposes.
const p = this.sidecarPath(docPath);
this.selfWrites.set(p, (this.selfWrites.get(p) ?? 0) + 1);
return artifact;
}
}