Files
vscode-cowriting-plugin-con…/specs/coauthoring-inner-loop.md
T

15 KiB
Raw Blame History

status
status
graduated

Solution Design: Coauthoring Inner-Loop Git-Native Model (region-anchored threads)

Author(s) Ben Stull (with Claude)
Reviewers / approvers Ben Stull
Status draft
Version v0.1.0
Source artifacts Epic benstull/vscode-cowriting-plugin#1 · First consumer: Feature #4 (F2) · POC: #2 (shipped, session 0002) · Lineage: ben.stull/rfc-app#48 (inner-loop rung), #46 (Gitea substrate rung) — not re-read this session (cross-host)

Change log

Date Version Change By
2026-06-10 v0.1.0 Initial draft — brainstorming session 0003 Ben Stull + Claude

1. Business Context

1.1 Executive Summary

The VS Code coauthoring plugin (Epic #1) needs a shared, git-native data model underneath every coauthoring capability: provenance, region anchors, threads, attributed spans, and proposed diffs. This spec designs that model and fully specifies its first consumer — region-anchored discussion threads (F2) — while defining the shared primitives (anchors, provenance envelope) so that F3 (attribution), F4 (propose/accept), and F5 (cross-rung persistence) land without a format change. It is intentionally focused: threads are designed in depth; F3F5 are named, accommodated extension points, not built here.

1.2 Background

The forge (Gitea / rfc-app) is the outer loop — review, deliberation, durable record. The inner loop, where writing happens keystroke by keystroke, has no home. Epic #1 delivers it as a standalone VS Code extension on @cline/sdk (Approach A, validated by the POC #2). Epic #1 explicitly earmarked the full Solution Design — the git-native format, anchoring, attribution, where Claude runs — for this follow-on brainstorming session.

1.3 Business Actors / Roles

  • Coauthor (human) — a writer/engineer authoring prose or specs in VS Code.
  • Coauthor (machine) — Claude, via @cline/sdk. Not an active participant in F2 threads (see §1.7); the model reserves its authorship for F3/F4.

1.4 Problem Statement

There is nowhere to discuss a specific region of a document durably. Highlighted context evaporates on the next keystroke or reload; nothing anchors discussion to a span that survives editing, and nothing persists without a server.

1.5 Pain Points

  • No region-scoped, persistent discussion in the editor.
  • No anchor that survives edits, reload, or an external change (a git pull).
  • No git-native (serverless, durable, portable) home for coauthoring metadata.

1.6 Targeted Business Outcomes

A durable, region-anchored thread surface that reloads across sessions, survives edits, and is stored as a diffable git artifact — and a model skeleton that carries F3F5 forward without rework.

1.7 Scope (business)

In scope: the shared git-native model envelope (anchors + provenance); region-anchored threads (create, reply, resolve) via the native Comments API; hybrid anchoring; per-document sidecar persistence with reload. Out of scope (deferred, not forgotten): F3 live attribution; F4 propose/accept diff flow; F5 cross-rung round-trip to the Gitea substrate (#46); live @cline/sdk agent turns + provider/auth (the POC deliberately used only pure, key-free SDK calls); multi-file orchestration; file rename/move tracking; any server.

1.8 Assumptions · Constraints · Dependencies

  • Built on the POC skeleton (#2): CJS extension, esbuild, @cline/sdk loaded via runtime dynamic import(). Threads need no SDK turn → no provider/auth in F2.
  • VS Code Comments API (vscode.comments) supplies the thread UI natively.
  • Depends on: Epic #1; first consumer F2 (#4). Relates to: F5, rfc-app#46.

1.9 Business Use Cases

  • BUC-1 A coauthor attaches a discussion to a region of a document and returns to it later — across edits and across sessions — and it is still on the right span (or clearly flagged if it could not be re-anchored).

2. Solution Proposal

Build software: a VS Code extension feature on the POC skeleton. Persist a versioned coauthoring artifact per source document as a git-committed sidecar (.threads/<repo-relative-path>.json). Render threads through the native Comments API. Maintain anchors with a hybrid strategy — exact live offset tracking while a document is open, plus a durable content fingerprint as the source of truth for re-resolution on load/external change, with an explicit orphaned state when a confident match is not found. The artifact's anchors and provenance primitives are shared: F3/F4/F5 reuse them.


3. Product Personas

  • PP-1 Inner-loop coauthor — the human writer/engineer (embodies the Coauthor (human) business role). The only active F2 persona.
  • PP-2 Machine coauthor (Claude) — reserved in the model (an agent provenance author) but not active in F2; embodies Coauthor (machine).

4. Product Use Cases

  • PUC-1 Create a thread on the selected text range (Comments gutter).
  • PUC-2 Reply within a thread; mark a thread resolved/reopened.
  • PUC-3 Reopen the workspace → threads reload at their anchors.
  • PUC-4 Edit the document (or pull an external change) → threads re-anchor; un-resolvable threads are shown as orphaned, never silently moved.

5. UX Layout

No custom UI. Threads render in the native VS Code Comments gutter/peek. A contributed command/gutter action creates a thread on the current selection. Orphaned threads surface with a distinct label (and, where the API allows, a "detached" context value) at their last-known lineHint. UX is described in text; no bespoke webview to wireframe.


6. Technical Design

6.1 Invariants

  • INV-1 A thread is never silently moved to a wrong span. If its anchor cannot be confidently re-resolved, it becomes orphaned (visible, flagged).
  • INV-2 Persistence is git-native and serverless: plain, diffable, human-resolvable JSON committed in the repo. No server, no external store.
  • INV-3 The fingerprint is the source of truth for an anchor's location; live editor offsets are a within-session optimization, not the persisted truth.
  • INV-4 The artifact is forward-compatible: schemaVersion is present; threads, attributions (F3), and proposals (F4) all reference the same anchors primitive, so later features add arrays without a format break.
  • INV-5 F2 ships no live @cline/sdk turn and requires no credentials.

6.2 High-level architecture

Three vscode-free units (unit-testable, mirroring the POC's vscode-free cline.ts) and one thin editor-facing controller:

  • Artifact schema — the versioned envelope (§6.3).
  • CoauthorStore — load/save sidecars; FileSystemWatcher for external change.
  • Anchorer — fingerprint build, hybrid resolve/refresh, orphan detection.
  • ThreadController — wires Store + Anchorer to vscode.comments (create-on-selection, reply, resolve, render, orphaned state).
selection ─▶ ThreadController ─▶ Anchorer (build fingerprint) ─▶ CoauthorStore (persist)
document change ─▶ Anchorer (shift live ranges) ─▶ on save: refresh fingerprint
load / external change ─▶ CoauthorStore (read) ─▶ Anchorer (resolve|orphan) ─▶ ThreadController (render)

6.3 Data model & ownership

One sidecar per document, owned by the extension, committed to git:

// .threads/docs/spec.md.json
{
  "schemaVersion": 1,
  "document": { "path": "docs/spec.md" },          // repo-relative; the key
  "anchors": {                                       // SHARED primitive (INV-4)
    "a1": { "fingerprint": {
      "text": "the exact anchored text",
      "before": "<= ~3 lines / 120 chars leading context",
      "after":  "<= ~3 lines / 120 chars trailing context",
      "lineHint": 42                                 // tie-breaker, not truth (INV-3)
    } }
  },
  "threads": [
    { "id": "t1", "anchorId": "a1", "status": "open",        // open | resolved
      "messages": [
        { "id": "m1", "author": <Provenance>, "body": "markdown", "createdAt": "ISO-8601" }
      ] }
  ],
  "attributions": [],   // F3 extension point — reuses anchors[]; not implemented in F2
  "proposals": []       // F4 extension point — reuses anchors[] + provenance; not in F2
}

Provenance primitive (the reusable author field):

{ "kind": "human" | "agent",
  "id": "ben",                                       // human: git user; agent: model/agent id
  "agent": { "sdk": "@cline/sdk", "model": "…", "sessionId": "…" }  // only when kind = agent
}
  • Ownership: the extension is the sole writer of .threads/. JSON is pretty-printed with stable key ordering to minimize merge conflicts.
  • Ids: stable, generated at create time (e.g. crypto.randomUUID() / timestamp+counter); Date.now() is available in the extension runtime.

6.4 Interfaces & contracts

  • CoauthorStore: load(docPath): Artifact | null · save(docPath, Artifact) · emits onExternalChange(docPath) (sidecar changed on disk).
  • Anchorer: buildFingerprint(docText, range): Fingerprint · resolve(docText, Fingerprint): Range | "orphaned" · shift(ranges, change): ranges (live offset maintenance).
  • ThreadController: createThreadOnSelection() · reply(threadId, body) · setStatus(threadId, status) · renderAll(docPath) — all via vscode.comments.

6.5 PerProduct-Use-Case design

  • PUC-1 (create): capture selection → buildFingerprint → store anchor + thread → render. Lazy-create the sidecar on the first thread for a document.
  • PUC-2 (reply/resolve): append a human message / flip status; persist.
  • PUC-3 (reload): on open, load → for each anchor resolve against current text → render at resolved range or as orphaned.
  • PUC-4 (re-anchor): while open, onDidChangeTextDocument shifts live ranges and (on save) refreshes fingerprints; on external change (FileSystemWatcher on the doc or .threads/) re-run resolve. Resolution ladder: exact-unique → context-disambiguated (lineHint breaks ties) → orphaned.

6.6 Non-functional requirements & cross-cutting concerns

Resolution is O(document) text search per anchor on load/change — fine for human-scale documents; skip non-text/oversized files. No secrets (INV-5). Sidecar diffs must stay readable (stable formatting). Concurrency: single editor writer.

6.7 Key decisions & alternatives considered

Decision Chosen Alternatives rejected
Persistence substrate .threads/ committed sidecar, per document git notes (bind to committed objects, weak tooling, no default push); commit trailers (only fit F4 diffs)
Anchoring Hybrid: live offsets + durable fingerprint + orphan fallback offsets-only (stale on external edit/reload); fingerprint-only (no exact in-session tracking)
Model scope Threads-deep + model-wide skeleton (shared anchors/provenance) threads-only (reworked at F3/F4); full inner-loop now (large, speculative)
Agent in F2 Human-only; agent author reserved live @cline/sdk turn in F2 (pulls provider/auth + "where Claude runs" into F2)

6.8 Testing strategy

  • Unit (vitest, vscode-free): Anchorer — fingerprint build; resolution (exact / context-disambiguated / orphaned); live offset shifting. CoauthorStore — serialize → reload round-trip; stable formatting.
  • Integration/E2E (@vscode/test-electron): create-on-selection → reply → persist → reload → re-anchor in the Extension Development Host. Where the harness cannot drive native Comments UI directly, fall back to driving the ThreadController API and asserting Comments-controller state. (No Playwright — Comments API is native, not a webview.) E2E are first-class plan tasks.

6.9 Failure modes, rollback & flags

Orphaned threads (INV-1) are the primary failure mode — handled, recoverable (auto-re-anchor if the text reappears). Sidecar merge conflicts → plain JSON, human-resolvable. No feature flag needed (additive, off by absence of .threads/).


7. Delivery Plan

7.1 Approach / strategy

Land the shared envelope and the threads consumer together (F2 = #4), as a just-in-time plan in a follow-on planning-and-executing session. This spec is the reviewed input to that plan.

7.2 Slicing plan

  • SLICE-1 Artifact schema + CoauthorStore (load/save, stable formatting, round-trip tests).
  • SLICE-2 Anchorer (fingerprint build + resolution ladder + orphan; live offset shift) with unit tests.
  • SLICE-3 ThreadController wiring to vscode.comments (create/reply/resolve, render).
  • SLICE-4 Reload + external-change re-anchoring (FileSystemWatcher) and the orphaned-thread surface.
  • SLICE-5 E2E (@vscode/test-electron) across create→persist→reload→re-anchor.

UI surface ⇒ E2E browser/host tests are first-class plan tasks (handbook §4 / solution-design §6.8), not a follow-up.

7.3 Rollout / launch plan

Non-shippable still (no marketplace publish). "Done" = F2 acceptance met, unit + host E2E green. No deploy stage (a VS Code extension, not a webapp — §9 has no applicable stage).

7.4 Risks & mitigations

Risk Mitigation
Fingerprint mis-resolves on duplicated/heavily-edited text context + lineHint disambiguation; orphan rather than guess (INV-1)
Native Comments API hard to E2E fall back to ThreadController API assertions
Format churn when F3/F4 land shared anchors/provenance + schemaVersion (INV-4)

8. Traceability matrix

Requirement Use case Design Slice
Region-anchored threads BUC-1, PUC-1/2 §6.3, §6.5 SLICE-1/3
Survives edits / reload / external change PUC-3/4 §6.5 (hybrid), INV-1/3 SLICE-2/4
Git-native persistence BUC-1 §6.3, INV-2 SLICE-1
Forward-compat for F3F5 INV-4, §6.3 SLICE-1

9. Open Questions & Decisions log

  • RESOLVED (this session): substrate = .threads/ sidecar; anchoring = hybrid; model scope = threads-deep + skeleton; agent participation = none in F2.
  • OPEN → F5: the cross-rung persistence format for round-trip to the Gitea substrate (#46) may extend or re-home the sidecar; F2's format is the inner-loop shape, not yet the cross-rung contract.
  • OPEN → F3/F4: exact shapes of attributions[] and proposals[] (reserved as extension points here).
  • OPEN → later: file rename/move tracking (path is the key in F2).
  • DEFERRED: where Claude runs + provider/auth (arrives with the first live @cline/sdk turn, F3/F4).

10. Glossary & References

  • Anchor — a fingerprinted reference to a document span, re-resolvable across edits. Fingerprint — anchored text + bounded context + lineHint. Orphaned — a thread whose anchor could not be confidently re-resolved. Sidecar — the per-document .threads/<path>.json artifact.
  • Epic #1; F2 (#4); POC #2 (session 0002); rfc-app#48/#46 (lineage).