15 KiB
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; F3–F5 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 F3–F5 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/sdkloaded via runtime dynamicimport(). 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
agentprovenance 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:
schemaVersionis present; threads, attributions (F3), and proposals (F4) all reference the sameanchorsprimitive, so later features add arrays without a format break. - INV-5 F2 ships no live
@cline/sdkturn 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;
FileSystemWatcherfor 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)· emitsonExternalChange(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 viavscode.comments.
6.5 Per–Product-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
humanmessage / flipstatus; persist. - PUC-3 (reload): on open,
load→ for each anchorresolveagainst current text → render at resolved range or as orphaned. - PUC-4 (re-anchor): while open,
onDidChangeTextDocumentshifts live ranges and (on save) refreshes fingerprints; on external change (FileSystemWatcher on the doc or.threads/) re-runresolve. 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 theThreadControllerAPI 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 F3–F5 | — | 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[]andproposals[](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/sdkturn, 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>.jsonartifact. - Epic #1; F2 (#4); POC #2 (session 0002); rfc-app#48/#46 (lineage).