feat(f9): preview gains authorship mode + attribution dep + setMode wiring
Per-panel mode (default changes); refresh branches to renderAuthorship reading AttributionController.spansFor; webview setMode message; extension.ts reorders the F7 controller after attribution. getMode/setMode test seams. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+11
-10
@@ -70,16 +70,6 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
// still works — PUC-5.
|
// still works — PUC-5.
|
||||||
const globalSidecarStore = new GlobalSidecarStore(baselineStorageDir ?? "");
|
const globalSidecarStore = new GlobalSidecarStore(baselineStorageDir ?? "");
|
||||||
|
|
||||||
// --- F7: rendered track-changes preview (Feature #21) — workspace-INDEPENDENT ---
|
|
||||||
// Like F6, F7 works on any markdown doc (incl. untitled / out-of-folder), so it
|
|
||||||
// is constructed regardless of an open folder and its command is always live.
|
|
||||||
// It reuses the F6 baseline (INV-20) and adds no persistence.
|
|
||||||
const trackChangesPreviewController = new TrackChangesPreviewController(
|
|
||||||
diffViewController,
|
|
||||||
context.extensionUri,
|
|
||||||
);
|
|
||||||
context.subscriptions.push(trackChangesPreviewController);
|
|
||||||
|
|
||||||
// --- F8: authoring on ANY document (in-folder, out-of-folder, untitled) ---
|
// --- F8: authoring on ANY document (in-folder, out-of-folder, untitled) ---
|
||||||
// The router routes per-document: in-workspace file: → the committable repo
|
// The router routes per-document: in-workspace file: → the committable repo
|
||||||
// `.threads/` sidecar (CoauthorStore, INV-2); out-of-folder file: + untitled:
|
// `.threads/` sidecar (CoauthorStore, INV-2); out-of-folder file: + untitled:
|
||||||
@@ -99,6 +89,17 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
|||||||
const attributionController = new AttributionController(sidecarRouter, root, versionGuard);
|
const attributionController = new AttributionController(sidecarRouter, root, versionGuard);
|
||||||
context.subscriptions.push(attributionController);
|
context.subscriptions.push(attributionController);
|
||||||
|
|
||||||
|
// --- F7: rendered track-changes preview (Feature #21) + F9 authorship mode ---
|
||||||
|
// Workspace-INDEPENDENT (works on any markdown doc, reuses the F6 baseline,
|
||||||
|
// INV-20). Constructed AFTER attribution so F9's authorship view can read F3
|
||||||
|
// spans (AttributionController.spansFor).
|
||||||
|
const trackChangesPreviewController = new TrackChangesPreviewController(
|
||||||
|
diffViewController,
|
||||||
|
context.extensionUri,
|
||||||
|
attributionController,
|
||||||
|
);
|
||||||
|
context.subscriptions.push(trackChangesPreviewController);
|
||||||
|
|
||||||
// --- F4: propose/accept (Feature #12) ---
|
// --- F4: propose/accept (Feature #12) ---
|
||||||
const proposalController = new ProposalController(sidecarRouter, attributionController, root, versionGuard);
|
const proposalController = new ProposalController(sidecarRouter, attributionController, root, versionGuard);
|
||||||
context.subscriptions.push(proposalController);
|
context.subscriptions.push(proposalController);
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import * as path from "node:path";
|
|||||||
import { randomBytes } from "node:crypto";
|
import { randomBytes } from "node:crypto";
|
||||||
import * as vscode from "vscode";
|
import * as vscode from "vscode";
|
||||||
import type { DiffViewController } from "./diffViewController";
|
import type { DiffViewController } from "./diffViewController";
|
||||||
import { renderTrackChanges, diffBlocks, type BlockOp } from "./trackChangesModel";
|
import type { AttributionController } from "./attributionController";
|
||||||
|
import { renderTrackChanges, renderAuthorship, diffBlocks, type BlockOp } from "./trackChangesModel";
|
||||||
|
|
||||||
const VIEW_TYPE = "cowriting.trackChangesPreview";
|
const VIEW_TYPE = "cowriting.trackChangesPreview";
|
||||||
const DEBOUNCE_MS = 150;
|
const DEBOUNCE_MS = 150;
|
||||||
@@ -21,10 +22,13 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
|||||||
private readonly panels = new Map<string, vscode.WebviewPanel>();
|
private readonly panels = new Map<string, vscode.WebviewPanel>();
|
||||||
private readonly lastModel = new Map<string, BlockOp[]>();
|
private readonly lastModel = new Map<string, BlockOp[]>();
|
||||||
private readonly debounces = new Map<string, NodeJS.Timeout>();
|
private readonly debounces = new Map<string, NodeJS.Timeout>();
|
||||||
|
/** F9: per-panel view mode — track-changes (default) or authorship. */
|
||||||
|
private readonly mode = new Map<string, "changes" | "authorship">();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly diffView: DiffViewController,
|
private readonly diffView: DiffViewController,
|
||||||
private readonly extensionUri: vscode.Uri,
|
private readonly extensionUri: vscode.Uri,
|
||||||
|
private readonly attribution: AttributionController,
|
||||||
) {
|
) {
|
||||||
this.disposables.push(
|
this.disposables.push(
|
||||||
vscode.commands.registerCommand("cowriting.showTrackChangesPreview", () =>
|
vscode.commands.registerCommand("cowriting.showTrackChangesPreview", () =>
|
||||||
@@ -70,6 +74,18 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
|||||||
() => {
|
() => {
|
||||||
this.panels.delete(key);
|
this.panels.delete(key);
|
||||||
this.lastModel.delete(key);
|
this.lastModel.delete(key);
|
||||||
|
this.mode.delete(key);
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
this.disposables,
|
||||||
|
);
|
||||||
|
// F9: the webview's header toggle posts the chosen mode back.
|
||||||
|
panel.webview.onDidReceiveMessage(
|
||||||
|
(m: { type?: string; mode?: "changes" | "authorship" }) => {
|
||||||
|
if (m?.type === "setMode" && (m.mode === "changes" || m.mode === "authorship")) {
|
||||||
|
this.mode.set(key, m.mode);
|
||||||
|
this.refresh(document);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
this.disposables,
|
this.disposables,
|
||||||
@@ -97,14 +113,30 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
|||||||
if (doc) this.refresh(doc);
|
if (doc) this.refresh(doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Recompute the model + post HTML to the panel (no-op if no panel). */
|
/** Recompute the model + post HTML to the panel for its current mode (no-op if no panel). */
|
||||||
refresh(document: vscode.TextDocument): void {
|
refresh(document: vscode.TextDocument): void {
|
||||||
const key = document.uri.toString();
|
const key = document.uri.toString();
|
||||||
const panel = this.panels.get(key);
|
const panel = this.panels.get(key);
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
const baseline = this.diffView.getBaseline(key);
|
const mode = this.mode.get(key) ?? "changes";
|
||||||
const baselineText = baseline?.text ?? document.getText(); // no baseline → no marks
|
|
||||||
const current = document.getText();
|
const current = document.getText();
|
||||||
|
if (mode === "authorship") {
|
||||||
|
// F9: render the current buffer colored by F3 author, baseline-independent.
|
||||||
|
const spans = this.attribution.spansFor(document);
|
||||||
|
this.lastModel.set(key, diffBlocks(this.diffView.getBaseline(key)?.text ?? current, current));
|
||||||
|
void panel.webview.postMessage({
|
||||||
|
type: "render",
|
||||||
|
mode,
|
||||||
|
html: renderAuthorship(current, spans),
|
||||||
|
legend: {
|
||||||
|
claude: spans.some((s) => s.author === "claude"),
|
||||||
|
human: spans.some((s) => s.author === "human"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const baseline = this.diffView.getBaseline(key);
|
||||||
|
const baselineText = baseline?.text ?? current; // no baseline → no marks
|
||||||
const ops = diffBlocks(baselineText, current);
|
const ops = diffBlocks(baselineText, current);
|
||||||
this.lastModel.set(key, ops);
|
this.lastModel.set(key, ops);
|
||||||
const summary = {
|
const summary = {
|
||||||
@@ -112,11 +144,11 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
|||||||
removed: ops.filter((o) => o.kind === "removed").length,
|
removed: ops.filter((o) => o.kind === "removed").length,
|
||||||
changed: ops.filter((o) => o.kind === "changed").length,
|
changed: ops.filter((o) => o.kind === "changed").length,
|
||||||
};
|
};
|
||||||
const epoch = this.epochLabel(baseline);
|
|
||||||
void panel.webview.postMessage({
|
void panel.webview.postMessage({
|
||||||
type: "render",
|
type: "render",
|
||||||
|
mode,
|
||||||
html: renderTrackChanges(baselineText, current),
|
html: renderTrackChanges(baselineText, current),
|
||||||
epoch,
|
epoch: this.epochLabel(baseline),
|
||||||
summary,
|
summary,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -177,6 +209,16 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
|||||||
getLastModel(uriString: string): BlockOp[] | undefined {
|
getLastModel(uriString: string): BlockOp[] | undefined {
|
||||||
return this.lastModel.get(uriString);
|
return this.lastModel.get(uriString);
|
||||||
}
|
}
|
||||||
|
/** F9: current view mode for a panel (default track-changes). */
|
||||||
|
getMode(uriString: string): "changes" | "authorship" {
|
||||||
|
return this.mode.get(uriString) ?? "changes";
|
||||||
|
}
|
||||||
|
/** F9: set the view mode and re-render (the programmatic twin of the header toggle). */
|
||||||
|
setMode(uriString: string, mode: "changes" | "authorship"): void {
|
||||||
|
this.mode.set(uriString, mode);
|
||||||
|
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uriString);
|
||||||
|
if (doc) this.refresh(doc);
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
for (const t of this.debounces.values()) clearTimeout(t);
|
for (const t of this.debounces.values()) clearTimeout(t);
|
||||||
|
|||||||
Reference in New Issue
Block a user