feat(f11): SLICE-1 — Pin baseline toolbar button + reachability (#43)
Homes the orphaned cowriting.pinDiffBaseline command and gives the writer a
reachable Pin control in the preview toolbar. Per spec §7.2 SLICE-1
(docs/superpowers/specs/2026-06-12-f11-preview-toolbar-interaction-surface.md).
- trackChangesPreview: extract onDidReceiveMessage into handleWebviewMessage;
add the F11 `pinBaseline` intent → DiffViewController.pin(previewedDoc) (the
bound doc, not activeTextEditor — §6.7); ToolbarMsg union; receiveMessage test
seam exercising the real message→seam wiring (INV-35).
- webview: ⌖ Pin baseline button in #cw-header posting { type: "pinBaseline" };
theme-aware toolbar-button CSS (light/dark/high-contrast, disabled state).
- package.json: unhide pinDiffBaseline — commandPalette `when` false →
editorLangId == markdown (resolves the #34 orphan from the command side).
- host E2E (test/e2e/suite/f11Toolbar.test.ts): pinBaseline message clears the
change-marks + advances the baseline to `pinned`; palette `when` is reachable.
Also archives the F11 implementation plan to docs/superpowers/plans/.
197 unit + 47 host E2E green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import * as assert from "assert";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import * as vscode from "vscode";
|
||||
import type { CowritingApi } from "../../../src/extension";
|
||||
|
||||
const WS = process.env.E2E_WORKSPACE!;
|
||||
const settle = () => new Promise((r) => setTimeout(r, 400));
|
||||
|
||||
async function getApi(): Promise<CowritingApi> {
|
||||
const ext = vscode.extensions.getExtension("benstull.vscode-cowriting-plugin")!;
|
||||
const api = (await ext.activate()) as CowritingApi;
|
||||
assert.ok(api?.trackChangesPreviewController && api?.diffViewController, "exports preview + diffView");
|
||||
return api;
|
||||
}
|
||||
|
||||
/** Create + open a fresh markdown doc under WS, returning the doc + its uri key. */
|
||||
async function freshDoc(rel: string, body: string): Promise<{ doc: vscode.TextDocument; key: string }> {
|
||||
const abs = path.join(WS, rel);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, body, "utf8");
|
||||
const uri = vscode.Uri.file(abs);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
await settle();
|
||||
return { doc, key: uri.toString() };
|
||||
}
|
||||
|
||||
// F11 host E2E (no LLM): the preview toolbar is the primary interaction surface.
|
||||
// The webview posts intent messages; the host routes them through the existing
|
||||
// F4/F6/F3 seams (INV-35). The webview DOM (real button clicks) is sealed and
|
||||
// manual-smoke only; here we simulate the inbound messages via `receiveMessage`.
|
||||
suite("F11 preview toolbar (host E2E — message → seam wiring, no LLM)", () => {
|
||||
// SLICE-1: the Pin baseline button.
|
||||
test("pinBaseline message pins the PREVIEWED doc → marks clear, baseline reason is pinned (PUC-5, INV-35)", async () => {
|
||||
const { doc, key } = await freshDoc("docs/f11pin.md", "# F11 pin\n\nA baseline paragraph that will diverge.\n");
|
||||
const api = await getApi();
|
||||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.isOpen(key), true, "panel open");
|
||||
|
||||
// Diverge from the opened baseline so the preview carries a real change-mark.
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.insert(doc.uri, doc.positionAt(doc.getText().length), "\n\nA freshly typed paragraph that diverges.\n");
|
||||
assert.ok(await vscode.workspace.applyEdit(edit), "operator edit applied");
|
||||
await settle();
|
||||
const marked = (api.trackChangesPreviewController.getLastModel(key) ?? []).some((o) => o.kind !== "unchanged");
|
||||
assert.ok(marked, "the typed paragraph shows as a change before pinning");
|
||||
|
||||
// Simulate the webview's Pin baseline button posting its intent.
|
||||
api.trackChangesPreviewController.receiveMessage(key, { type: "pinBaseline" });
|
||||
await settle();
|
||||
|
||||
const model = api.trackChangesPreviewController.getLastModel(key) ?? [];
|
||||
assert.ok(model.length > 0 && model.every((o) => o.kind === "unchanged"), "after pin, every block is unchanged");
|
||||
assert.strictEqual(api.diffViewController.getBaseline(key)?.reason, "pinned", "baseline reason advanced to pinned");
|
||||
});
|
||||
|
||||
// SLICE-1 reachability: the orphaned pin command gets a real palette `when`.
|
||||
test("pinDiffBaseline is reachable from the command palette (when: editorLangId == markdown)", async () => {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../../package.json"), "utf8"));
|
||||
const entry = (pkg.contributes.menus.commandPalette as Array<{ command: string; when?: string }>).find(
|
||||
(m) => m.command === "cowriting.pinDiffBaseline",
|
||||
);
|
||||
assert.ok(entry, "pinDiffBaseline has a commandPalette entry");
|
||||
assert.notStrictEqual(entry!.when, "false", "it is no longer hidden (when:false)");
|
||||
assert.match(entry!.when ?? "", /editorLangId == markdown/, "guarded on markdown");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user