feat: authorship + change annotations in the built-in Markdown preview (D3/D21, PUC-3); cowriting.annotations toggle
Task 7 of the native-surfaces migration plan. Pure previewAnnotations.ts (annotateSource + cowritingMarkdownItPlugin) reuses trackChangesModel.ts's sentinel discipline (#33/#47), generalized to a 3-way ins-claude/ins-human/del tag (exported injectSentinels/sentinelsToSpans, colorByAuthor unaffected, 84/84 existing tests pass unmodified). Host hook in extension.ts wires registry/diffView/attribution/proposals/config into the plugin, exposed via CowritingApi.extendMarkdownIt + a previewAnnotationHost test seam. cowriting.annotations setting + toggleAnnotations command/menu. Q4 mermaid (previewScripts + options.highlight fence override) implemented per the proven bierner.markdown-mermaid pattern; intra-diagram diff augmentation scoped out (unverifiable via the structural E2E harness) — see task-7-report.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ import { EditorProposalController } from "./editorProposalController";
|
||||
import { isAuthorable, routeEdit } from "./workspacePath";
|
||||
import { CoeditingRegistry } from "./coeditingRegistry";
|
||||
import { ScmSurfaceController } from "./scmSurface";
|
||||
import type MarkdownIt from "markdown-it";
|
||||
import { cowritingMarkdownItPlugin, type AnnotationInputs } from "./previewAnnotations";
|
||||
|
||||
const CHANNEL_NAME = "Cowriting (Cline SDK)";
|
||||
|
||||
@@ -34,6 +36,13 @@ export interface CowritingApi {
|
||||
editorProposalController: EditorProposalController;
|
||||
coeditingRegistry: CoeditingRegistry;
|
||||
scmSurfaceController: ScmSurfaceController;
|
||||
/** Task 7 test seam: the REAL production `inputsFor`, so E2E exercises the
|
||||
* actual registry/diffView/attribution/proposals/config wiring (not a
|
||||
* hand-rolled reimplementation of it). */
|
||||
previewAnnotationHost: { inputsFor(env: unknown): AnnotationInputs | undefined };
|
||||
/** Task 7 (D3/D21): the markdown-it plugin factory VS Code's built-in preview
|
||||
* loads via `markdown.markdownItPlugins` (package.json). */
|
||||
extendMarkdownIt: (md: unknown) => unknown;
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): CowritingApi | undefined {
|
||||
@@ -391,6 +400,56 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
||||
vscode.workspace.textDocuments.forEach(renderIfOpen);
|
||||
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument(renderIfOpen));
|
||||
|
||||
// --- Task 7 (D3/D21, PUC-3): annotations in the BUILT-IN Markdown preview ---
|
||||
// `env.currentDocument` (VS Code's markdown-it render env) is an OBSERVED
|
||||
// preview-engine behavior, not a documented contract, so `lastActiveCoedited`
|
||||
// is the fallback that keeps single-doc correctness when it's absent — the
|
||||
// last editor that WAS coediting when it lost focus (e.g. focus moved to the
|
||||
// preview pane itself) stays the annotation target until a different
|
||||
// coedited doc becomes active.
|
||||
let lastCoeditedUri: string | undefined;
|
||||
const trackLastCoedited = (ed: vscode.TextEditor | undefined) => {
|
||||
if (ed && coeditingRegistry.isCoediting(ed.document.uri)) lastCoeditedUri = ed.document.uri.toString();
|
||||
};
|
||||
trackLastCoedited(vscode.window.activeTextEditor);
|
||||
context.subscriptions.push(
|
||||
vscode.window.onDidChangeActiveTextEditor(trackLastCoedited),
|
||||
// The active editor doesn't itself change when `cowriting.coeditDocument`
|
||||
// enters IT into coediting — re-check on every gate change too, so the
|
||||
// very first render after entering already has an annotation target.
|
||||
coeditingRegistry.onDidChange(() => trackLastCoedited(vscode.window.activeTextEditor)),
|
||||
);
|
||||
|
||||
const previewAnnotationHost = {
|
||||
inputsFor(env: unknown): AnnotationInputs | undefined {
|
||||
const envUri = (env as { currentDocument?: { toString(): string } } | undefined)?.currentDocument?.toString();
|
||||
const key = envUri && coeditingRegistry.list().includes(envUri) ? envUri : lastCoeditedUri;
|
||||
if (!key) return undefined;
|
||||
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === key);
|
||||
if (!doc) return undefined;
|
||||
const baseline = diffViewController.getBaseline(key);
|
||||
return {
|
||||
baselineText: baseline?.text,
|
||||
baselineReason: baseline?.reason,
|
||||
spans: attributionController.spansFor(doc),
|
||||
proposals: proposalController.listProposals(doc),
|
||||
enabled: vscode.workspace.getConfiguration("cowriting").get<boolean>("annotations", true),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Toggle: flips `cowriting.annotations` + refreshes the built-in preview
|
||||
// (VS Code re-invokes extendMarkdownIt's plugin on the next render — no
|
||||
// extension-side re-render call exists beyond the standard refresh command).
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand("cowriting.toggleAnnotations", async () => {
|
||||
const config = vscode.workspace.getConfiguration("cowriting");
|
||||
const current = config.get<boolean>("annotations", true);
|
||||
await config.update("annotations", !current, vscode.ConfigurationTarget.Global);
|
||||
await vscode.commands.executeCommand("markdown.preview.refresh");
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
threadController,
|
||||
attributionController,
|
||||
@@ -404,6 +463,8 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
|
||||
editorProposalController,
|
||||
coeditingRegistry,
|
||||
scmSurfaceController,
|
||||
previewAnnotationHost,
|
||||
extendMarkdownIt: (md: unknown) => cowritingMarkdownItPlugin(md as MarkdownIt, previewAnnotationHost),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user