tandem-editor 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tandem",
3
- "version": "0.6.2",
3
+ "version": "0.8.0",
4
4
  "description": "Edit and iterate on documents with Claude — no copy-paste, real-time push via plugin monitor",
5
5
  "author": {
6
6
  "name": "Tandem"
@@ -23,5 +23,12 @@
23
23
  "TANDEM_URL": "http://localhost:3479"
24
24
  }
25
25
  }
26
- }
26
+ },
27
+ "monitors": [
28
+ {
29
+ "name": "tandem-events",
30
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/monitor/index.js",
31
+ "description": "Tandem real-time document events (annotations, chat, selections)"
32
+ }
33
+ ]
27
34
  }
package/CHANGELOG.md CHANGED
@@ -7,362 +7,76 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## \[Unreleased]
9
9
 
10
- ## \[0.7.0] - 2026-04-20
10
+ ## \[0.8.0] - 2026-04-26
11
11
 
12
12
  ### Added
13
13
 
14
- - **Auth token storage** — on first boot the server generates a 32-byte base64url token and persists it to the platform data directory (`%LOCALAPPDATA%\tandem\Data\auth-token` on Windows, `~/.local/share/tandem/auth-token` on Linux, `~/Library/Application Support/tandem/auth-token` on macOS). Subsequent boots reuse the token. First-boot race is protected by `O_EXCL` file creation. Tauri mode receives the token via `TANDEM_AUTH_TOKEN` env before sidecar spawn and never regenerates.
15
- - **Auth middleware** — non-loopback MCP and API requests require `Authorization: Bearer <token>`. Loopback connections (`127.0.0.1`, `::1`, `::ffff:127.0.0.1`) remain exempt, preserving zero-config Claude Code usage. Token comparison uses SHA-256 on both sides before `crypto.timingSafeEqual` to eliminate the length oracle. Rate-limiting (5 attempts / 60 s) keyed by IPv4 address or IPv6 `/64` prefix with LRU eviction; Authorization headers are redacted from all rejection logs.
16
- - **`TANDEM_BIND_HOST` bind-mode selection** MCP HTTP server binds to `127.0.0.1` by default; set `TANDEM_BIND_HOST=0.0.0.0` (or a specific LAN IP) to expose Tandem on the local network. Hocuspocus WebSocket always stays loopback. Non-loopback bind without a token file exits 1 with guidance; `TANDEM_ALLOW_UNAUTHENTICATED_LAN=1` is the escape hatch. Multi-homed machines require `TANDEM_LAN_IP` to be set explicitly.
17
- - **`tandem rotate-token`** new CLI subcommand that atomically regenerates the auth token, notifies the running server to open a 60-second grace window for in-flight sessions, and re-runs `tandem setup` across all detected MCP config files. Prints old and new token fingerprints (first 8 hex chars of SHA-256). Refuses rotation when `TANDEM_AUTH_TOKEN` is set in the environment (Tauri mode).
18
- - **Token forwarding in stdio bridge, monitor, and channel sidecars** — `tandem mcp-stdio`, `tandem monitor`, and the channel sidecar now forward `TANDEM_AUTH_TOKEN` as `Authorization: Bearer` on upstream HTTP calls. Malformed tokens (empty, `Bearer`-prefixed, < 32 chars, non-URL-safe) exit 1 with a specific message.
19
- - **OAuth protected-resource metadata** — `/.well-known/oauth-protected-resource/mcp` now declares `bearer_methods_supported: ["header"]` and a literal-`localhost` `resource` field per RFC 9728.
20
- - **`/health` session-presence guard** — `hasSession` is omitted from `/health` responses on non-loopback requests, preventing session-presence leakage on LAN binds.
21
-
22
- ### Security
23
-
24
- - Loopback detection keys off `req.socket.remoteAddress` exclusively — `Host` header is never trusted for the loopback bypass decision.
25
- - Fail-closed on LAN bind: `TANDEM_BIND_HOST=0.0.0.0` without a token file exits 1; the server never auto-generates a token and proceeds silently.
26
- - `crypto.randomBytes` failure or non-writable data directory → server exits 1; no silent fallback.
27
-
28
- ## \[0.6.4] - 2026-04-20
29
-
30
- ### Fixed
31
-
32
- - **Flaky layout-switch E2E test (#281)** — `toHaveCount` assertions on resize-handle locators lacked explicit timeouts, causing intermittent CI failures under load when React re-renders were slower than the default 5 s expectation. All 8 assertions now carry `{ timeout: 10_000 }`, and the missing `right-panel-resize-handle` absence assertion in the tabbed-layout block has been added.
33
- - **Silent crashes in CLI entry points (#336)** — `src/cli/index.ts` and `src/channel/index.ts` lacked `process.once("uncaughtException")` / `process.once("unhandledRejection")` handlers. Uncaught throws in `tandem start`, `tandem setup`, and the Tauri channel sidecar exited silently with code 0, surfacing as "tools never appear" with no diagnostics. Both entries now write a labelled message to stderr and exit 1. The `uncaughtException` handler uses `err: unknown` with an `instanceof Error` guard so non-Error throws (strings, plain objects) produce the actual thrown value rather than `"undefined"`.
34
-
35
- ## \[0.6.3] - 2026-04-19
14
+ - **Semantic token lint enforcement (#356)** — `npm run check:tokens` scans `src/client/` for raw hex and non-neutral `rgba()` violations. Runs on pre-commit via lint-staged, blocking merges that introduce unsanctioned color literals.
15
+ - **`--tandem-suggestion-*` token family (#340)** violet semantic tokens for replacement/suggestion annotations (`--tandem-suggestion`, `-fg-strong`, `-bg`, `-border`), visually distinct from the indigo accent family.
16
+ - **Annotation drop count surfacing (#351)** `normalizeAnnotation` now returns drop counts in snapshot metadata so callers can detect lossy session migrations.
17
+ - **Plugin monitor declaration (#376)** `plugin.json` now declares the `monitor` entry, closing the event push gap where Claude Code plugin installs didn't receive real-time notifications.
18
+ - **Persistent annotation undo (#415)** — undo state survives panel switches and scrolling; persists until page reload instead of clearing on the next render cycle.
19
+ - **Diagnostic position tests (#377)** — regression test suite for flat-offset resolution across headings, inline marks, nested lists, and blockquotes.
36
20
 
37
21
  ### Fixed
38
22
 
39
- - **Annotation GC race on startup (#334)** — `cleanupOrphanedAnnotationFiles` previously ran as a `.then()` chain during boot, racing the boot-path doc opens. On upgrade paths where `sample/welcome.md` or `CHANGELOG.md` hadn't been opened in 30+ days, the GC could unlink the annotation file between read intent and the actual read, silently returning an empty doc. Now `await`-ed before all boot-path opens.
40
- - **Settings Popover extends out of view (#306)** — centered the popover in the viewport with `transform: translate(-50%, -50%)` and added `maxHeight: calc(100vh - 32px)` + `overflowY: auto` so it is always fully visible and internally scrollable on short screens.
41
- - **Dark-mode&#x20;****\*-bg****&#x20;tokens inconsistent (#307)** — `--tandem-success-bg` and `--tandem-warning-bg` in dark mode were hand-coded hex while `--tandem-error-bg` used `color-mix`. All three now use `color-mix(in srgb, var(--tandem-<semantic>) 15%, var(--tandem-surface))` for consistency with light-mode behavior.
42
- - **stdio bridge silent-failure paths (#336 partial)** — three paths in `src/cli/mcp-stdio.ts` (preflight exit, `http.onclose`, `http.start()` TOCTOU) previously closed stdio without writing a JSON-RPC error, producing "tools never appear in Cowork" with no diagnostics. All three now synthesize `-32000` for any in-flight request ID before exit. Remaining #336 items (channel-shim tests, Windows npx smoke, nits) carry to v0.7.0.
23
+ - **Three compounding coordinate system bugs (#260)** — inline markup (bold, italic, code) inflated character offsets; nested block structures (list items, blockquotes) lacked separators; and list item text extraction omitted `\n` between siblings. All three bugs compounded silently a bold word inside a nested list could shift annotation placement by 10+ characters. Fixed in `getElementText()` and `extractText()` with full encapsulation of the position module behind `resolveToElement()`.
24
+ - **Flash animation alpha wash (#308)** — accept/dismiss flash used opaque background that hid annotation text; now uses `color-mix` with translucent blend against the surface.
25
+ - **Dark mode scrollbar styling (#369)** — scrollbars in the editor and side panel now respect the active theme.
26
+ - **Biome format check (#424)** — expanded a single-expression `useEffect` arrow that Biome 2.x reformatted differently than the original.
43
27
 
44
28
  ### Changed
45
29
 
46
- - **Annotation module internals** — extracted `mergeMap<T>` helper (#324), promoted `UPLOAD_PREFIX` to shared constants (#327), centralized app-data dir resolution in `platform.ts` (#328), extracted `ReplyAuthorSchema`, trimmed module headers, and dropped unused `docContexts` map (#332). No user-facing behavior changes.
47
- - **Annotation serialization upgrades legacy&#x20;****type****&#x20;values on write** (#329) — records with non-canonical `type` (`"suggestion"` / `"question"` / anything outside `highlight` / `comment` / `flag`) are now routed through `sanitizeAnnotation` during snapshot serialization, which rewrites them to `"comment"`. **One-way lossy migration:** users with legacy-type annotations will see `type` flip to `"comment"` on the next durable write for that document — the original distinction between `suggestion` and `question` is not recoverable.
48
- - **migrateToV1****&#x20;reports drop counts** (#330) — `migrateToV1(raw)` now returns `{ doc, droppedAnnotations, droppedReplies }` so future production callers can surface lossy upgrades to users rather than silently discarding malformed records. No production caller exists yet; a follow-up will wire drop counts to `npm run doctor` or a toast when the first caller lands.
49
-
50
- ### Internal
51
-
52
- - Test coverage: `pickWinner`, `SerializedRelPos` edges, UNC paths, upload-path edges (#331); `wireAnnotationStore` perf baseline at 500/1000/5000 annotations (#335).
53
- - Test sweep: stale hex color refs cleaned up post-PR #303 (#309).
54
- - Accessibility: forced-colors fallback audit on PR #303 annotation surfaces (#311).
55
- - CI: typecheck / lint / tests now gate on all PRs regardless of base branch; dropped unused `baseUrl` from `tsconfig.server.json` (#310).
56
-
57
- ## \[0.6.2] - 2026-04-16
30
+ - **User annotations simplified (#381)** user-authored annotations show Edit and Remove only; Accept/Reject reserved for Claude and imported annotations. Reduces cognitive load user notes are notes, not proposals.
31
+ - **Toolbar streamlined (#382)**removed Replace and @Claude checkboxes from the annotation creation toolbar. These features remain available via MCP tools (`tandem_suggest`, `tandem_comment` with `directedAt`).
58
32
 
59
- ### Fixed
60
-
61
- - **Plugin stdio entries crash-loop on Windows** — `tandem-editor@0.6.1`'s published `package.json` shipped `"workspaces": ["packages/*"]`, a dev-only field for the vestigial `packages/tandem-doc/` alias stub. On Windows with Node 24 + npm 11, the presence of `workspaces` in an installed consumer package caused `npx -y tandem-editor …` to fail with `ERR_UNSUPPORTED_ESM_URL_SCHEME` (the bin path was handed to the ESM loader as a raw `c:\…` string instead of a `file://` URL). Claude Desktop's plugin loader spawns the stdio entries via `npx -y`, so both `tandem` and `tandem-channel` crash-exited before any user code ran, manifesting in Cowork sessions as entries that flapped "connecting → gone" and never surfaced `tandem_*` tools. Removed the unused `packages/tandem-doc/` directory and the `workspaces` field; direct `node dist/cli/index.js` invocation was never affected, so the Tauri desktop app's bundled sidecar was fine and only the npm-tarball/`npx` path needed the fix.
62
-
63
- ## \[0.6.1] - 2026-04-15
33
+ ### Refactored
64
34
 
65
- ### Fixed
66
-
67
- - **Tauri desktop app fails to start** `src/cli/skill-content.ts` reads `skills/tandem/SKILL.md` at module-init via `readFileSync`, and `src/server/mcp/api-routes.ts` transitively imports it, so the bundled sidecar server crashed on startup with `ENOENT: skills/tandem/SKILL.md`. The `skills/` directory was never declared in `src-tauri/tauri.conf.json` bundle resources (latent since the v0.5.1 refactor to read SKILL.md at runtime). Add `"../skills/": "skills/"` so the sidecar's relative path resolution matches the npm-install layout. npm-published 0.6.0 was unaffected because `skills/` ships in the npm tarball via `package.json` `files`.
68
-
69
- ## \[0.6.0] - 2026-04-15
70
-
71
- ### Added
72
-
73
- - **Plugin bridge to Cowork** — new `tandem mcp-stdio` subcommand is a stdio ↔ HTTP JSON-RPC proxy so Claude Desktop's plugin loader surfaces the full `tandem_*` tool surface into Cowork VM sessions. Verified empirically that plugin-loaded HTTP MCP entries don't bridge to Cowork but plugin-loaded stdio entries do. The plugin's `.claude-plugin/plugin.json` now declares stdio entries for both `tandem` (proxy) and `tandem-channel` (existing shim re-exposed as `tandem channel` subcommand), invoked via `npx -y tandem-editor …` so the plugin cache never needs dev dependencies. Both subcommands share a strict preflight in `src/cli/preflight.ts` that fails fast with a single clear message when the Tandem server isn't running on `localhost:3479`.
74
- - `tandem channel` CLI subcommand — npm-delivered entry for the plugin's `tandem-channel` MCP server; shares runtime with the standalone `src/channel/index.ts` binary via the new `src/channel/run.ts` extraction.
75
- - **Settings expansion** — Settings popover grows from layout/dwell/authorship into a fuller preferences surface:
76
- - Ctrl+, / Cmd+, hotkey (AZERTY/QWERTZ/IME-safe, survives non-QWERTY layouts)
77
- - Display Name field, synced live with the StatusBar via a shared `useUserName` hook
78
- - Reduce Motion toggle (JS-gates all autoscroll paths; defaults to `prefers-reduced-motion`)
79
- - Text Size S/M/L for editor reading density (browser zoom remains the WCAG 1.4.4 path)
80
- - Theme Light/Dark/System (CSS custom-property token system on `<html data-theme>` with `forced-colors` support for Windows High Contrast)
81
- - Tier 0 accessibility prerequisites on SettingsPopover: `role="dialog"` + `aria-modal`, focus trap, Escape-to-close, pointerdown outside-dismiss, radiogroup semantics, 24×24 hit targets, focus-return on close
82
-
83
- ### Changed
84
-
85
- - Repo's project-level `.mcp.json` renamed to `.mcp.json.example` and gitignored so it no longer ships inside plugin installs. The plugin's own `.claude-plugin/plugin.json` is authoritative for MCP wiring. Developers who clone the repo should copy the example to `.mcp.json` (gitignored) if they want Claude Code to auto-connect locally.
86
- - Settings heading renamed "Layout Settings" → "Settings"
87
- - Settings popover hardcoded hex values swapped to CSS tokens (remaining components will migrate in a follow-up)
88
- - `shutdownForTests` renamed to `shutdownMonitor` (test-only alias kept for backward compatibility)
89
- - `refreshMode` IIFE now wrapped in an outer `.catch` to keep future synchronous throws off the hot path
90
-
91
- ### Fixed
92
-
93
- - **Monitor preserves last-known&#x20;****documentId** — doc-less events (e.g. `chat:message`) no longer blank out the tracked document, so the shutdown awareness clear always targets a valid document.
94
- - **Monitor exits 1 on shutdown awareness failure** — if the final `clearAwareness` POST fails during SIGINT/SIGTERM, the monitor exits with a non-zero status rather than silently succeeding.
95
- - **/api/setup****&#x20;returns accurate status codes** — 207 on partial failure (some targets configured, some failed) and 500 on total failure, instead of always returning 200.
96
- - **Checkpoint after stdout write** — `lastEventId` is only advanced after `process.stdout.write` returns, so EPIPE on a closed pipe no longer silently skips an event on reconnect.
97
- - **Async EPIPE surfaces as exit(1)** — `process.stdout.on('error')` listener now catches asynchronous EPIPE (plugin host closes pipe mid-stream); monitor exits 1 instead of silently advancing `lastEventId` past lost events.
98
- - **Defensive exit on monitor fallthrough** — the retry loop exits 1 if it ever terminates without hitting the explicit exhaustion path.
99
-
100
- ## \[0.5.1] - 2026-04-13
101
-
102
- ### Added
103
-
104
- - **Claude Code plugin support** — monitor-based event push (`src/monitor/index.ts`) gives real-time notifications without polling or the channel shim. Install via `claude plugin marketplace add bloknayrb/tandem`.
105
- - **--with-channel-shim****&#x20;opt-in** — `tandem setup --with-channel-shim` writes the legacy `tandem-channel` MCP entry for setups that can't install the plugin.
106
-
107
- ### Changed
35
+ - **Codebase audit remediation (Phases 1–4)** — 8 god-files decomposed across 4 phases:
36
+ - **Phase 1** (PRs #384–#389): wire-protocol types to `shared/`, token-store extraction, `awareness.ts` semantic tokens, Editor CSS extraction, tsconfig tightening, dead Tauri JS deps removed.
37
+ - **Phase 2** (PRs #391, #392): `api-routes.ts` split into per-route handler modules; `file-opener.ts` decomposed into phased helpers with lifecycle tests.
38
+ - **Phase 3** (PR #398, tests #399/408): event queue observer split — monolithic `queue.ts` broken into focused observer modules per Y.Map.
39
+ - **Phase 4** (PRs #409–#413): `App.tsx` hooks extracted, `SidePanel.tsx` decomposed, `Toolbar.tsx`/`SettingsPopover.tsx` split, `AnnotationCard.tsx` broken into 3 sub-components.
40
+ - **Zero-arg handler factory simplification (#393)** — reduced boilerplate in MCP handler registrations.
41
+ - **Shared annotation test fixtures (#344)** — extracted reusable test helpers for annotation creation.
42
+ - **HTTP API silent failure surfacing (#396)** — API routes that swallowed errors now return proper status codes.
108
43
 
109
- - `tandem setup` no longer writes the `tandem-channel` MCP entry by default — running the plugin and the shim simultaneously produces duplicate event notifications. The shim is now opt-in only via `--with-channel-shim`.
110
-
111
- ### Fixed
112
-
113
- - **Windows update failure** — sidecar is now killed before the NSIS installer runs, preventing "Error opening file for writing: node-sidecar.exe" during updates
114
- - **Mode check fails closed** — `/api/mode` errors now fall back to "solo" at startup (privacy signal, not a permissive default) while the hot-path background refresh keeps the last known good value to avoid mid-session suppression.
115
- - **Retry counter resets on stable uptime** — retry count now resets only after 60s of continuous uptime, not on every delivered event; prevents infinite reconnect loops when the server crashes after each event.
116
- - **Exponential backoff on reconnect** — monitor reconnects use 2s/4s/8s/16s/30s backoff instead of a fixed 2s delay.
117
- - **SIGINT/SIGTERM clears awareness** — monitor posts a final `clearAwareness` before exit so the "Claude is active" indicator doesn't hang in the browser.
118
- - **Per-route fetch timeouts** — `AbortSignal.timeout` enforces budgets per route (connect 10s, mode 2s, awareness 5s, error report 3s) to prevent hung SSE connects or mode lookups from stalling the monitor.
119
- - **SSE parse errors don't advance&#x20;****lastEventId** — JSON parse failures and schema validation errors are logged with event ID + frame tail but do not advance `lastEventId`, so bad events are re-delivered on reconnect rather than silently dropped.
120
- - **SKILL.md corrected** — `question` annotation guidance now uses `type === 'comment' && directedAt === 'claude' && author === 'user'`; all 5 highlight colors listed (yellow, red, green, blue, purple).
121
-
122
- ## \[0.5.0] - 2026-04-13
123
-
124
- ### Added
125
-
126
- - **Authorship tracking** — Y.Map overlay marks text as user-written or Claude-written, with text-color styling (blue for user, orange for Claude) (#190)
127
- - **Threaded annotation replies** — reply to annotations with back-and-forth conversation threads (#187)
128
- - **Claude cursor decoration** — character-level cursor shows where Claude is editing in real time (#209)
129
- - **Auto-save** — documents save automatically on change; Ctrl+S triggers immediate manual save (#272)
130
- - **Text zoom** — keyboard shortcuts (Ctrl+=/Ctrl+-) for adjusting text size in the Tauri desktop app (#273)
131
- - **Three-panel default layout** — editor, side panel, and chat visible by default (#264)
132
- - **Selection event suppression** — selection events only fire after a chat message is sent, reducing noise (#270)
133
- - V1.0 release plan added to roadmap (#279)
134
-
135
- ### Fixed
136
-
137
- - Session persistence, tab bar horizontal scrollbar, and tab cycling keyboard shortcuts (#278)
138
- - Authorship styling uses text color instead of background highlight; reopen sync corrected
139
- - Annotation replies renamed from Acknowledge/Dismiss to Accept/Reject for clarity
140
-
141
- ### Changed
142
-
143
- - Pinned Hocuspocus and Y.js dependency versions to prevent upstream breakage (#271)
144
- - EOL normalizer added to lint-staged for .yml and .md files (#263)
145
- - Lessons learned applied to codebase and tooling (#280)
146
-
147
- ## \[0.4.0] - 2026-04-12
148
-
149
- ### Added
150
-
151
- - Tauri v2 desktop app wrapping the existing web editor (macOS, Linux, Windows)
152
- - System tray with menu: Open Editor, Setup Claude, Check for Updates, About, Quit
153
- - Single-instance detection — second launch focuses the existing window instead of opening a duplicate
154
- - Node.js sidecar lifecycle: auto-spawn on startup, health polling, crash restart with exponential backoff
155
- - Auto-updater checks on launch and every 8h; manual check available via tray menu "Check for Updates"
156
- - Cross-platform CI release workflow (macOS arm64/x64, Linux x64, Windows x64) with GitHub Releases
157
- - Window state persistence — position and size remembered across sessions
158
- - Sample file copied to writable data directory on first run (supports read-only app bundles)
159
- - Self-signed code signing for Windows builds in CI
160
- - MCP setup auto-configuration on launch — writes Claude Code/Desktop config without manual `tandem setup`
161
-
162
- ### Fixed
163
-
164
- - Accept `tauri.localhost` origin in WebSocket and CORS checks so the production WebView can connect
165
- - Strip Windows `\\?\` extended-length path prefix returned by Tauri path APIs before passing to Node
166
- - Sidecar binary name resolution corrected for Tauri's platform-specific naming convention
167
- - Self-contained JS bundles via tsup `noExternal` so Tauri doesn't require `node_modules` at runtime
168
- - Poll for port release before restarting after update install, preventing a startup race condition
169
- - Surface last HTTP error in health-poll timeout diagnostics for easier debugging
170
-
171
- ### Changed
172
-
173
- - Updater-unavailable log downgraded from error to debug (reduces noise in dev builds without updater keys)
174
- - Unhandled sidecar events are now logged instead of silently dropped
175
- - Warn when `sample/` directory is missing in release builds
176
- - CI: fail build when updater signing key secret is absent
177
- - CI: summary job catches partial platform build failures rather than reporting false success
178
-
179
- ## \[0.3.2] - 2026-04-12
180
-
181
- ### Changed
182
-
183
- - **Annotation type unification:** Three semantically identical types (`comment`, `suggestion`, `question`) collapsed into a single `comment` type with optional `suggestedText` and `directedAt` fields. `AnnotationTypeSchema` reduced from 5 values to 3: `highlight`, `comment`, `flag`. (#193, #245, #255)
184
- - **Toolbar:** Three annotation buttons (Comment, Suggest, Ask Claude) replaced with a single Comment button with "Replace" and "@Claude" toggles (#193)
185
- - **Side panel filters:** "Suggestions" → "With replacement", "Questions" → "For Claude" (#193)
186
- - **sanitizeAnnotation()****&#x20;moved to&#x20;****src/shared/sanitize.ts** — now available to both server and client code. Client-side Y.Map reads are sanitized to handle legacy session data. (#255)
187
-
188
- ### Added
189
-
190
- - Link (Ctrl+K), Horizontal Rule, and Code Block buttons in the formatting toolbar (#204)
191
- - Replacement cards show a visual diff — original text in red strikethrough → replacement in green (#195)
192
- - Undo countdown progress bar — a shrinking indicator shows the 10-second undo window (#196)
193
- - Review mode shortcut hints (Y / N / ↑↓ / Z) shown below the Review button (#200)
194
- - Chat anchor previews expand on hover to show full text (#198)
195
- - `disabledTitle` prop on toolbar buttons — annotation buttons show "Select text first" when no text is selected (#197)
196
- - Explicit ✕ close button on the highlight color picker (#203)
197
- - `tandem_comment` now accepts optional `suggestedText` and `directedAt` parameters (#193)
198
- - `sanitizeAnnotation()` normalizes legacy `suggestion`/`question` entries at read boundaries — permanent migration for historical session data (#193)
199
- - Exhaustive type switch in `buildDecorations` catches unhandled annotation types at compile time (#255)
200
- - 8 new tests covering MCP tool params, sanitization edge cases, and legacy migration paths (#255)
201
-
202
- ### Fixed
203
-
204
- - Toolbar wraps to a second row on narrow windows instead of overflowing; inline inputs shrink responsively (#192)
205
- - Edit button on annotation cards now shows a visible "✎ Edit" label instead of icon-only (#201)
206
- - Client-side legacy annotations (from pre-0.3.2 sessions) no longer render as invisible decorations or display raw JSON (#255)
207
- - `sanitizeAnnotation` no longer drops `textSnapshot: ""` or `editedAt: 0` via falsy-check bug (#255)
208
- - Event queue observer no longer silently dies if `sanitizeAnnotation` throws (#255)
209
- - `handleEdit` catch-block no longer corrupts annotation data on JSON parse failure (#255)
210
-
211
- ### Deprecated
212
-
213
- - `tandem_suggest` MCP tool — use `tandem_comment` with `suggestedText` parameter instead (#193)
214
-
215
- ### Removed
216
-
217
- - **MCP wire change:** Removed unused `"overlay"` annotation kind from `AnnotationTypeSchema`. External clients sending `type: "overlay"` will now receive a Zod validation error. (#249)
218
- - **MCP wire change:** `suggestion` and `question` annotation types removed from `AnnotationTypeSchema`. Use `comment` with `suggestedText` or `directedAt` fields. Legacy data is migrated automatically via `sanitizeAnnotation()`. (#193)
219
-
220
- ## \[0.3.0] - 2026-04-07
221
-
222
- ### Wave 4: Notification & Interruption Redesign
223
-
224
- - **Solo/Tandem mode** replaces All/Urgent/Paused interruption controls (#207, #226)
225
- - **Dwell-time selection events** — selections fire after 1s hold (#188)
226
- - **Configurable layout** — tabbed or three-panel, with settings popover (#206)
227
- - **Click-to-navigate** — click annotated text to jump to annotation card
228
- - **Tab badges** — notification counts on inactive panel tabs
229
- - **Skill-directed response routing** — Claude responds in chat panel, not terminal
230
- - Review banner replaced with per-annotation toasts (#208, landed earlier)
231
- - `Y_MAP_MODE` constant, Zod validation for mode reads, error logging in channel event bridge
232
- - 894 tests passing
233
-
234
- ## \[0.2.12] - 2026-04-06
235
-
236
- ### Added
237
-
238
- - Undo/redo toolbar buttons powered by Y.js UndoManager — tracks document edits with proper CRDT-aware undo scoping (#189, #210)
239
- - Adjustable editor content width toggle — switch between comfortable and full-width layouts, preference persists in localStorage (#185, #205)
240
- - SVG icons for unordered list, ordered list, and blockquote toolbar buttons, replacing plain text labels (#194)
241
- - Automated npm publishing via GitHub Actions with OIDC trusted publisher (tokenless)
242
-
243
- ### Fixed
244
-
245
- - Guard all localStorage access with try-catch for private/disabled browser storage modes; reset scroll position on annotation filter clear (#212, #202)
246
-
247
- ## \[0.2.11] - 2026-04-06
248
-
249
- ### Added
250
-
251
- - Auto-reload documents when files change on disk — Tandem detects external edits (e.g., Claude's Edit tool) via `fs.watch`, reloads content, and preserves existing annotations (#175)
252
- - File watcher module with 500ms debounce and self-write suppression (prevents reload loops when Tandem saves)
253
- - Toast notification when a document is reloaded from disk
254
- - Runtime warning when `onDocSwapped` callback is missing during Hocuspocus doc swap (defensive guard for #178 audit)
255
- - 28 new tests: observer reattachment, CTRL\_ROOM lifecycle, buffer cap, file watcher debounce/suppress, annotation-preserving reload
256
-
257
- ### Fixed
258
-
259
- - Dead CRDT `relRange` handling — `refreshRange` now strips broken CRDT anchors and re-anchors from flat offsets instead of leaving annotations permanently stuck with non-functional RelativePositions (#175)
260
- - Buffer cap test was previously a no-op (empty loop body) — now actually exercises the event queue buffer (#178)
261
-
262
- ### Changed
263
-
264
- - CLAUDE.md gotcha for Hocuspocus doc replacement updated to document the automatic `onDocSwapped` callback lifecycle (#178)
265
-
266
- ## \[0.2.10] - 2026-04-05
267
-
268
- ### Added
269
-
270
- - Resizable side panel — drag to resize between 200–600px, width persists in localStorage
271
- - Accessibility: ARIA labels on annotation highlights (type-specific), annotation cards (`role="listitem"`, `aria-current`), annotation list (`role="list"`), review mode button (`aria-pressed`), live region for pending count and review progress
272
-
273
- ### Fixed
274
-
275
- - Flaky session tests — each test file now uses an isolated temp directory via `vi.mock`, eliminating cross-file race conditions (#177)
276
- - Session file writes use atomic rename with retry on Windows EPERM/EACCES (#173)
277
-
278
- ### Changed
44
+ ### Internal
279
45
 
280
- - `atomicWrite()` extracted as shared helper in session manager — consolidates duplicate write-tmp-rename logic with exponential backoff retry
46
+ - Mixed-partial `/api/setup` 207 test (#292).
47
+ - `@xmldom/xmldom` dependency bump (#390).
48
+ - CI: typecheck/lint/test gates on all PR base branches.
281
49
 
282
- ## \[0.2.9] - 2026-04-05
50
+ ## \[0.7.1] - 2026-04-20
283
51
 
284
52
  ### Fixed
285
53
 
286
- - Changelog tab no longer disappears after upgrade version check and sample/welcome.md now open before servers start, preventing CRDT merge races with stale browser tabs
287
- - Tutorial annotation injection errors now get their own log message instead of being misattributed as file-open failures
288
-
289
- ## \[0.2.8] - 2026-04-05
290
-
291
- ### Added
292
-
293
- - CHANGELOG.md opens as the active tab on first startup after an npm update
294
- - `checkVersionChange` helper tracks version transitions via `last-seen-version` file
295
- - CHANGELOG.md now ships in the npm package
296
-
297
- ## \[0.2.7] - 2026-04-05
298
-
299
- ### Fixed
54
+ - **MSIX Claude Desktop detection** `tandem setup` now detects Claude Desktop installed via MSIX (Microsoft Store) and generates stdio MCP entries for it (#372)
300
55
 
301
- - Force-reload (`tandem_open` with `force: true`) now clears Y.Doc in-place instead of destroying the Hocuspocus room — sidebar, observers, and connections survive
302
- - TOCTOU fix: session deletion moved after successful reload transaction
303
- - Observer ownership table corrected in architecture docs
56
+ ## \[0.7.0] - 2026-04-20
304
57
 
305
58
  ### Added
306
59
 
307
- - 4 new tests for force-reload (annotation clearing, awareness clearing, .txt reload, metadata)
308
-
309
- ## \[0.2.6] - 2026-04-05
310
-
311
- ### Fixed
312
-
313
- - Demo script rewritten to be self-referential for recording
314
- - Observer ownership documentation added to architecture.md
315
-
316
- ## \[0.2.5] - 2026-04-05
317
-
318
- ### Fixed
319
-
320
- - `tandem setup` Claude Code MCP config path updated
321
-
322
- ## \[0.2.4] - 2026-04-05
323
-
324
- ### Fixed
325
-
326
- - Security audit findings (DNS rebinding, CORS, input validation)
327
-
328
- ## \[0.2.3] - 2026-04-05
329
-
330
- ### Fixed
331
-
332
- - `tandem setup` now writes Claude Code MCP config to `~/.claude.json` instead of `~/.claude/mcp_settings.json`, which Claude Code no longer reads
333
-
334
- ## \[0.2.2] - 2025-04-05
60
+ - **Auth token storage** on first boot the server generates a 32-byte base64url token and persists it to the platform data directory (`%LOCALAPPDATA%\tandem\Data\auth-token` on Windows, `~/.local/share/tandem/auth-token` on Linux, `~/Library/Application Support/tandem/auth-token` on macOS). Subsequent boots reuse the token. First-boot race is protected by `O_EXCL` file creation. Tauri mode receives the token via `TANDEM_AUTH_TOKEN` env before sidecar spawn and never regenerates.
61
+ - **Auth middleware** — non-loopback MCP and API requests require `Authorization: Bearer <token>`. Loopback connections (`127.0.0.1`, `::1`, `::ffff:127.0.0.1`) remain exempt, preserving zero-config Claude Code usage. Token comparison uses SHA-256 on both sides before `crypto.timingSafeEqual` to eliminate the length oracle. Rate-limiting (5 attempts / 60 s) keyed by IPv4 address or IPv6 `/64` prefix with LRU eviction; Authorization headers are redacted from all rejection logs.
62
+ - **`TANDEM_BIND_HOST` bind-mode selection** — MCP HTTP server binds to `127.0.0.1` by default; set `TANDEM_BIND_HOST=0.0.0.0` (or a specific LAN IP) to expose Tandem on the local network. Hocuspocus WebSocket always stays loopback. Non-loopback bind without a token file exits 1 with guidance; `TANDEM_ALLOW_UNAUTHENTICATED_LAN=1` is the escape hatch. Multi-homed machines require `TANDEM_LAN_IP` to be set explicitly.
63
+ - **`tandem rotate-token`** — new CLI subcommand that atomically regenerates the auth token, notifies the running server to open a 60-second grace window for in-flight sessions, and re-runs `tandem setup` across all detected MCP config files. Prints old and new token fingerprints (first 8 hex chars of SHA-256). Refuses rotation when `TANDEM_AUTH_TOKEN` is set in the environment (Tauri mode).
64
+ - **Token forwarding in stdio bridge, monitor, and channel sidecars** — `tandem mcp-stdio`, `tandem monitor`, and the channel sidecar now forward `TANDEM_AUTH_TOKEN` as `Authorization: Bearer` on upstream HTTP calls. Malformed tokens (empty, `Bearer`-prefixed, < 32 chars, non-URL-safe) exit 1 with a specific message.
65
+ - **OAuth protected-resource metadata** — `/.well-known/oauth-protected-resource/mcp` now declares `bearer_methods_supported: ["header"]` and a literal-`localhost` `resource` field per RFC 9728.
66
+ - **`/health` session-presence guard** `hasSession` is omitted from `/health` responses on non-loopback requests, preventing session-presence leakage on LAN binds.
335
67
 
336
- ### Fixed
68
+ ### Security
337
69
 
338
- - Silent failure review findings
70
+ - Loopback detection keys off `req.socket.remoteAddress` exclusively — `Host` header is never trusted for the loopback bypass decision.
71
+ - Fail-closed on LAN bind: `TANDEM_BIND_HOST=0.0.0.0` without a token file exits 1; the server never auto-generates a token and proceeds silently.
72
+ - `crypto.randomBytes` failure or non-writable data directory → server exits 1; no silent fallback.
339
73
 
340
- ## \[0.2.1] - 2025-04-05
74
+ ## \[0.6.4] - 2026-04-20
341
75
 
342
76
  ### Fixed
343
77
 
344
- - Full security audit25 findings across 7 categories (#172)
345
-
346
- ## \[0.2.0] - 2025-04-04
347
-
348
- ### Added
349
-
350
- - Initial public release on npm as `tandem-editor`
351
- - 30 MCP tools for collaborative document editing
352
- - Multi-document tabs with CRDT-anchored annotations
353
- - Chat sidebar with real-time channel push
354
- - Support for .md, .docx, .txt, .html files
355
- - `tandem` CLI with `setup` and `start` commands
356
- - Claude Code skill auto-installation
357
-
358
- # Changelog
359
-
360
- All notable changes to Tandem will be documented in this file.
361
-
362
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
363
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
364
-
365
- ## \[Unreleased]
78
+ - **Flaky layout-switch E2E test (#281)** `toHaveCount` assertions on resize-handle locators lacked explicit timeouts, causing intermittent CI failures under load when React re-renders were slower than the default 5 s expectation. All 8 assertions now carry `{ timeout: 10_000 }`, and the missing `right-panel-resize-handle` absence assertion in the tabbed-layout block has been added.
79
+ - **Silent crashes in CLI entry points (#336)** — `src/cli/index.ts` and `src/channel/index.ts` lacked `process.once("uncaughtException")` / `process.once("unhandledRejection")` handlers. Uncaught throws in `tandem start`, `tandem setup`, and the Tauri channel sidecar exited silently with code 0, surfacing as "tools never appear" with no diagnostics. Both entries now write a labelled message to stderr and exit 1. The `uncaughtException` handler uses `err: unknown` with an `instanceof Error` guard so non-Error throws (strings, plain objects) produce the actual thrown value rather than `"undefined"`.
366
80
 
367
81
  ## \[0.6.3] - 2026-04-19
368
82
 
package/README.md CHANGED
@@ -29,7 +29,7 @@ Requires **Node.js 22+** ([download](https://nodejs.org)) and **Claude Code** (`
29
29
  ```bash
30
30
  npm install -g tandem-editor
31
31
  tandem setup # registers MCP tools + installs Claude Code skill
32
- tandem # starts server + opens browser
32
+ tandem # starts server + opens editor
33
33
  ```
34
34
 
35
35
  `tandem setup` auto-detects Claude Code and Claude Desktop, writes MCP configuration, and installs a skill (`~/.claude/skills/tandem/SKILL.md`) that teaches Claude how to use Tandem's tools effectively. Re-run after upgrading (`npm update -g tandem-editor && tandem setup`).
@@ -73,7 +73,7 @@ claude --dangerously-load-development-channels server:tandem-channel
73
73
 
74
74
  This is the magic-sauce mode — and it's the one I'd recommend you run with. The channel shim pushes events (selections, annotations, chat) to Claude over SSE the moment they happen, so Tandem genuinely feels like there's another person on the other end of the document: someone watching what you highlight, reacting to edits you accept, and chiming in on a paragraph the instant you select it, the way a collaborator on a Google Doc would. The `--dangerously-load-development-channels` flag is an experimental Claude Code feature, which is why it isn't on by default — but turning it on is what makes the whole experience click.
75
75
 
76
- **Recommended layout:** snap the Claude Code terminal to one side of your screen and the Tandem browser window to the other. You'll be flipping attention between them constantly, and having both visible is what makes the side-by-side-collaborator feeling land.
76
+ **Recommended layout:** snap the Claude Code terminal to one side of your screen and the Tandem editor window to the other. You'll be flipping attention between them constantly, and having both visible is what makes the side-by-side-collaborator feeling land.
77
77
 
78
78
  Then try:
79
79
 
@@ -81,11 +81,11 @@ Then try:
81
81
  "Open sample/welcome.md and review it with me"
82
82
  ```
83
83
 
84
- Claude calls `tandem_open`, the document appears in the browser, and you're ready to collaborate.
84
+ Claude calls `tandem_open`, the document appears in the editor, and you're ready to collaborate.
85
85
 
86
86
  #### The core loop — no copy/paste
87
87
 
88
- 1. Highlight a paragraph in the browser editor.
88
+ 1. Highlight a paragraph in the editor.
89
89
  2. With channels on, Claude often reacts before you even say anything. Otherwise, just type what you want in the terminal: *"what do you think of this paragraph?"* or *"rewrite this to be more concise"*.
90
90
  3. Claude reads your selection directly from the shared Tandem state (via `activity.selectedText` on `tandem_checkInbox`). You never paste the passage into the terminal.
91
91
  4. Claude replies in the Tandem chat sidebar (`tandem_reply`) or drops annotations on the document (`tandem_annotate` / `tandem_suggestEdit`), which you can accept, dismiss, or edit in the side panel.
@@ -119,7 +119,7 @@ Or check the raw health endpoint:
119
119
 
120
120
  ```bash
121
121
  curl http://localhost:3479/health
122
- # → {"status":"ok","version":"0.4.0","transport":"http","hasSession":false}
122
+ # → {"status":"ok","version":"0.8.0","transport":"http","hasSession":false}
123
123
  ```
124
124
 
125
125
  `hasSession` becomes `true` once Claude Code connects.
@@ -131,7 +131,7 @@ curl http://localhost:3479/health
131
131
  git clone https://github.com/bloknayrb/tandem.git
132
132
  cd tandem
133
133
  npm install
134
- npm run dev:standalone # starts server (:3478/:3479) + browser client (:5173)
134
+ npm run dev:standalone # starts server (:3478/:3479) + editor client (:5173)
135
135
  ```
136
136
 
137
137
  Open http://localhost:5173 — you'll see `sample/welcome.md` loaded automatically on first run. The `.mcp.json` in the repo configures Claude Code automatically when run from this directory.
@@ -142,7 +142,7 @@ Open http://localhost:5173 — you'll see `sample/welcome.md` loaded automatical
142
142
 
143
143
  You point at text, Claude sees it. Here's how that plays out day-to-day:
144
144
 
145
- - **Open a document.** Ask Claude (`"let's work on notes.md in tandem"`), drag a file onto the browser, or click the **+** in the tab bar. `.md`, `.txt`, `.html`, and `.docx` (review-only) are supported.
145
+ - **Open a document.** Ask Claude (`"let's work on notes.md in tandem"`), drag a file onto the editor, or click the **+** in the tab bar. `.md`, `.txt`, `.html`, and `.docx` (review-only) are supported.
146
146
  - **Point at what you mean.** Select text in the editor and ask Claude about "this paragraph" in the terminal — or just wait for Claude to react if you have channels on. Claude reads your selection directly, no copy-paste needed. Hold the selection for about a second so it registers (dwell-time gating filters out incidental clicks).
147
147
  - **Iterate on Claude's response.** Claude's suggestions appear as annotations in the side panel — accept, dismiss, edit, or ask follow-up questions. Each round refines the text without you ever leaving the document. Press **Ctrl+Shift+R** for keyboard review mode: **Tab** to navigate, **Y** accept, **N** dismiss, **E** edit, **Z** undo within a 10-second window.
148
148
  - **Heads-down vs collaborative.** Toggle **Solo** mode when you want to write without interruptions — Tandem queues non-urgent annotations until you flip back to **Tandem** mode. Both `tandem_status` and `tandem_checkInbox` return the current mode so Claude adapts its behavior automatically.
@@ -183,42 +183,52 @@ Press **Ctrl+Shift+R** to enter keyboard review mode. Navigate with **Tab**, acc
183
183
  - **Configurable display name** — set your name so Claude knows who's reviewing
184
184
  - **Atomic file saves** — write to temp, then rename, preventing partial writes
185
185
  - **E2E tested** — Playwright tests cover the annotation lifecycle end-to-end
186
+ - **Authorship text coloring** — blue for your edits, orange for Claude's, toggled per-document
187
+ - **Threaded annotation replies** — back-and-forth conversation on any annotation
188
+ - **Auto-save** — documents save on change; Ctrl+S for manual trigger
189
+ - **Settings popover** — Light/Dark/System theme, text size (S/M/L), reduce motion, display name (Ctrl+,)
190
+ - **Auth tokens for LAN exposure** — bind to `0.0.0.0` with auto-generated tokens; `tandem rotate-token` for rotation
191
+ - **Durable annotation persistence** — annotations survive server restarts independently of session files
192
+ - **Claude Code plugin** — `tandem mcp-stdio` + `tandem channel` bridge Tandem into Cowork and Claude Desktop
186
193
 
187
194
  ## Where Tandem is headed
188
195
 
189
- Tandem v0.4.0 ships a native desktop app (macOS, Linux, Windows) alongside the existing npm CLI. A few directions on the radar for later releases:
196
+ Since the v0.4.0 desktop app launch, Tandem has added auth tokens for LAN exposure (v0.7.0), a Claude Code plugin bridge for Cowork and Claude Desktop (v0.6.0+), durable annotation persistence, settings with Light/Dark/System theming, and authorship text coloring. A few directions on the radar for later releases:
190
197
 
191
- - **High-fidelity .docx round-trip** — current `.docx` support is review-only; LibreOffice-headless-based production export is planned so you can stay in Tandem through the final draft.
192
- - **Claude Desktop parity** — the MCP server already works with Claude Desktop; polish and documentation for a first-class experience there is in the works.
198
+ - **High-fidelity .docx round-trip** — current `.docx` support is review-only; production export is planned so you can stay in Tandem through the final draft.
193
199
  - **Exportable annotated documents** — PDF (and eventually `.docx`) with annotations baked in, so you can share reviewed drafts outside Tandem.
194
200
  - **Code editing mode** — CodeMirror 6 surface for reviewing code the same way you review prose.
195
- - **Standalone mode** — direct Anthropic API connection so Tandem can run without Claude Code in the loop, for users who want a pure browser-based experience.
201
+ - **Standalone mode** — direct Anthropic API connection so Tandem can run without Claude Code in the loop, for users who want a pure standalone experience.
196
202
 
197
203
  See the full [Roadmap](docs/roadmap.md) and [Known Limitations](docs/roadmap.md#known-limitations-v1) for the complete picture, including items that are explicitly out of scope for v1.
198
204
 
199
205
  ## Documentation
200
206
 
201
- - **[User Guide](docs/user-guide.md)** — How to use Tandem: browser UI, annotations, chat, review mode, keyboard shortcuts
207
+ - **[User Guide](docs/user-guide.md)** — How to use Tandem: editor UI, annotations, chat, review mode, keyboard shortcuts
202
208
  - [MCP Tool Reference](docs/mcp-tools.md) — 31 MCP tools + channel API endpoints
203
209
  - [Architecture](docs/architecture.md) — System design, data flows, coordinate systems, channel push
204
210
  - [Workflows](docs/workflows.md) — Claude Code usage patterns: text iteration, cross-referencing, multi-model
205
211
  - [Roadmap](docs/roadmap.md) — Phase 2+ roadmap, known issues, future extensions
206
- - [Design Decisions](docs/decisions.md) — ADR-001 through ADR-022
207
- - [Lessons Learned](docs/lessons-learned.md) — 37 implementation lessons
212
+ - [Design Decisions](docs/decisions.md) — ADR-001 through ADR-024
213
+ - [Lessons Learned](docs/lessons-learned.md) — 44 implementation lessons
208
214
 
209
215
  ## CLI Commands
210
216
 
211
217
  | Command | What it does |
212
218
  |---------|-------------|
213
- | `tandem` | Start server and open browser (global install) |
219
+ | `tandem` | Start server and open editor (global install) |
214
220
  | `tandem setup` | Register MCP tools with Claude Code / Claude Desktop |
215
221
  | `tandem setup --force` | Register to default paths regardless of auto-detection |
216
222
  | `tandem --version` | Show installed version |
217
223
  | `tandem --help` | Show usage |
224
+ | `tandem setup --with-channel-shim` | Also register the stdio channel shim |
225
+ | `tandem rotate-token` | Rotate auth token (60-second grace window) |
226
+ | `tandem mcp-stdio` | Run as stdio MCP server (proxy to local HTTP, for plugin bridge) |
227
+ | `tandem channel` | Run the channel shim (stdio MCP for plugin's tandem-channel entry) |
218
228
 
219
229
  ## MCP Configuration
220
230
 
221
- Tandem registers two MCP connections: **HTTP** for document tools (30 tools including annotation editing — always on), and a **channel shim** for real-time push notifications. The channel shim is what enables the live-collaborator experience described in [Connect Claude Code](#connect-claude-code) and is recommended; it activates when you start Claude Code with `--dangerously-load-development-channels server:tandem-channel`. If you'd rather not pass that experimental flag, the entry sits idle and everything still works through polling on the HTTP connection — you just lose spontaneous reactions.
231
+ Tandem registers two MCP connections: **HTTP** for document tools (31 tools including annotation editing — always on), and a **channel shim** for real-time push notifications. The channel shim is what enables the live-collaborator experience described in [Connect Claude Code](#connect-claude-code) and is recommended; it activates when you start Claude Code with `--dangerously-load-development-channels server:tandem-channel`. If you'd rather not pass that experimental flag, the entry sits idle and everything still works through polling on the HTTP connection — you just lose spontaneous reactions.
222
232
 
223
233
  **Global install** (`tandem setup`): Automatically writes both entries to `~/.claude/mcp_settings.json` (Claude Code) and/or `claude_desktop_config.json` (Claude Desktop) with absolute paths. No manual configuration needed.
224
234
 
@@ -254,6 +264,13 @@ All optional — defaults work out of the box.
254
264
  | `TANDEM_TRANSPORT` | `http` | Transport mode (`http` or `stdio`) |
255
265
  | `TANDEM_NO_SAMPLE` | unset | Set to `1` to skip auto-opening `sample/welcome.md` |
256
266
  | `TANDEM_CLAUDE_CMD` | `claude` | Claude Code executable name (for `tandem setup` auto-detection) |
267
+ | `TANDEM_BIND_HOST` | `127.0.0.1` | Bind address for MCP HTTP (`0.0.0.0` for LAN) |
268
+ | `TANDEM_AUTH_TOKEN` | auto-generated | Override auth token (set by Tauri; manual use rare) |
269
+ | `TANDEM_ALLOW_UNAUTHENTICATED_LAN` | unset | Set to `1` to skip token requirement on LAN bind |
270
+ | `TANDEM_LAN_IP` | auto-detected | Explicit LAN IP for multi-homed machines |
271
+ | `TANDEM_REQUEST_TIMEOUT_MS` | `30000` | Per-request timeout in stdio bridge (ms) |
272
+ | `TANDEM_APP_DATA_DIR` | platform default | Override app-data root (sessions, auth-token, annotations) |
273
+ | `TANDEM_ANNOTATION_STORE` | unset | Set to `off` to disable durable annotation persistence |
257
274
 
258
275
  See `.env.example` for a copy-paste template.
259
276
 
@@ -270,10 +287,10 @@ Tandem kills stale processes on :3478/:3479 at startup. If another app uses thos
270
287
  **Channel shim fails to start**
271
288
  The `tandem-channel` entry spawns a subprocess. For global installs, `tandem setup` writes absolute paths to the bundled `dist/channel/index.js` — re-run `tandem setup` after upgrading. For dev setup, if you see `MODULE_NOT_FOUND` with a production config (`node dist/channel/index.js`), run `npm run build`. The default dev config uses `npx tsx` and doesn't require a build step.
272
289
 
273
- **Browser shows "Cannot reach the Tandem server"**
274
- The browser connects to the server via WebSocket. For global installs, run `tandem` to start the server. For dev setup, use `npm run dev:standalone` (or `npm run dev:server`). The message appears after 3 seconds of failed connection.
290
+ **Editor shows "Cannot reach the Tandem server"**
291
+ The editor connects to the server via WebSocket. For global installs, run `tandem` to start the server. For dev setup, use `npm run dev:standalone` (or `npm run dev:server`). The message appears after 3 seconds of failed connection.
275
292
 
276
- **Empty browser with no document**
293
+ **Empty editor with no document**
277
294
  On first run, `sample/welcome.md` auto-opens. If you've cleared sessions or deleted the sample file, click the **+** button in the tab bar or drop a file onto the editor.
278
295
 
279
296
  ## Development