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:
@@ -31,6 +31,12 @@ interface DocAttribution {
|
||||
orphans: AttributionRecord[];
|
||||
/** record metadata per live span id (updatedAt bookkeeping). */
|
||||
records: Map<string, AttributionRecord>;
|
||||
/**
|
||||
* true once this doc has had any span/record this session — lets save persist
|
||||
* deliberate deletion to empty (so stale records don't come back as phantom
|
||||
* orphans on reload).
|
||||
*/
|
||||
hadAttributions: boolean;
|
||||
}
|
||||
|
||||
const AGENT_DECO: vscode.DecorationRenderOptions = {
|
||||
@@ -77,7 +83,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
private state(docPath: string): DocAttribution {
|
||||
let s = this.docs.get(docPath);
|
||||
if (!s) {
|
||||
s = { docPath, spans: [], orphans: [], records: new Map() };
|
||||
s = { docPath, spans: [], orphans: [], records: new Map(), hadAttributions: false };
|
||||
this.docs.set(docPath, s);
|
||||
}
|
||||
return s;
|
||||
@@ -110,6 +116,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
}
|
||||
}
|
||||
s.spans = coalesce(s.spans);
|
||||
if (artifact.attributions.length > 0) s.hadAttributions = true;
|
||||
}
|
||||
this.render(document);
|
||||
}
|
||||
@@ -135,7 +142,9 @@ export class AttributionController implements vscode.Disposable {
|
||||
return;
|
||||
}
|
||||
const s = this.state(docPath);
|
||||
for (const change of e.contentChanges) {
|
||||
// Sort descending by offset so earlier changes don't invalidate later offsets
|
||||
// (VS Code's order is undocumented; defensive sort is the safe guarantee).
|
||||
for (const change of [...e.contentChanges].sort((a, b) => b.rangeOffset - a.rangeOffset)) {
|
||||
const edit = {
|
||||
start: change.rangeOffset,
|
||||
end: change.rangeOffset + change.rangeLength,
|
||||
@@ -149,6 +158,7 @@ export class AttributionController implements vscode.Disposable {
|
||||
turnId: hit?.turnId,
|
||||
});
|
||||
}
|
||||
if (s.spans.length > 0) s.hadAttributions = true;
|
||||
this.render(e.document);
|
||||
}
|
||||
|
||||
@@ -193,7 +203,10 @@ export class AttributionController implements vscode.Disposable {
|
||||
if (!this.isTracked(document)) return;
|
||||
const docPath = this.docPathOf(document.uri);
|
||||
const s = this.docs.get(docPath);
|
||||
if (!s || (s.spans.length === 0 && s.orphans.length === 0)) return;
|
||||
// Allow save when hadAttributions is true even if spans/orphans are now empty:
|
||||
// that means the user deliberately deleted all attributed text, and we must
|
||||
// persist a.attributions=[] so stale records don't return as phantom orphans.
|
||||
if (!s || (!s.hadAttributions && s.spans.length === 0 && s.orphans.length === 0)) return;
|
||||
const text = document.getText();
|
||||
const now = new Date().toISOString();
|
||||
const records: AttributionRecord[] = [];
|
||||
@@ -239,14 +252,21 @@ export class AttributionController implements vscode.Disposable {
|
||||
}
|
||||
|
||||
private render(document: vscode.TextDocument): void {
|
||||
const editor = vscode.window.visibleTextEditors.find((e) => e.document === document);
|
||||
if (!editor || !this.isTracked(document)) return;
|
||||
if (!this.isTracked(document)) return;
|
||||
const s = this.docs.get(this.docPathOf(document.uri));
|
||||
const spans = this.visible && s ? s.spans : [];
|
||||
const toRange = (sp: LiveSpan) =>
|
||||
new vscode.Range(document.positionAt(sp.start), document.positionAt(sp.end));
|
||||
editor.setDecorations(this.agentType, spans.filter((x) => x.author.kind === "agent").map(toRange));
|
||||
editor.setDecorations(this.humanType, spans.filter((x) => x.author.kind === "human").map(toRange));
|
||||
const agentRanges = spans.filter((x) => x.author.kind === "agent").map(toRange);
|
||||
const humanRanges = spans.filter((x) => x.author.kind === "human").map(toRange);
|
||||
// Apply decorations to ALL visible split-editors showing this document, not
|
||||
// just the first match — each editor pane has its own decoration layer.
|
||||
for (const editor of vscode.window.visibleTextEditors) {
|
||||
if (editor.document === document) {
|
||||
editor.setDecorations(this.agentType, agentRanges);
|
||||
editor.setDecorations(this.humanType, humanRanges);
|
||||
}
|
||||
}
|
||||
this.renderStatus(s);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user