fix(edit-selection): accurate per-condition warnings + path-boundary workspace check

cowriting.editSelection collapsed four distinct failures (no editor / no
selection / unsaved / outside the workspace folder) into one misleading
"select some text in a workspace document first" warning — so editing a
selection in a file OUTSIDE the EDH workspace root (e.g. a content-repo file
while the EDH opens sandbox/) was rejected as if no text were selected.

- New pure, vscode-free src/workspacePath.ts: isUnderRoot() (separator-bounded
  membership, fixing a latent startsWith() prefix collision where a sibling
  whose name prefixes the root — e.g. vscode-cowriting-plugin-content vs
  vscode-cowriting-plugin — falsely matched) + selectionRejection() (one
  message per condition). Unit-tested (8 cases), incl. the reported bug.
- Wire isUnderRoot into all five membership checks: extension.ts (editSelection
  guard + renderIfOpen) and the thread/attribution/proposal controllers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-11 12:12:18 -07:00
parent ce01ef81b5
commit 0bdee1c286
6 changed files with 133 additions and 12 deletions
+16 -9
View File
@@ -9,6 +9,7 @@ import { VersionGuard } from "./versionGuard";
import { BaselineStore } from "./baselineStore";
import { DiffViewController } from "./diffViewController";
import { TrackChangesPreviewController } from "./trackChangesPreview";
import { isUnderRoot, selectionRejection } from "./workspacePath";
const CHANNEL_NAME = "Cowriting (Cline SDK)";
@@ -187,22 +188,28 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
context.subscriptions.push(
vscode.commands.registerCommand("cowriting.editSelection", async () => {
const editor = vscode.window.activeTextEditor;
if (
!editor ||
editor.selection.isEmpty ||
editor.document.uri.scheme !== "file" ||
!editor.document.uri.fsPath.startsWith(root)
) {
void vscode.window.showWarningMessage("Cowriting: select some text in a workspace document first.");
// Each failure names its real reason (no editor / no selection / unsaved /
// outside the workspace folder) — not always "select some text" (#bug:
// an out-of-workspace file was rejected with the no-selection message).
const reason = selectionRejection({
hasEditor: !!editor,
selectionEmpty: editor?.selection.isEmpty ?? true,
scheme: editor?.document.uri.scheme ?? "",
fsPath: editor?.document.uri.fsPath ?? "",
root,
});
if (reason) {
void vscode.window.showWarningMessage(reason);
return;
}
if (!editor) return; // unreachable once reason is null, but narrows the type
const instruction = await vscode.window.showInputBox({
prompt: "What should Claude do with the selection?",
placeHolder: "e.g. tighten this paragraph",
});
if (!instruction) return;
if (editor.selection.isEmpty) {
void vscode.window.showWarningMessage("Cowriting: select some text in a workspace document first.");
void vscode.window.showWarningMessage("Cowriting: select some text to send to Claude first.");
return;
}
const document = editor.document;
@@ -262,7 +269,7 @@ export function activate(context: vscode.ExtensionContext): CowritingApi | undef
// Render threads + attributions for already-open editors, and on future opens.
const renderIfOpen = (doc: vscode.TextDocument) => {
if (doc.uri.scheme === "file" && doc.uri.fsPath.startsWith(root)) {
if (doc.uri.scheme === "file" && isUnderRoot(doc.uri.fsPath, root)) {
threadController.renderAll(doc);
attributionController.loadAll(doc);
proposalController.renderAll(doc);