tandem-editor 0.11.2 → 0.13.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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +3 -3
- package/CHANGELOG.md +201 -72
- package/README.md +141 -238
- package/dist/channel/index.js +211 -81
- package/dist/channel/index.js.map +1 -1
- package/dist/cli/index.js +749 -170
- package/dist/cli/index.js.map +1 -1
- package/dist/client/assets/CoworkSettings-BOYbyKul.js +3 -0
- package/dist/client/assets/event-CNdo2oXa.js +1 -0
- package/dist/client/assets/index-D8uS4cj7.css +1 -0
- package/dist/client/assets/index-Dm_QtxGQ.js +1 -0
- package/dist/client/assets/index-g-KwmRn9.js +271 -0
- package/dist/client/assets/webview-KiZyy_pC.js +1 -0
- package/dist/client/assets/window-DePn7tLG.js +1 -0
- package/dist/client/fonts/OFL-Hanuman.txt +93 -0
- package/dist/client/fonts/OFL-InterTight.txt +93 -0
- package/dist/client/fonts/OFL-JetBrainsMono.txt +93 -0
- package/dist/client/fonts/OFL-SNPro.txt +93 -0
- package/dist/client/fonts/OFL-Sono.txt +93 -0
- package/dist/client/fonts/OFL-SourceSerif4.txt +93 -0
- package/dist/client/fonts/hanuman-latin.woff2 +0 -0
- package/dist/client/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/dist/client/fonts/sn-pro-latin.woff2 +0 -0
- package/dist/client/fonts/sono-latin.woff2 +0 -0
- package/dist/client/fonts/source-serif-4-latin.woff2 +0 -0
- package/dist/client/index.html +206 -17
- package/dist/client/logo.png +0 -0
- package/dist/monitor/index.js +241 -160
- package/dist/monitor/index.js.map +1 -1
- package/dist/server/index.js +22828 -19659
- package/dist/server/index.js.map +1 -1
- package/package.json +12 -4
- package/sample/welcome.md +6 -6
- package/skills/tandem/SKILL.md +15 -0
- package/dist/client/assets/CoworkSettings-DK3jjdwK.js +0 -3
- package/dist/client/assets/index-CfT503n4.js +0 -297
- package/dist/client/assets/index-DeJe09pn.css +0 -1
- package/dist/client/assets/webview-Ben21ZLJ.js +0 -1
- package/dist/client/assets/window-BxBvHL5k.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,7 +5,143 @@ All notable changes to Tandem will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.13.0] - 2026-05-25
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Settings → Models tab + edit modal (Wave 2 PR 8b, #659)** — new tab in `SettingsModal` for managing the Models registry. Provider-grouped list with per-row enable toggle, edit, two-step delete confirm. `ModelEditModal` covers add and edit flows: provider select (Anthropic / OpenAI / Gemini / Ollama / llama.cpp), conditional cloud-vs-local fields, reveal-gated API key (masked `••••${last4}` on edit; "Replace key" button to enter a new value — existing key never round-trips through the DOM). Mandatory in-product disclosure banner about plaintext localStorage storage.
|
|
13
|
+
- **Models registry data model + `useModels` hook (Wave 2 PR 8a, #659)** — `TandemSettings` now carries a `models: ModelRegistryEntry[]` array tracking AI providers Tandem can call out to (Anthropic, OpenAI, Gemini, local Ollama, local llama.cpp). The data layer ships in this PR; the Settings → Models UI ships in PR 8b. Orthogonal to `IntegrationConfig` (#477) — that schema tracks MCP clients connecting INTO Tandem, while this tracks providers Tandem talks OUT to.
|
|
14
|
+
- **Multi-provider Models registry (#659, #784)** — keychain-backed secret storage, per-provider default selection, and a first-run model picker spanning Anthropic / OpenAI / Gemini cloud providers plus local Ollama / llama.cpp endpoints. A legacy-migration banner upgrades pre-registry single-key setups.
|
|
15
|
+
- **Schema v2 → v3 migration with forward-compat read-only mode** — `loadSettings` now walks an explicit migration chain (v1→v2→v3). An on-disk `schemaVersion` greater than v3 loads defensively with `_readOnly: true`, and `createTandemSettings.updateSettings` short-circuits writes on read-only settings. This is the load-bearing defence against a downgraded client clobbering a newer client's Models registry / future fields on first save.
|
|
16
|
+
- **Integration setup wizard + first-run auto-open (#477 PRs 1/3a/3b/3c-i/3c-ii-b — #728, #729, #730, #731, #773)** — a full-screen wizard for connecting MCP clients (Claude Code and other agents) to Tandem. Ships the `IntegrationConfig` schema with atomic storage + v1→v2→v3 migration, a non-mutating reader for existing `~/.claude.json` entries, and an apply path (`POST /api/integrations/apply`) that backs up the target config before rewriting it. On first launch with no `integrations.json` the wizard opens automatically; later launches do not. Enabled by default.
|
|
17
|
+
- **Native OS keychain on the Tauri desktop app (#477 PR 3c-tauri-keychain, #732)** — integration secrets are stored in the platform keychain on desktop, with graceful env-var fallback when the keychain is unavailable.
|
|
18
|
+
- **Auto-launcher for Claude Code (#477 PR 4a/4b, #800)** — Tandem spawns and supervises the Claude Code CLI alongside the server, with a native cross-platform parent-death reaper so the child is reliably cleaned up on exit. Adds command-palette actions "Relaunch Claude in this folder" and "Start fresh Claude conversation", a working-directory picker in Settings → Integrations, and a project-context skill bundled on startup. Enabled by default for users with a configured `claude-code` integration; opt out with `TANDEM_DISABLE_LAUNCHER=1`.
|
|
19
|
+
- **Native file picker + drag-and-drop on the Tauri desktop app (#378)** — open files through the OS-native file dialog and by dragging them onto the window; the File Open dialog is consolidated to a single Browse + Recent panel.
|
|
20
|
+
- **Heading-section collapse (#650)** — collapse everything under a heading via its fold control; collapse state persists per document in localStorage and survives heading-text edits and reloads.
|
|
21
|
+
- **Scratchpad save-to-disk (#827)** — save a scratchpad to a real file via the native dialog (`.md` / `.txt`); Save-As accepts user-chosen locations anywhere on disk while keeping the symlink + UNC guards.
|
|
22
|
+
- **Claude typing-presence indicator (#651)** — a presence cue shows when Claude is actively writing, with monotonic token ownership so overlapping presence updates resolve deterministically.
|
|
23
|
+
- **Warm canvas theme (#738)** — a fourth theme option (System / Light / Dark / Warm) and the foundation for the v7 floating-chrome redesign.
|
|
24
|
+
- **Re-landed D11 bundled fonts (#680)** — re-applied the fontsource Latin variable-axis builds of Source Serif 4, Inter Tight, and JetBrains Mono originally shipped in PR #663 then reverted via #678. The SIL OFL 1.1 license texts (`public/fonts/OFL-*.txt`) ship alongside the binaries per §4. Editor body text renders in Source Serif 4 via the existing `--tandem-editor-font-family` token.
|
|
25
|
+
- **Margin annotation view (#649, #711, #715, #719, #720)** — Word-style margin comment bubbles with breathing-room gaps and leader lines connecting each card to its anchor, action parity with the sidebar, and the annotation filter chip relocated into the panel header.
|
|
26
|
+
- **Margin view auto-collapses on rail open or narrow viewport (#683)** — opening the left rail now hides only the left margin column; opening the right rail hides only the right column. When the viewport gets narrow enough that margin reserve + open rails + readable editor would crowd, both columns hide together. A 32px hysteresis band on the viewport threshold prevents flicker when a user drags through the boundary. New `useViewportWidth` rune store provides the rAF-debounced subscription.
|
|
27
|
+
- **Status-bar word-count cycle + page count (#741, #792)** — the floating status pill cycles through word, character, and page counts.
|
|
28
|
+
- **Inline annotation decoration toggle (#791)** — turn the inline highlight/underline decorations on or off without hiding the annotations themselves.
|
|
29
|
+
- **Knowledge-graph pilot (#769, #771)** — 25 hand-curated concept/rule/ADR nodes with cross-edges, queryable via `npm run kg`.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- **Network settings split into Connection / Advanced (Wave 2 PR 6)** — connection status, transport, and the restart-sidecar button stay always-visible; loopback port, degraded-banner delay, reconnect strategy, hold-while-offline, and token rotation collapsed under a new "Advanced" disclosure. Disclosure state is ephemeral (resets each time the modal opens). New `CollapsibleSection.svelte` primitive uses native `<details>/<summary>` for free keyboard a11y.
|
|
34
|
+
- **v7 floating-chrome redesign sweep (Waves 1–M)** — a multi-wave reskin that lifts the editor chrome off the canvas:
|
|
35
|
+
- Edge-anchored side rails with inner-rounded corners (Wave 2, #739) and a shared `.tandem-floating-pill` recipe (Wave 3, #740) applied across the formatting bar, status bar (Wave 5, #741), selection popup (Wave C, #762), and slash menu (Wave 10, #757).
|
|
36
|
+
- TitleBar + DocumentTabs reflow: bar seam removed so chrome melts into the canvas (Wave 4a, #746), pill-shaped tabs (Wave 4b, #748), tabs lifted into the TitleBar center cluster with mask-fade overflow and a floating `+` button (Wave 4a/4b maximalist, #752, #755).
|
|
37
|
+
- Left rail locked to outline-only (Wave D, #759); outline rail clears the status pill lane (Wave 6, #742); peek-from-side strips + edge-click panel collapse (Wave E, #764).
|
|
38
|
+
- Narrow-viewport Settings sidebar drawer + hamburger (Wave 9, #745); `.docx` batch-promote with always-visible checkboxes (Wave 8, #756).
|
|
39
|
+
- Native scrollbars hidden with mask-faded overflow edges (Wave B, #761); titlebar/status/right-rail header polish (Wave A, #760); placeholder + margin-card hover affordance (Wave F, #763).
|
|
40
|
+
- Redesign-parity sweep across titlebar, rails, picker teardown, popup, composer (Waves G–L, #765) plus the Wave M titlebar dropdown + solo-mode fade + dark-mode v7 alignment (#776).
|
|
41
|
+
- **README + documentation rewrite (#777, #778)** — README rewritten for a lay reader with technical detail moved below the fold, plus new `docs/cli.md`, `docs/configuration.md`, `docs/troubleshooting.md`, and `docs/security.md`.
|
|
42
|
+
- **MCP-first integration policy; your AI as the default integration (ADR-038, #722)** — documents that Tandem's integration contract is MCP and Claude is the default concrete integration.
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **Inline code combined with bold/italic/strikethrough/link no longer loses its `code` formatting on save** — opening a `.md` file in Tandem (editable, not read-only) and letting autosave write it back used to drop the `code` mark on any span that also carried bold/italic/strike, and silently discard the surrounding link for code-in-link. The damage surfaced as doubled asterisks and ` ` entity-spaces — code-heavy docs like `docs/decisions.md` were corrupted just by being viewed. `deltaToPhrasingContent` now treats `code` as a leaf and wraps link/strike/italic/bold unconditionally, and a new coalescing pass merges adjacent same-wrapper phrasing nodes so a bold run holding a code span serializes as one emphasis instead of several. Round-trip verified lossless across the repo's code-heavy docs.
|
|
47
|
+
- **Keyboard tab switch no longer reverts under a stale sync** — a local `Ctrl+1..9` (or click) tab switch is client-only and was being clobbered when a late `documentMeta` re-broadcast re-applied the server's active doc, surfacing as a flaky "Ctrl+N switches to the Nth tab" E2E test. The server now broadcasts a monotonic activation epoch (`activeDocumentEpoch`) alongside the active id; the client applies the server's active only when the epoch advances, so a stale re-sync is ignored while a genuine re-activation (e.g. re-opening the already-active doc) still steals focus.
|
|
48
|
+
- **Selection toolbar places itself clear of fixed chrome (#680)** — the selection BubbleMenu flips below the selection when above-placement would overlap the TitleBar + FormattingBar; selections that straddle the viewport fold pin the toolbar to the viewport bottom rather than clamping onto the chrome; and a 4px hysteresis band at the flip boundary keeps the toolbar from shimmering as a selection drifts across the threshold.
|
|
49
|
+
- **Clicked highlight stays focused over an overlapping comment (#817)** — clicking a highlight that overlaps a comment now collapses the ProseMirror selection so the overlay clears and the click-to-focus ordering resolves to the highlight; unknown annotation types fall back gracefully.
|
|
50
|
+
- **Solo/Tandem mode never auto-flips on a transient error (#822)** — a failed `/api/mode` poll preserves the last-known mode instead of silently reverting to Solo.
|
|
51
|
+
- **Annotation palette remap aligned with the v7 design (Wave 7, #743)**.
|
|
52
|
+
- **Post-wave correctness fixes across 9 sites (#758)** — a cleanup pass over regressions introduced during the redesign sweep.
|
|
53
|
+
- **Kept-tab metadata refreshes on document-list reconcile** — tab titles and state no longer go stale when the open-document list re-syncs.
|
|
54
|
+
- **Unsupported-extension toast names the right extension (#808)** — `extensionAllowed` extracts the basename correctly (handling dot-in-directory and dotfile edge cases) and surfaces `.htm` properly.
|
|
55
|
+
- **`.docx` comment-extraction failures surface via notification (#696, #701)** — instead of failing silently.
|
|
56
|
+
|
|
57
|
+
### Security
|
|
58
|
+
|
|
59
|
+
- **`IntegrationConfig.url` constrained to loopback only (#753)** — accepts `http://127.0.0.1[:port][/path]` only; rejects `localhost`, IPv6 loopback `[::1]`, other `127.x.x.x` addresses, `https`, and embedded credentials (`http://user:pw@…`). Legacy `http://localhost` entries are normalized to `http://127.0.0.1` on read; any other host shape is surfaced as invalid and re-prompts the wizard.
|
|
60
|
+
- **Windows ACL hardening on `.claude.json` (#643, #795)** — a restrictive DACL is applied to the written config.
|
|
61
|
+
- **`.claude.json` backed up before overwrite (#644, #796)** — backups written under `${appDataDir}/.backups/` with mode `0o600` (POSIX) / restrictive DACL (Windows), capped at `MAX_BACKUPS=3`. Token rotation triggers a backup, so up to 3 superseded bearer tokens may persist on disk per integration; clear the backup folder manually for tighter retention.
|
|
62
|
+
- **Malformed-input matrix + shape gates + BOM strip on integration-config reads (#645, #797)** — broken-JSON backups on Windows also receive a restrictive ACL, with the read-modify-write TOCTOU window closed.
|
|
63
|
+
- **CI guard: harness components must not leak into production client bundle (Wave 2 PR 7)** — new `scripts/ci/verify-harness-stripped.mjs` runs after `npm run build` and greps `dist/client/` for known harness symbols (`UpdateAvailableHarness`, `harness-acknowledge`, etc.). Defends against a future commit accidentally importing a harness component from a production-shipping module, which would expose internal state (e.g. acknowledge buttons, version accessors) to end users. Pairs with the #660 audit confirming every settings-open path routes through the ack-clearing wrapper.
|
|
64
|
+
|
|
65
|
+
### Internal
|
|
66
|
+
|
|
67
|
+
- **Annotation-lifecycle architecture pass (ADRs 031–037, #695–#714)** — origin-tagged transaction wrappers (ADR-031), a `RefreshResult` tagged variant (ADR-032), `DocumentRegistry` extracted from `document-service` (ADR-033), named file-open entry points (ADR-034), a per-key change-observer factory + `AnnotationLifecycle` module (ADR-035), the `FormatAdapter` capability set + `LoadResult` (ADR-036), and the `LayoutModel` rune store (ADR-037). Includes observer-driven tombstoning that records on every delete.
|
|
68
|
+
- **`#477` Phase 0 spikes (#712, #726, #750)** — session-resume Spike A (GO), plugin-monitor Spike B (NO-GO), marketplace-install spike re-validated NO-GO.
|
|
69
|
+
- **Sidecar-launcher validation spike (#642, #794)** — canonicalize-before-allowlist, Windows reparse-point walk, POSIX hardlink + world-writable-parent rejection, prototyped and tested under `#[cfg(test)]` in `integrations_probe.rs`. Not yet wired into the shipped spawn path.
|
|
70
|
+
- **Test-suite audit (#786)** — ADR-027 privacy bug fix, weak-assertion sweep, ADR-031 gap fill; E2E `waitForTimeout` flake removal (#785); schema-version assertions pinned to `CURRENT_SCHEMA_VERSION` (#789).
|
|
71
|
+
- **Refactors** — `matchShortcut` helper extraction + dead `useSettingsShortcut` removal (#820), shared SSE event-consumer extraction (#822), `THEME_OPTIONS` extracted from TitleBar (#790), typed `RejectionReason` from `extract_file_arg` (#819), dead Solo/Tandem mode plumbing removed.
|
|
72
|
+
- **Tooling / CI** — `check:tokens` extended with a bundle-color blocklist (#826), `claude-review` workflow hardened + auto `/code-review` on new PRs (#830), `block-e2e-port-kill` PreToolUse hook removed (#787).
|
|
73
|
+
- **Dependencies** — `ws` 8.19.0 → 8.20.1 (#772) and `qs` bumped (npm_and_yarn group); no CVE cited.
|
|
74
|
+
- **Docs** — `RelativeRange` invariants documented (#793), roadmap and CLAUDE.md file-map refreshes (#744, #749, #751), design-notes + redesign-review archival (#774, #775), reference-doc consolidation (#852, #854, #856), and AGENTS.md tracked as the cross-agent contributor guide (#858).
|
|
75
|
+
|
|
76
|
+
## [0.12.0] - 2026-05-15
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **Editor width slider was non-monotonic and overlapped margin annotations (PR #690)** — the slider claimed 40–100% but at 100% the wrapper returned `undefined`, falling back to a fixed `68ch` reading column; dropping to 95% jumped the wrapper wider than 68ch and collided with the absolutely-positioned `MarginColumn` cards (240px column + 8px edge inset per side). The derived now always returns a concrete percentage so 100% genuinely means 100%, and when margin view is enabled the wrapper subtracts `2 × (240 + 8)px` via `calc()` so editor text never sits underneath the margin cards.
|
|
81
|
+
- **`setEditable` no longer re-fires on spurious `readOnly` re-emits** — guarded the editor against redundant `setEditable` calls when the `readOnly` prop re-emits with an unchanged value, eliminating a class of cursor-position resets during rapid Y.Doc swaps.
|
|
82
|
+
- **OS file-association review fixes round 2 (#628 follow-up)** — three additional findings from a multi-PR review pass:
|
|
83
|
+
- `restart_sidecar` now clears `SIDECAR_HEALTHY` via a new `clear_healthy_under_lock` helper that takes the `PendingOpens` mutex, mirroring `promote_healthy_and_drain`'s consumer-side flip. A bare atomic store outside the lock re-opened the same TOCTOU window the lock was introduced to close: a macOS `RunEvent::Opened` reading flag=true after `kill_sidecar` but before the clear could POST to a dying server. New `pending_opens_tests::restart_clears_flag_under_lock_so_late_producer_queues` materializes the proof.
|
|
84
|
+
- `handle_opened_urls` hoists `token_store::get_or_create_token` out of the per-URL loop. An "Open With Tandem" multi-file batch now hits the keyring once instead of N times. Mirrors `post_drained_paths`.
|
|
85
|
+
- `extract_file_arg` now documents that `is_file()` follows symlinks intentionally — the final read goes through server-side `openFileByPath` which is the authority for path validation. Prevents a future reviewer from "tightening" this into `symlink_metadata`-based checks and duplicating the server's allowlist.
|
|
86
|
+
- **OS file-association review fixes (#628 follow-up)** — six fixes addressing findings from a multi-agent review pass on the cold-start file-open path:
|
|
87
|
+
- Cold-start file is now resolved once in Tauri's `setup()` and threaded into `start_sidecar` as an explicit parameter. `restart_sidecar` passes `None`, so a Settings → Restart Sidecar (or any auto-restart) no longer re-injects `TANDEM_OPEN_FILE` and never re-opens the original launch file.
|
|
88
|
+
- Closed the `PendingOpens` drain TOCTOU window by serializing `SIDECAR_HEALTHY` access through the queue mutex on both consumer (`promote_healthy_and_drain`: flip flag + drain in one critical section) and producer (`try_queue_or_post`: check flag under same lock before push). Eliminates the load-before-push race the original drain-then-flip ordering left open.
|
|
89
|
+
- Windows NTFS Alternate Data Stream colon check now runs on the resolved absolute path (post-`cwd.join`), not just the argv candidate. Closes a gap where a relative path with a suspiciously-positioned colon could slip through.
|
|
90
|
+
- `maybeOpenStartupFile` catch narrowed to wrap `openFileByPath` only; `setActiveDocId` failures (programming-bug class) now propagate to the top-level startup error handler instead of being silently swallowed.
|
|
91
|
+
- When `TANDEM_OPEN_FILE` is set but fails to open and the welcome.md fallback fires, a distinct "Falling back to welcome.md" log line correlates the two events for support diagnostics.
|
|
92
|
+
- `token_store::get_or_create_token` errors are logged at warn level at all three call sites (no functional impact today since loopback bypasses Bearer enforcement; removes a silent-failure footgun if LAN bind is ever enabled).
|
|
93
|
+
- **Markdown serializer escape noise on `.md` saves (#605, v1.0 blocker)** — every `.md` file Tandem auto-saved was silently rewritten with backslash-escape noise by `remark-stringify`'s default `unsafe` table (`[anchor]` → `\[anchor]`, `foo_bar` → `foo\_bar`, etc.). The serializer in `src/server/file-io/markdown.ts` now overrides the `text` handler to call `state.safe()` (preserving block-context escapes for line-leading `# `, `- `, `> `, fence runs, table pipes, setext underlines) and then selectively un-escape four intra-text classes: `\[label]` when `label` is not a `definition` identifier in the same tree (parse-aware via a `unist-util-visit` pre-pass — guards against the collapsed-reference-link regression class; label is normalized per CommonMark; negative lookahead also rejects an immediately following `[` to block adjacent-bracket reference-link injection; label character class excludes `\` to prevent O(n²) backtracking on adversarial input like `\[\[\[\[\[…`), `\_` strictly between word chars (CommonMark §6.2 intra-word flanking rule), standalone `` \` ``, and `\~` not followed by another `~` (GFM strikethrough needs `~~`). GFM autolink-literal `@`/`.`/`:` and table `|` escapes still flow through `safe()` untouched. `CHANGELOG.md` was re-serialized through the fixed code as a one-time data cleanup. The `readOnly: true` workaround on the upgrade auto-open path (PR #603) is retained as defense-in-depth.
|
|
94
|
+
|
|
95
|
+
### Added
|
|
96
|
+
|
|
97
|
+
- **15 keyboard shortcuts (#626)** — grouped by area:
|
|
98
|
+
|
|
99
|
+
- **Tabs:** `Ctrl+W` close active tab (records to in-memory LIFO), `Ctrl+Alt+T` reopen most-recently-closed tab, `Ctrl+1`..`Ctrl+9` jump to tab by index, `Ctrl+O` open file dialog.
|
|
100
|
+
- **Find / outline:** `Ctrl+F` focus outline search when outline panel is visible, otherwise open find bar (doc scope); `Ctrl+Shift+F` open find bar pre-scoped to "Open tabs"; `Ctrl+G` / `Ctrl+Shift+G` find next/previous (falls back to opening find bar when no active query).
|
|
101
|
+
- **Panels / chrome:** `Ctrl+\` toggle left panel, `Ctrl+Shift+\` toggle right panel, `Ctrl+Shift+M` toggle solo/tandem mode.
|
|
102
|
+
- **Annotations:** `Alt+]` / `Alt+[` next/previous annotation; `Ctrl+Enter` / `Ctrl+Shift+Enter` accept/dismiss focused annotation; `Ctrl+Alt+M` open the comment popup on current selection; `Ctrl+Alt+A` toggle authorship colors.
|
|
103
|
+
- **Editor polish:** `Alt+L` select containing block (paragraph / heading / list item).
|
|
104
|
+
|
|
105
|
+
All letter shortcuts use `KeyboardEvent.code` (e.g. `KeyT`, `KeyM`) rather than `e.key` so they remain layout-independent (Dvorak / AZERTY) and fire correctly on macOS when Option is held — `Option+letter` produces alt characters (`†`, `µ`, `¬`, `å`) that don't match the unmodified letter `e.key`. Digits and Backslash already used `e.code`; Enter is layout-stable and remains on `e.key`. `Ctrl+M` vs `Ctrl+Shift+M` discrimination is now explicit (`!e.shiftKey` / `e.shiftKey`).
|
|
106
|
+
|
|
107
|
+
`reopenClosedTab` now checks `response.ok` so server-side 4xx/5xx no longer silently drop the popped record (fetch only rejects on network failures). Failure restores the record onto the stack and surfaces a toast with the basename so the user can retry; previously a failed reopen was logged to devtools only and the record was lost.
|
|
108
|
+
|
|
109
|
+
`Ctrl+Alt+M` distinguishes its preconditions: no selection → "Select text to comment"; read-only doc → "Document is read-only"; palette/find open → silent (the user is in a different UI context); selection toolbar setting off → "Enable selection toolbar in Settings to comment via keyboard". A single toast string would have misfired in three out of four cases.
|
|
110
|
+
|
|
111
|
+
`useNotifications` now exposes a `push()` method so client-originated UI feedback (the toasts above) can be surfaced through the same toast container as server-pushed notifications, instead of `console.warn`-ing into devtools the user never opens.
|
|
112
|
+
|
|
113
|
+
### Fixed
|
|
114
|
+
|
|
115
|
+
- **`reloadFromDisk` two-write crash window (#622, PR #635)** — when the file watcher detects an external edit, `reloadFromDisk` previously ran `refreshAllRanges` and the relocation pass as two separate `MCP_ORIGIN` transactions; if the server was killed between them, durable annotation state could be left at partially-refreshed ranges. Both passes now merge into a single transaction via a new `skipTransact` parameter on `refreshAllRanges`, closing the crash window. Pre-existing in master; surfaced by audit v2.
|
|
116
|
+
- **Codebase leanness audit v2 (PR #621)** — eight-step audit (`docs/audit-v2.md`) covering dead code, dependency bloat, over-engineering, wrong-tool-for-the-job, and stale docs. Validated by four domain reviewers (annotation-model, crdt, security, svelte-migration) before any deletion. Outcomes:
|
|
117
|
+
- **A1:** `reloadFromDisk` (file-watcher reload path in `mcp/file-opener.ts`) — first transaction (content repopulate + awareness clear) tagged `FILE_SYNC_ORIGIN` so the durable-annotation sync observer skips re-persisting state just loaded from disk. The second transaction (textSnapshot-driven relocation pass) stays `MCP_ORIGIN` so its writes persist (caught during the post-merge CRDT review).
|
|
118
|
+
- **A2 + A2b:** Tutorial note now uses `author: "user"` (per ADR-027 — notes are user-private). `useTutorial.svelte.ts` updated to exclude tutorial-seeded annotations from its user-action detection so the step-1 → step-2 advance still gates on a real user-created annotation.
|
|
119
|
+
- **A3:** Six `INVALID_RANGE` error codes in `mcp/annotations.ts` replaced with `NOT_FOUND` / `ANNOTATION_RESOLVED` / `INVALID_ARGUMENT`. `ToolErrorCodeSchema` extended to document the codes the code already uses.
|
|
120
|
+
- **Tab drag-to-reorder unblocked in Tauri + browser, then hardened against mid-drag races (PR #625)** — drag-to-reorder document tabs was wired end-to-end but didn't actually move tabs. Two independent root causes plus two follow-up refinements landed together: (1) **Tauri:** WebView2 on Windows defaults to `dragDropEnabled: true`, which routes all drag-drop through Tauri's native handler and blocks in-page HTML5 DnD entirely — flipped to `false` in `tauri.conf.json` (`useFileDrop.svelte.ts` already uses HTML5 `dataTransfer.files`, so OS file drops still work). (2) **Browser + Tauri:** an `$effect(() => { void tabs.length; clearDragState(); })` in `DocumentTabs.svelte` cleared `draggedId` / `dropTarget` whenever the `tabs` prop reference changed, so any upstream Yjs awareness / settings ping that re-derived `orderedTabs` during a drag nulled the drag state mid-flight; removed, and `handleDrop` now prefers closure-captured `draggedId` over `dataTransfer.getData("text/plain")` as defense-in-depth. (3) **Refinements:** a narrower replacement `$effect` clears drag state only when the dragged or target id actually disappears from `tabs` (mid-drag tab close — `dragend` doesn't fire reliably when the source element leaves the DOM), and `handleDragOver` now gates `e.preventDefault()` on `draggedId` being set so foreign drags (file from Explorer, now reachable in WebView thanks to `dragDropEnabled: false`) get the OS no-drop cursor instead of being silently swallowed. New `tests/client/DocumentTabs.svelte.test.ts` pins the original regression (case B fails red with the deleted effect re-added); the E2E `mouse drag reorders tabs` spec dispatches real HTML5 `DragEvent`s via `page.evaluate` because Playwright's `locator.dragTo()` synthesizes mouse events only and never fires HTML5 drag events.
|
|
121
|
+
- **Large markdown documents no longer freeze the editor on open (PR #612, closes #609)** — `loadContentIntoDoc` now batches the entire `mdastToYDoc` populate into a single `MCP_ORIGIN` transaction. Previously each `fragment.insert` / `xmlText.insert` fired its own CRDT update; on a ~4500-token document this flooded `y-prosemirror` with thousands of tiny updates, saturated the event loop, and tripped Hocuspocus disconnects. The shared `populateDocFromContent` helper now batches both the disk-open path (`openFileByPath`) and the upload path (`openFileFromContent`), so drag-dropped large markdown files no longer freeze either. Matches the existing `clearAndReload` / `reloadFromDisk` batching shape.
|
|
122
|
+
- **`.docx` files with malformed Word comments no longer abort the entire document open (PR #612)** — `injectCommentsAsAnnotations` runs inside the same flatten-into-outer transact as `htmlToYDoc`, so a single bad comment range used to throw out of the whole populate. The inject call is now contained per-comment with partial-write rollback (Yjs does not roll back inner-transact writes on throw, so we snapshot annotation keys before the inject and undo any that landed before the failure). Comment-extraction failures additionally surface as a warning notification instead of disappearing into the server log. Comment-injection failures (rare unhandled exceptions during annotation write) now surface as a warning notification too — symmetric with the extract-failure path, so the user always sees feedback when imported comments go missing.
|
|
123
|
+
- **Populate failures no longer poison the Y.Doc cache (PR #612)** — `populateDocFromContent` clears partial fragment + annotation state in a fresh top-level `MCP_ORIGIN` transact before rethrowing. Previously, a populate throw would leave the Hocuspocus-cached Y.Doc with half-applied content, and a retry would silently inherit the corrupted state.
|
|
124
|
+
- **Coalesce annotation decoration rebuilds with rAF (closes #610)** — `src/client/editor/extensions/annotation.ts` rebuilt the entire `DecorationSet` synchronously on every Y.Map('annotations') observer fire. On initial sync of a document with hundreds of annotations (force-reload, session restore, docx import) the observer can fire dozens of times in one tick — each one O(n) over every annotation — making the burst O(n²). The view-side observer now coalesces fires through `requestAnimationFrame`: one rebuild per frame, regardless of how many Y.Map mutations land in the burst. Adds a 500ms post-sync settle rebuild mirroring the `authorship.ts:223` pattern so initial-sync annotations land even if the observer attached before y-prosemirror populated the doc.
|
|
125
|
+
|
|
126
|
+
### Internal
|
|
127
|
+
|
|
128
|
+
- **Audit tooling (PR #621)** — added `knip` (devDep) + `audit:origins` + `audit:ymap-keys` scripts under `npm run`. `audit:origins` rewritten with the TypeScript compiler API after the initial regex+line-window heuristic produced 13/13 false positives.
|
|
129
|
+
- **Five Y.Map raw-key fixes (PR #621)** — `channel-routes.ts`, `document.ts`, `file-opener.ts` now use `Y_MAP_CLAUDE` / `Y_MAP_READ_ONLY` constants instead of raw string literals (Critical Rule #1 violations; functionally equivalent — same string values).
|
|
130
|
+
- **Dead-code sweep (PR #621)** — removed 13 React-migration stub files (`hooks/use*.ts` left as `export {};` after Svelte 5 port), 5 server exports (`killClaude`, `getHocuspocus`, `getClaudeStatus`, `AwarenessState`, `shutdownForTests` alias + test migration), 7 unused shared constants + 2 unused types, 4 unused client exports (`unregisterAction` / `unregisterByPrefix` / `getActions` from action registry; `createEditorFont`), 3 unused color exports (`errorStateColors` / `successStateColors` / `suggestionStateColors`), and 2 dependencies (`@tiptap/extension-unique-id`, `concurrently`). 11 commits, ~250 LOC removed, all CI green.
|
|
131
|
+
- **Centralize Tandem HTTP API paths into shared constants (closes #283)** — every `/api/*` path (registration + every client/CLI/channel/monitor fetch) now flows through `src/shared/api-paths.ts`. `API_BASE` in `src/client/utils/fileUpload.ts` no longer carries the `/api` suffix; clients build URLs as `${API_BASE}${API_FOO}`. Renaming a route now touches one file instead of N. Source-level coverage tests in `tests/channel/run-timeouts.test.ts` and `tests/monitor/runtime-fetches.test.ts` accept either the literal path or the resolved `API_*` constant.
|
|
132
|
+
- **Define `ChannelErrorCodeSchema` enum for `/api/channel-error` payloads (closes #284)** — channel-shim and monitor failure codes (`CHANNEL_CONNECT_FAILED`, `MONITOR_CONNECT_FAILED`) are now z.enum constants in `src/shared/types.ts` instead of free-form strings. The server route handler validates the incoming `error` field and returns 400 on unknown codes (still logs the rejected value so the diagnostic trail survives). Mirrors the existing `ToolErrorCodeSchema` pattern.
|
|
133
|
+
- **Extract shared `onOutsideEvent` dismiss helper (closes #589)** — `Toolbar.svelte` (scroll-based dismiss) and `HighlightColorPicker.svelte` (mousedown-based dismiss) both rolled their own document-listener + `contains(event.target)` guard. Consolidates into `src/client/utils/dismiss-outside.ts`. Capture-phase parity preserved at each call site.
|
|
134
|
+
- **Route `clearAndReload` through `populateDocFromContent` (closes #611)** — the force-reload path was the last remaining inline duplication of `loadDocx` + `extractDocxComments` + `htmlToYDoc` + `injectCommentsAsAnnotations`. Routing it through the shared helper means the rollback containment, partial-write cleanup, and comment-extract/inject notification UX that #612 added to the normal-open path now also apply on force-reload. Net delete: ~50 LOC, zero functional regressions.
|
|
135
|
+
- **Windows code signing via Azure Trusted Signing + OIDC (PR #685, closes #428)** — replaces self-signed Windows builds with Azure Trusted Signing (Basic tier, Individual Validation). CI authenticates via OIDC federation through a GitHub Actions environment as the subject anchor (no long-lived `AZURE_CLIENT_SECRET`); signing is restricted to `refs/tags/v*` refs by both a workflow-level pwsh guard and a `release`-environment deployment-tag rule. See ADR-030 for full decision rationale, operator setup steps, and rollback procedure.
|
|
136
|
+
|
|
137
|
+
### Changed
|
|
138
|
+
|
|
139
|
+
- **Browser distribution deprecated, CORS allowlist narrowed (PR #637, part of #477)** — Tauri desktop is the primary form factor; the npm-global `tandem start` path is being retired. `src/server/open-browser.ts` deleted and the browser auto-open branch removed from `mcp/server.ts`. `tandem start` now emits a deprecation warning and no longer sets `TANDEM_OPEN_BROWSER=1`. The overloaded env var is renamed to `TANDEM_TAURI_SIDECAR` at the three remaining sites (`src/server/index.ts`, `src-tauri/src/lib.rs`, `scripts/ci/stdio-smoke.mjs`). CORS / DNS-rebinding allowlists were narrowed in lockstep across HTTP (`isHostAllowed`, `LOCALHOST_ORIGIN_RE`) and WebSocket (Hocuspocus `onConnect`): the bare `localhost` hostname is rejected — only `127.0.0.1` and `tauri.localhost` are accepted. Tauri sends `Host: tauri.localhost`; the sidecar uses `127.0.0.1`. **Dev workflow note:** local dev must now be accessed at `http://127.0.0.1:5173`, not `http://localhost:5173`; Vite is pinned to `server.host: "127.0.0.1"` so the page origin is unambiguous. Client fetches (`API_BASE` in `fileUpload.ts`, notify-stream EventSource, `/api/close`, `/api/chat`) and Playwright config + E2E spec gotos / fetch helpers all migrated to `127.0.0.1` so the in-page origin and Node `Host` header both pass the narrowed allowlist.
|
|
140
|
+
- **Host allowlist audit completion (PR #686, finishes #477 PR 2)** — PR #637 narrowed the server's `isHostAllowed` allowlist to `127.0.0.1` + `tauri.localhost`, but the original audit grep was port-anchored (`localhost:347[89]`) and missed all template-literal forms (`localhost:${port}`). #686 sweeps the remaining surfaces: Rust supervisor URL constants (`HEALTH_URL` / `SETUP_URL` / `OPEN_URL` in `src-tauri/src/lib.rs`), Tauri devUrl + CSP `connect-src`, client Hocuspocus URL, CLI fan-out (`resolveTandemUrl()` default, `mcp setup` URL), server-internal callers (channel spawn, startup banner), OAuth metadata (`/.well-known/oauth-protected-resource` `resource` + `authorization_servers`), vite proxy target, distributed templates (`.mcp.json.example`, `.claude-plugin/plugin.json`, `.env.example`), and dev scripts. The visible symptom was `npm run dev:tauri` reporting "Server failed to start after 3 restart attempts" because the Rust supervisor's `HEALTH_URL` got 403 from the narrowed gate for 15s. A new Rust `#[cfg(test)] mod url_constants_tests` regression-guards the three supervisor URLs against drift back to `localhost`.
|
|
141
|
+
|
|
142
|
+
### Deferred
|
|
143
|
+
|
|
144
|
+
<!-- Populated as PRs land. -->
|
|
9
145
|
|
|
10
146
|
## [0.11.2] - 2026-05-13
|
|
11
147
|
|
|
@@ -37,7 +173,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
37
173
|
|
|
38
174
|
- **Token-violation hook promoted to blocking** — `check-token-violation.sh` PostToolUse hook now exits 2 (blocking) with `continueOnBlock` framing redirected to stderr so Claude Code sees actionable `file:line` details from the semantic-token scanner instead of silently warning. Hook framing aligned with `block-no-verify.sh` and `block-e2e-port-kill.sh`.
|
|
39
175
|
|
|
40
|
-
##
|
|
176
|
+
## [0.11.0] - 2026-05-11
|
|
41
177
|
|
|
42
178
|
### Added
|
|
43
179
|
|
|
@@ -60,7 +196,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
60
196
|
|
|
61
197
|
- **Authorship toggle moved to toolbar (closes #587)** — The "Show Authorship" toggle moved from the Settings popover Accessibility section to the main toolbar right cluster for faster access. New testid: `toolbar-authorship-toggle`.
|
|
62
198
|
- **Settings dialog responsive breakpoint (closes #515)** — stacked single-column layout at ≤640px; sidebar capped at 45% of dialog height with vertical scroll; four E2E tests cover nav reachability, Tab cycling, focus-after-resize, and content width.
|
|
63
|
-
- **Redesign bundle checked into
|
|
199
|
+
- **Redesign bundle checked into ****docs/redesign-bundle/**** (#521)** — captured the current handoff, HTML previews, CSS, and JSX surfaces used for the app-shell visual pass so follow-on UI work is grounded in a repo-local artifact instead of a transient design URL.
|
|
64
200
|
- **Regression coverage added for the remaining app-shell contracts (#521)** — new Playwright and Vitest checks now cover connection banners, reply threads, panel resize, layout switching, onboarding, readonly DOCX review, and apply-changes behavior.
|
|
65
201
|
- **Keyboard navigation E2E tests for floating selection toolbar (closes #516)** — Tab/Shift+Tab focus traversal, Enter activation, and Escape-to-editor focus return are now covered by four Playwright tests documenting APG-compliant behavior for transient contextual toolbars.
|
|
66
202
|
- **Redesign final QA suite (closes #522)** — Playwright tests covering viewport layouts (600/1280/1920px), `prefers-reduced-motion`, forced-colors/high-contrast mode, dark/light color scheme switching, and keyboard Tab-order reachability.
|
|
@@ -74,6 +210,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
74
210
|
- **Tauri shell**: reload shortcuts (F5, Ctrl+F5, Shift+F5, Ctrl+R, Ctrl+Shift+R) are now blocked in the desktop app to prevent accidental navigation away from the editor; DevTools, Find, Print, and right-click context menu are preserved (#541)
|
|
75
211
|
- **Semantic token foundation expanded for redesign wave 2 (#521)** — added radius, font-size, shadow, z-index, editor-font-size, and highlight-color token families in `index.html`, plus checker rules that now flag raw `border-radius: <n>px` and inline `box-shadow: ... rgba(...)` in `src/client/`.
|
|
76
212
|
- **Read-only/info surfaces now use the shared info token family (#521)** — `ReviewOnlyBanner`, `ConnectionBanner`, `ToastContainer`, `StatusBar`, and related chrome now consume the shared token scales instead of hardcoded radius/text/shadow values.
|
|
213
|
+
- **Monitor and channel honor `CLAUDE_PLUGIN_OPTION_SERVER_URL`** — `resolveTandemUrl()` now checks the `CLAUDE_PLUGIN_OPTION_SERVER_URL` environment variable (exported by Claude Code's plugin host from `plugin.json` `userConfig`) before falling back to `TANDEM_URL` and the localhost default. Both the monitor (`src/monitor/index.ts`) and channel shim (`src/channel/run.ts`) benefit automatically. No change for existing installs that don't use `userConfig`. (Drafted as a standalone v0.10.1 patch that was never tagged; shipped as part of v0.11.0.)
|
|
214
|
+
- **Monitor and channel honor `CLAUDE_PLUGIN_OPTION_AUTH_TOKEN`** — new `resolveAuthToken()` function in `src/shared/cli-runtime.ts` mirrors `resolveTandemUrl()`. Precedence: `CLAUDE_PLUGIN_OPTION_AUTH_TOKEN` → `TANDEM_AUTH_TOKEN`. `authFetch` uses it automatically, so all stdio subcommands gain the new lookup without caller changes.
|
|
77
215
|
|
|
78
216
|
### Tests
|
|
79
217
|
|
|
@@ -81,7 +219,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
81
219
|
|
|
82
220
|
### Removed
|
|
83
221
|
|
|
84
|
-
- **ReviewSummary
|
|
222
|
+
- **ReviewSummary**\*\* overlay removed with review mode already gone (#521)\*\* — the dead component and `App.svelte` mount path are deleted rather than carried forward as unreachable redesign debt.
|
|
85
223
|
|
|
86
224
|
### Fixed
|
|
87
225
|
|
|
@@ -90,28 +228,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
90
228
|
- **Browser path: no light-flash on first paint for dark-mode users** — an inline pre-mount script in `index.html` reads the persisted theme preference (falling back to `matchMedia`) and sets `data-theme` on `<html>` before Svelte mounts, matching the behaviour the Tauri shell already provided via `window.__TANDEM_INITIAL_THEME__` (#551 partial — FOUC mitigated; matchMedia source-of-truth fix deferred to #477)
|
|
91
229
|
- **ErrorBoundary now offers in-place recovery before falling back to a full reload (#507)** — the app-root `<svelte:boundary>` re-renders children via `reset()` on a "Try to recover" click, capped at three attempts before forcing the user to reload. The budget resets after each successful recovery so an unrelated subsequent error gets a fresh three attempts. Failed-state surface uses `--tandem-error-bg`/`-border`/`-fg-strong` tokens (was neutral) and re-announces via `role="alert"` on each fresh failure.
|
|
92
230
|
- **Toolbar**: HighlightColorPicker border now uses `--tandem-border` token, correctly adapting to light/dark theme switching (#536)
|
|
93
|
-
- **Theme system: Tauri shell now reads Windows app-mode preference (****AppsUseLightTheme****) for
|
|
231
|
+
- **Theme system: Tauri shell now reads Windows app-mode preference (****AppsUseLightTheme****) for ****theme: "system"**** instead of taskbar color mode (closes #535)** — `get_app_theme` Rust command reads `WebviewWindow::theme()`, which maps to `HKCU\...\Personalize\AppsUseLightTheme`. Initial theme is seeded before Svelte mounts; `useTauriTheme.svelte.ts` subscribes to `onThemeChanged` and polls every 3s while focused. `matchMedia` subscription is skipped in Tauri to prevent race conditions.
|
|
94
232
|
- **Tauri shell: live OS app-mode flips now retheme without restart** — `systemTheme()` reads the live `tauriTheme.current` reactive store (updated by the Tauri theme bridge) instead of a startup-only snapshot; `applyTheme()` in `useTheme.svelte.ts` subscribes reactively so `<html data-theme>` updates immediately when the user switches Windows between light and dark app mode (Codex P1 follow-up to #535).
|
|
95
233
|
- **Dark annotation highlight colors** — `--tandem-highlight-yellow/green/blue/pink` now have dark-adapted overrides in `[data-theme="dark"]`; the light `rgba(255, 235, 59, 0.3)`-style values were washed out against dark surfaces.
|
|
96
234
|
- **Forced-colors fallbacks for background-only state surfaces (closes #311)** — StatusBar status dots, toast badge, ModeToggle active button, BulkActions confirm button, AnnotationCard type-badge and Private pill now have `border`/`outline` fallbacks in `@media (forced-colors: active)`.
|
|
97
235
|
|
|
98
|
-
##
|
|
99
|
-
|
|
100
|
-
Plugin URL and auth resolution for custom-port and network-remote setups.
|
|
101
|
-
|
|
102
|
-
### Changed
|
|
103
|
-
|
|
104
|
-
- **Monitor and channel honor ****CLAUDE\_PLUGIN\_OPTION\_SERVER\_URL** — `resolveTandemUrl()` now checks the `CLAUDE_PLUGIN_OPTION_SERVER_URL` environment variable (exported by Claude Code's plugin host from `plugin.json` `userConfig`) before falling back to `TANDEM_URL` and the localhost default. Both the monitor (`src/monitor/index.ts`) and channel shim (`src/channel/run.ts`) benefit automatically. No change for existing installs that don't use `userConfig`.
|
|
105
|
-
- **Monitor and channel honor ****CLAUDE\_PLUGIN\_OPTION\_AUTH\_TOKEN** — new `resolveAuthToken()` function in `src/shared/cli-runtime.ts` mirrors `resolveTandemUrl()`. Precedence: `CLAUDE_PLUGIN_OPTION_AUTH_TOKEN` → `TANDEM_AUTH_TOKEN`. `authFetch` uses it automatically, so all stdio subcommands gain the new lookup without caller changes.
|
|
106
|
-
|
|
107
|
-
## \[0.10.0] - 2026-05-03
|
|
236
|
+
## [0.10.0] - 2026-05-03
|
|
108
237
|
|
|
109
238
|
Complete React → Svelte 5 migration. All 39 client `.tsx` files have been replaced with Svelte 5 rune-based equivalents; `react`, `react-dom`, and `@tiptap/react` are no longer in the bundle. Includes a review-mode correctness fix, accessibility improvements, and follow-on Codex security hardening.
|
|
110
239
|
|
|
111
240
|
### Removed
|
|
112
241
|
|
|
113
|
-
- **react
|
|
114
|
-
- **
|
|
242
|
+
- **react**\*\*, ****react-dom****, ****@tiptap/react**** dropped (#472, #508)\*\* — the React adapter layer is gone. The editor integrates directly with `@tiptap/core` via Svelte 5 components. Bundle size and startup time both decrease.
|
|
243
|
+
- **tandem_suggest**\*\*, ****tandem_flag****, ****tandem_highlight**** hard-removed\*\* — stub tools deprecated in v0.9.0 (ADR-027) are now fully removed. MCP tool count: 28 → 25.
|
|
115
244
|
|
|
116
245
|
### Changed
|
|
117
246
|
|
|
@@ -121,11 +250,11 @@ Complete React → Svelte 5 migration. All 39 client `.tsx` files have been repl
|
|
|
121
250
|
### Fixed
|
|
122
251
|
|
|
123
252
|
- **Review mode incorrectly treated private notes as review targets (#512, #523)** — Tab/Y/N keyboard navigation, "Accept All" / "Dismiss All" bulk actions, the "Review Complete" overlay trigger, tally counts, and the chat tab badge now all exclude `type: "note"` annotations. Notes remain visible as cards in the side panel. Word-imported comments (`author: "import"`) continue to be review targets.
|
|
124
|
-
- **Note privacy
|
|
253
|
+
- **Note privacy — ****tandem_getAnnotations**** and channel events never surface notes to Claude** — `type: "note"` entries are filtered from MCP tool responses and SSE channel events (Codex security review).
|
|
125
254
|
- **Y.Map key strings enforced via constants** — raw string literals for Y.Map keys eliminated across the codebase; all access goes through `Y_MAP_ANNOTATIONS`, `Y_MAP_AWARENESS`, etc. from `shared/constants.ts` (Codex security review).
|
|
126
255
|
- **Chat message XSS hardening** — link rendering in the chat panel now enforces a protocol allowlist (`https:`, `http:`, `mailto:`), blocking `javascript:` and other unsafe schemes (Codex security review).
|
|
127
|
-
- **annotation:edited
|
|
128
|
-
- **svelte-check --fail-on-warnings
|
|
256
|
+
- **annotation:edited**\*\* channel event deduplication\*\* — rapid successive edits no longer emit duplicate events to the channel (Codex security review).
|
|
257
|
+
- **svelte-check --fail-on-warnings**\*\* now gates the build\*\* — 26 pre-existing Svelte type warnings cleared; CI enforces zero-warning policy going forward.
|
|
129
258
|
|
|
130
259
|
### Added
|
|
131
260
|
|
|
@@ -134,7 +263,7 @@ Complete React → Svelte 5 migration. All 39 client `.tsx` files have been repl
|
|
|
134
263
|
- **Form label associations (#511, #524)** — AnnotationEditForm inputs are now properly associated with their `<label>` elements.
|
|
135
264
|
- **AnnotationCard role corrected (#511, #524)** — changed from `role="button"` (nested-button violation) to `role="listitem"`.
|
|
136
265
|
|
|
137
|
-
##
|
|
266
|
+
## [0.9.1] - 2026-05-01
|
|
138
267
|
|
|
139
268
|
Hotfix patch bundling ADR-027 surface cleanup and file-I/O correctness fixes before the v0.10.0 Svelte conversion. All changes are patch-class; no MCP API changes.
|
|
140
269
|
|
|
@@ -152,32 +281,32 @@ Hotfix patch bundling ADR-027 surface cleanup and file-I/O correctness fixes bef
|
|
|
152
281
|
|
|
153
282
|
- **E2E toolbar regression guard (#484)** — Playwright coverage for the redesigned toolbar (ADR-027 note/comment/highlight flow), including a regression guard for the note button empty-annotation bug (#480).
|
|
154
283
|
|
|
155
|
-
##
|
|
284
|
+
## [0.9.0] - 2026-04-28
|
|
156
285
|
|
|
157
286
|
### Breaking Changes (MCP)
|
|
158
287
|
|
|
159
288
|
This is the last breaking-change window before semver lock. MCP tool count: 31 → 28.
|
|
160
289
|
|
|
161
|
-
- **
|
|
162
|
-
- **
|
|
163
|
-
- **
|
|
164
|
-
- **
|
|
165
|
-
- **
|
|
166
|
-
- **
|
|
167
|
-
- **Annotation
|
|
290
|
+
- **tandem_suggest**\*\* deprecated (#259)\*\* — returns a structured error stub pointing to `tandem_comment` with `suggestedText`. Hard-remove in v0.10.0.
|
|
291
|
+
- **tandem_getContent**\*\* removed (#259)\*\* — superseded by `tandem_getTextContent`.
|
|
292
|
+
- **tandem_getSelections**\*\* removed (#259)\*\* — superseded by `tandem_checkInbox`.
|
|
293
|
+
- **tandem_setStatus**\*\* merged into ****tandem_status**** (#259)\*\* — `tandem_status` now accepts optional write params (`text`, `focusParagraph`, `focusOffset`). When params are present it writes to awareness; when absent it reads.
|
|
294
|
+
- **tandem_flag**\*\* deprecated (ADR-027, #473)\*\* — returns a `DEPRECATED` error stub. Use `tandem_comment` instead. Hard-remove in v0.10.0.
|
|
295
|
+
- **tandem_highlight**\*\* deprecated (ADR-027, #473)\*\* — returns a `DEPRECATED` error stub. Highlights are user-only. Hard-remove in v0.10.0.
|
|
296
|
+
- **Annotation ****directedAt**** field removed (ADR-027, #473)** — silently ignored on input; stripped from on-disk records via `sanitizeAnnotation` and the `normalizeAnnotation` fast path on read.
|
|
168
297
|
|
|
169
298
|
### Added
|
|
170
299
|
|
|
171
|
-
- **/api/info
|
|
300
|
+
- **/api/info**\*\* endpoint (#441)\*\* — returns app version, MCP SDK version, tool count, data directory, and platform. Serves the Settings panel About footer.
|
|
172
301
|
- **Tabbed-left layout variant (#445)** — new `"tabbed-left"` layout mode places the side panel on the left and editor on the right. Three layout modes total: `tabbed`, `tabbed-left`, `three-panel`.
|
|
173
302
|
- **App version in Settings (#435)** — `useAppInfo` hook fetches `/api/info` and displays version + MCP SDK version in the Settings popover footer.
|
|
174
303
|
- **View Changelog button (#437)** — Settings panel button opens `CHANGELOG.md` as a read-only document tab via `POST /api/open` with `readOnly: true`.
|
|
175
|
-
- **Authorship
|
|
304
|
+
- **Authorship ****data-tandem-author**** attributes (#443)** — authorship decorations switched from CSS classes (`.tandem-authorship--user`) to data attributes (`[data-tandem-author="user"]`), per ADR-026. Enables future attribute-based styling without class proliferation.
|
|
176
305
|
- **Schema foundations (#440, #442, #444, #450)** — `heldInSolo` field on `AnnotationBase`; 7 new `TandemSettings` fields (`accentHue`, `editorFont`, `density`, `defaultMode`, `highContrast`, `annotationPatterns`, `selectionToolbar`); `showAuthorship` default flipped to `true`; editor width minimum lowered from 50% to 40%.
|
|
177
306
|
- **Highlight palette migration (#450)** — palette switched from 5 colors to 4 (yellow/green/blue/pink). `LEGACY_COLOR_MAP` migrates `red` → `yellow`, `purple` → `blue` on annotation load.
|
|
178
307
|
- **CI stdio smoke test (#341)** — GitHub Actions step validates the Cowork stdio bridge (`scripts/ci/stdio-smoke.mjs`) on every push.
|
|
179
|
-
- **\
|
|
180
|
-
- **
|
|
308
|
+
- **\__MCP_SDK_VERSION\_\_\*\*\*\* build-time injection** — tsup reads the real SDK version from the package root (not the CJS type marker) and injects it at build time.
|
|
309
|
+
- **tandem_getAnnotations**\*\* ****includeImports**** opt-in (ADR-027, #473)\*\* — accepts `includeImports: true` to surface `author: "import"` reviewer comments imported from `.docx` files. Default still excludes them so the user triages first. When imports are filtered out, the response includes `importsExcluded: N` so Claude can prompt the user to opt in.
|
|
181
310
|
- **Deprecated-tool user notifications (ADR-027, #473)** — `tandem_highlight`, `tandem_flag`, and `tandem_suggest` stubs now `pushNotification` a warning toast in addition to returning the `DEPRECATED` mcpError, so the user sees what Claude tried.
|
|
182
311
|
|
|
183
312
|
### Fixed
|
|
@@ -190,7 +319,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
190
319
|
- **Event-bridge error handling** — uncaught errors in SSE delivery no longer crash the event loop.
|
|
191
320
|
- **MCP SDK version resolution** — `require("@modelcontextprotocol/sdk/package.json")` resolves to `dist/cjs/package.json` (a CJS type marker without `version`); build now walks back past `dist/` to find the real version.
|
|
192
321
|
- **Silent-migration logging (ADR-027, #473)** — `parseAnnotationDoc`, `migrateToV1`, and the `directedAt` strip fast path now log via the new `migration-log.ts` module (once per `${docHash}:${kind}`) instead of silently rewriting v0 records. Restores forensic trail for the v0→v1 transition.
|
|
193
|
-
- **normalizeReply
|
|
322
|
+
- **normalizeReply**\*\* validation (#473)\*\* — replies are now Zod-validated before being merged; malformed entries are dropped + logged instead of poisoning the envelope.
|
|
194
323
|
|
|
195
324
|
### Changed
|
|
196
325
|
|
|
@@ -208,13 +337,13 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
208
337
|
- `file-opener` read-only mode support for changelog viewing.
|
|
209
338
|
- 900+ lines of new test coverage: authorship decorations, annotation decorations, panel layout, app info hook, settings fields, schema migration, info route, document edit edge cases.
|
|
210
339
|
|
|
211
|
-
##
|
|
340
|
+
## [0.8.0] - 2026-04-26
|
|
212
341
|
|
|
213
342
|
### Added
|
|
214
343
|
|
|
215
344
|
- **NSIS pre-install sidecar kill (#434)** — the NSIS installer now kills the running `node-sidecar.exe` process before file replacement, preventing "Error opening file for writing" failures during upgrade installs. Uses `nsis_tauri_utils::KillProcessCurrentUser` for user-scoped process termination. Tauri's built-in `CheckIfAppIsRunning` already handles the main binary.
|
|
216
345
|
- **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.
|
|
217
|
-
- **--tandem-suggestion
|
|
346
|
+
- **--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.
|
|
218
347
|
- **Annotation drop count surfacing (#351)** — `normalizeAnnotation` now returns drop counts in snapshot metadata so callers can detect lossy session migrations.
|
|
219
348
|
- **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.
|
|
220
349
|
- **Persistent annotation undo (#415)** — undo state survives panel switches and scrolling; persists until page reload instead of clearing on the next render cycle.
|
|
@@ -249,23 +378,23 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
249
378
|
- `@xmldom/xmldom` dependency bump (#390).
|
|
250
379
|
- CI: typecheck/lint/test gates on all PR base branches.
|
|
251
380
|
|
|
252
|
-
##
|
|
381
|
+
## [0.7.1] - 2026-04-20
|
|
253
382
|
|
|
254
383
|
### Fixed
|
|
255
384
|
|
|
256
385
|
- **MSIX Claude Desktop detection** — `tandem setup` now detects Claude Desktop installed via MSIX (Microsoft Store) and generates stdio MCP entries for it (#372)
|
|
257
386
|
|
|
258
|
-
##
|
|
387
|
+
## [0.7.0] - 2026-04-20
|
|
259
388
|
|
|
260
389
|
### Added
|
|
261
390
|
|
|
262
391
|
- **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.
|
|
263
392
|
- **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.
|
|
264
|
-
- **
|
|
393
|
+
- **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.
|
|
265
394
|
- **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).
|
|
266
395
|
- **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.
|
|
267
396
|
- **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.
|
|
268
|
-
- **/health
|
|
397
|
+
- **/health**\*\* session-presence guard\*\* — `hasSession` is omitted from `/health` responses on non-loopback requests, preventing session-presence leakage on LAN binds.
|
|
269
398
|
|
|
270
399
|
### Security
|
|
271
400
|
|
|
@@ -273,27 +402,27 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
273
402
|
- 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.
|
|
274
403
|
- `crypto.randomBytes` failure or non-writable data directory → server exits 1; no silent fallback.
|
|
275
404
|
|
|
276
|
-
##
|
|
405
|
+
## [0.6.4] - 2026-04-20
|
|
277
406
|
|
|
278
407
|
### Fixed
|
|
279
408
|
|
|
280
409
|
- **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.
|
|
281
410
|
- **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"`.
|
|
282
411
|
|
|
283
|
-
##
|
|
412
|
+
## [0.6.3] - 2026-04-19
|
|
284
413
|
|
|
285
414
|
### Fixed
|
|
286
415
|
|
|
287
416
|
- **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.
|
|
288
417
|
- **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.
|
|
289
|
-
- **Dark-mode
|
|
418
|
+
- **Dark-mode ****\*-bg**** 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.
|
|
290
419
|
- **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.
|
|
291
420
|
|
|
292
421
|
### Changed
|
|
293
422
|
|
|
294
423
|
- **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.
|
|
295
|
-
- **Annotation serialization upgrades legacy
|
|
296
|
-
- **migrateToV1
|
|
424
|
+
- **Annotation serialization upgrades legacy ****type**** 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.
|
|
425
|
+
- **migrateToV1**\*\* 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.
|
|
297
426
|
|
|
298
427
|
### Internal
|
|
299
428
|
|
|
@@ -302,19 +431,19 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
302
431
|
- Accessibility: forced-colors fallback audit on PR #303 annotation surfaces (#311).
|
|
303
432
|
- CI: typecheck / lint / tests now gate on all PRs regardless of base branch; dropped unused `baseUrl` from `tsconfig.server.json` (#310).
|
|
304
433
|
|
|
305
|
-
##
|
|
434
|
+
## [0.6.2] - 2026-04-16
|
|
306
435
|
|
|
307
436
|
### Fixed
|
|
308
437
|
|
|
309
438
|
- **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.
|
|
310
439
|
|
|
311
|
-
##
|
|
440
|
+
## [0.6.1] - 2026-04-15
|
|
312
441
|
|
|
313
442
|
### Fixed
|
|
314
443
|
|
|
315
444
|
- **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`.
|
|
316
445
|
|
|
317
|
-
##
|
|
446
|
+
## [0.6.0] - 2026-04-15
|
|
318
447
|
|
|
319
448
|
### Added
|
|
320
449
|
|
|
@@ -338,19 +467,19 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
338
467
|
|
|
339
468
|
### Fixed
|
|
340
469
|
|
|
341
|
-
-
|
|
470
|
+
- \*\*Monitor preserves last-known \*\***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.
|
|
342
471
|
- **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.
|
|
343
|
-
- **/api/setup
|
|
472
|
+
- **/api/setup**\*\* returns accurate status codes\*\* — 207 on partial failure (some targets configured, some failed) and 500 on total failure, instead of always returning 200.
|
|
344
473
|
- **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.
|
|
345
474
|
- **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.
|
|
346
475
|
- **Defensive exit on monitor fallthrough** — the retry loop exits 1 if it ever terminates without hitting the explicit exhaustion path.
|
|
347
476
|
|
|
348
|
-
##
|
|
477
|
+
## [0.5.1] - 2026-04-13
|
|
349
478
|
|
|
350
479
|
### Added
|
|
351
480
|
|
|
352
481
|
- **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`.
|
|
353
|
-
- **--with-channel-shim
|
|
482
|
+
- **--with-channel-shim**\*\* opt-in\*\* — `tandem setup --with-channel-shim` writes the legacy `tandem-channel` MCP entry for setups that can't install the plugin.
|
|
354
483
|
|
|
355
484
|
### Changed
|
|
356
485
|
|
|
@@ -364,10 +493,10 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
364
493
|
- **Exponential backoff on reconnect** — monitor reconnects use 2s/4s/8s/16s/30s backoff instead of a fixed 2s delay.
|
|
365
494
|
- **SIGINT/SIGTERM clears awareness** — monitor posts a final `clearAwareness` before exit so the "Claude is active" indicator doesn't hang in the browser.
|
|
366
495
|
- **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.
|
|
367
|
-
-
|
|
496
|
+
- \*\*SSE parse errors don't advance \*\***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.
|
|
368
497
|
- **SKILL.md corrected** — `question` annotation guidance now uses `type === 'comment' && directedAt === 'claude' && author === 'user'`; all 5 highlight colors listed (yellow, red, green, blue, purple).
|
|
369
498
|
|
|
370
|
-
##
|
|
499
|
+
## [0.5.0] - 2026-04-13
|
|
371
500
|
|
|
372
501
|
### Added
|
|
373
502
|
|
|
@@ -392,7 +521,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
392
521
|
- EOL normalizer added to lint-staged for .yml and .md files (#263)
|
|
393
522
|
- Lessons learned applied to codebase and tooling (#280)
|
|
394
523
|
|
|
395
|
-
##
|
|
524
|
+
## [0.4.0] - 2026-04-12
|
|
396
525
|
|
|
397
526
|
### Added
|
|
398
527
|
|
|
@@ -424,14 +553,14 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
424
553
|
- CI: fail build when updater signing key secret is absent
|
|
425
554
|
- CI: summary job catches partial platform build failures rather than reporting false success
|
|
426
555
|
|
|
427
|
-
##
|
|
556
|
+
## [0.3.2] - 2026-04-12
|
|
428
557
|
|
|
429
558
|
### Changed
|
|
430
559
|
|
|
431
560
|
- **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)
|
|
432
561
|
- **Toolbar:** Three annotation buttons (Comment, Suggest, Ask Claude) replaced with a single Comment button with "Replace" and "@Claude" toggles (#193)
|
|
433
562
|
- **Side panel filters:** "Suggestions" → "With replacement", "Questions" → "For Claude" (#193)
|
|
434
|
-
-
|
|
563
|
+
- \*\*sanitizeAnnotation()\*\*\*\* moved to \*\***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)
|
|
435
564
|
|
|
436
565
|
### Added
|
|
437
566
|
|
|
@@ -465,7 +594,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
465
594
|
- **MCP wire change:** Removed unused `"overlay"` annotation kind from `AnnotationTypeSchema`. External clients sending `type: "overlay"` will now receive a Zod validation error. (#249)
|
|
466
595
|
- **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)
|
|
467
596
|
|
|
468
|
-
##
|
|
597
|
+
## [0.3.0] - 2026-04-07
|
|
469
598
|
|
|
470
599
|
### Wave 4: Notification & Interruption Redesign
|
|
471
600
|
|
|
@@ -479,7 +608,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
479
608
|
- `Y_MAP_MODE` constant, Zod validation for mode reads, error logging in channel event bridge
|
|
480
609
|
- 894 tests passing
|
|
481
610
|
|
|
482
|
-
##
|
|
611
|
+
## [0.2.12] - 2026-04-06
|
|
483
612
|
|
|
484
613
|
### Added
|
|
485
614
|
|
|
@@ -492,7 +621,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
492
621
|
|
|
493
622
|
- Guard all localStorage access with try-catch for private/disabled browser storage modes; reset scroll position on annotation filter clear (#212, #202)
|
|
494
623
|
|
|
495
|
-
##
|
|
624
|
+
## [0.2.11] - 2026-04-06
|
|
496
625
|
|
|
497
626
|
### Added
|
|
498
627
|
|
|
@@ -500,7 +629,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
500
629
|
- File watcher module with 500ms debounce and self-write suppression (prevents reload loops when Tandem saves)
|
|
501
630
|
- Toast notification when a document is reloaded from disk
|
|
502
631
|
- Runtime warning when `onDocSwapped` callback is missing during Hocuspocus doc swap (defensive guard for #178 audit)
|
|
503
|
-
- 28 new tests: observer reattachment,
|
|
632
|
+
- 28 new tests: observer reattachment, CTRL_ROOM lifecycle, buffer cap, file watcher debounce/suppress, annotation-preserving reload
|
|
504
633
|
|
|
505
634
|
### Fixed
|
|
506
635
|
|
|
@@ -511,7 +640,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
511
640
|
|
|
512
641
|
- CLAUDE.md gotcha for Hocuspocus doc replacement updated to document the automatic `onDocSwapped` callback lifecycle (#178)
|
|
513
642
|
|
|
514
|
-
##
|
|
643
|
+
## [0.2.10] - 2026-04-05
|
|
515
644
|
|
|
516
645
|
### Added
|
|
517
646
|
|
|
@@ -527,14 +656,14 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
527
656
|
|
|
528
657
|
- `atomicWrite()` extracted as shared helper in session manager — consolidates duplicate write-tmp-rename logic with exponential backoff retry
|
|
529
658
|
|
|
530
|
-
##
|
|
659
|
+
## [0.2.9] - 2026-04-05
|
|
531
660
|
|
|
532
661
|
### Fixed
|
|
533
662
|
|
|
534
663
|
- 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
|
|
535
664
|
- Tutorial annotation injection errors now get their own log message instead of being misattributed as file-open failures
|
|
536
665
|
|
|
537
|
-
##
|
|
666
|
+
## [0.2.8] - 2026-04-05
|
|
538
667
|
|
|
539
668
|
### Added
|
|
540
669
|
|
|
@@ -542,7 +671,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
542
671
|
- `checkVersionChange` helper tracks version transitions via `last-seen-version` file
|
|
543
672
|
- CHANGELOG.md now ships in the npm package
|
|
544
673
|
|
|
545
|
-
##
|
|
674
|
+
## [0.2.7] - 2026-04-05
|
|
546
675
|
|
|
547
676
|
### Fixed
|
|
548
677
|
|
|
@@ -554,44 +683,44 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
|
|
|
554
683
|
|
|
555
684
|
- 4 new tests for force-reload (annotation clearing, awareness clearing, .txt reload, metadata)
|
|
556
685
|
|
|
557
|
-
##
|
|
686
|
+
## [0.2.6] - 2026-04-05
|
|
558
687
|
|
|
559
688
|
### Fixed
|
|
560
689
|
|
|
561
690
|
- Demo script rewritten to be self-referential for recording
|
|
562
691
|
- Observer ownership documentation added to architecture.md
|
|
563
692
|
|
|
564
|
-
##
|
|
693
|
+
## [0.2.5] - 2026-04-05
|
|
565
694
|
|
|
566
695
|
### Fixed
|
|
567
696
|
|
|
568
697
|
- `tandem setup` Claude Code MCP config path updated
|
|
569
698
|
|
|
570
|
-
##
|
|
699
|
+
## [0.2.4] - 2026-04-05
|
|
571
700
|
|
|
572
701
|
### Fixed
|
|
573
702
|
|
|
574
703
|
- Security audit findings (DNS rebinding, CORS, input validation)
|
|
575
704
|
|
|
576
|
-
##
|
|
705
|
+
## [0.2.3] - 2026-04-05
|
|
577
706
|
|
|
578
707
|
### Fixed
|
|
579
708
|
|
|
580
709
|
- `tandem setup` now writes Claude Code MCP config to `~/.claude.json` instead of `~/.claude/mcp_settings.json`, which Claude Code no longer reads
|
|
581
710
|
|
|
582
|
-
##
|
|
711
|
+
## [0.2.2] - 2025-04-05
|
|
583
712
|
|
|
584
713
|
### Fixed
|
|
585
714
|
|
|
586
715
|
- Silent failure review findings
|
|
587
716
|
|
|
588
|
-
##
|
|
717
|
+
## [0.2.1] - 2025-04-05
|
|
589
718
|
|
|
590
719
|
### Fixed
|
|
591
720
|
|
|
592
721
|
- Full security audit — 25 findings across 7 categories (#172)
|
|
593
722
|
|
|
594
|
-
##
|
|
723
|
+
## [0.2.0] - 2025-04-04
|
|
595
724
|
|
|
596
725
|
### Added
|
|
597
726
|
|