feat(f8): isAuthorable + widened selectionRejection (file|untitled)

Spec §6.4: replace the scheme!=file / !isUnderRoot rejections with a single
!isAuthorable branch. isUnderRoot retained as a routing input. Per-condition
messaging (#24) kept for no-editor / empty-selection / non-{file,untitled}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ben Stull
2026-06-11 13:06:58 -07:00
parent 515acb4868
commit bc77cee0bd
2 changed files with 45 additions and 34 deletions
+19 -13
View File
@@ -20,6 +20,17 @@ export function isUnderRoot(fsPath: string, root: string): boolean {
return fsPath === root || fsPath.startsWith(root + path.sep);
}
/**
* Documents Cowriting can author on: a saved file OR an unsaved buffer (F8 §6.2).
* F8 widened membership from "saved file under the workspace folder" to any
* `file:`/`untitled:` doc — the SidecarRouter then routes where its artifact is
* stored (repo `.threads/` in-workspace, global storage otherwise). `isUnderRoot`
* (above) is retained as that ROUTING input, no longer an authoring gate.
*/
export function isAuthorable(scheme: string): boolean {
return scheme === "file" || scheme === "untitled";
}
export interface SelectionContext {
/** Is there an active text editor at all? */
hasEditor: boolean;
@@ -27,17 +38,15 @@ export interface SelectionContext {
selectionEmpty: boolean;
/** The active document's URI scheme (`file`, `untitled`, …). */
scheme: string;
/** The active document's filesystem path (empty if none). */
fsPath: string;
/** The workspace folder Cowriting is anchored to. */
root: string;
}
/**
* Why a selection can't be sent to Claude — or `null` if it can. Each condition
* gets its OWN message so the user learns the real reason instead of always
* being told to "select some text" (the reported bug: an out-of-workspace file
* was rejected with the no-selection message even though text was selected).
* Why a selection can't be sent to Claude — or `null` if it can. F8 widened the
* membership: any `file:` or `untitled:` document is authorable (in-workspace
* files persist to the repo `.threads/` sidecar, out-of-workspace/untitled to
* global storage — the router decides). Only a non-{file,untitled} scheme (a
* read-only `git:`/`output:` view), a missing editor, or an empty selection is
* refused — each with its OWN message (#24's per-condition messaging).
*/
export function selectionRejection(ctx: SelectionContext): string | null {
if (!ctx.hasEditor) {
@@ -46,11 +55,8 @@ export function selectionRejection(ctx: SelectionContext): string | null {
if (ctx.selectionEmpty) {
return "Cowriting: select some text to send to Claude first.";
}
if (ctx.scheme !== "file") {
return "Cowriting: save this document to a file first — Cowriting anchors edits to a saved workspace file.";
}
if (!isUnderRoot(ctx.fsPath, ctx.root)) {
return "Cowriting: this file is outside your workspace folder — open a file inside it to ask Claude to edit (Cowriting anchors edits and attribution to files in the workspace).";
if (!isAuthorable(ctx.scheme)) {
return "Cowriting: this kind of document can't be edited — Cowriting authors on a file or an untitled buffer, not a read-only view.";
}
return null;
}