tandem-editor 0.9.0 → 0.11.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.
@@ -24,11 +24,13 @@
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
+ "experimental": {
28
+ "monitors": [
29
+ {
30
+ "name": "tandem-events",
31
+ "command": "node ${CLAUDE_PLUGIN_ROOT}/dist/monitor/index.js",
32
+ "description": "Tandem real-time document events (annotations, chat, selections)"
33
+ }
34
+ ]
35
+ }
34
36
  }
package/CHANGELOG.md CHANGED
@@ -5,7 +5,121 @@ 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
- ## \[Unreleased]
8
+ ## \[0.11.0] - 2026-05-11
9
+
10
+ ### Added
11
+
12
+ - **Audience-first selection popup (AR3, PR #590)** — Replaces the three-mode state machine (`idle → comment | note`) with a unified popup that appears on text selection. The user types first and chooses audience at submit time via two buttons: "Comment" (sends to Claude, requires text) and "Note to self" (private, always enabled). Bold/Italic formatting and highlight color swatches remain as one-click actions in the top row. Enter submits as Comment; Shift+Enter inserts a newline; Escape dismisses. `InputGroup.svelte` deleted. New testids: `popup-annotation-input`, `popup-note-submit`, `popup-comment-submit`, `popup-highlight-{yellow|green|blue|pink}`.
13
+ - **Five annotation visual languages (AR2, PR #586)** — Claude-authored comments now render with a solid underline (`--tandem-author-claude`) instead of the same dashed style as user comments. All annotation inline decorations carry a `data-annotation-author` attribute for CSS targeting (e.g. theme overrides, annotation-patterns mode). The five languages are now fully distinct: highlight (colored bg), note (dotted muted underline), user comment (dashed blue), Claude comment (solid orange underline), suggestion (wavy violet).
14
+ - **Annotation schema foundation — audience model (AR1, PR #583)** — adds three optional fields to `AnnotationBase`: `audience` (`"private" | "outbound"`), `promotedFrom` (`"note"`), and `importSource` (`{ author, file }`). `sanitize.ts` derives `audience` on every read for legacy annotations (highlight/note/flag → `"private"`, comment → `"outbound"`, import → `"private"` per design brief). Wire-shape change: all MCP tool responses and channel events now include `audience`. Backward-compatible — existing annotations gain the field on first read; no data loss.
15
+
16
+ - **Command palette + action registry (closes #571)** — Ctrl+Shift+P opens a fuzzy-search command palette. A central action registry (`src/client/actions/registry.ts`) is the new source of truth for commands and their display shortcuts; the Settings → Shortcuts tab now derives its content from the registry rather than a hardcoded array. Ctrl+S and Ctrl+, are migrated from dedicated hook files into the global keydown handler; `useSaveShortcut.svelte.ts` and `useSettingsShortcut.svelte.ts` are deleted. ADR-029 records the design. New testids: `command-palette`, `palette-input`, `palette-item-{id}`, `palette-empty`.
17
+ - **Find / Replace bar (closes #570)** — Ctrl+F opens a find bar anchored to the bottom-right of the editor. Highlights all matches in the document using the existing highlight-yellow token; active match gets a warning-bg border. Enter / Shift+Enter cycle through matches. Replace replaces the active match and advances; All replaces in 100-match chunks to keep Yjs updates bounded. Regex-mode toggle (off by default) with inline error for invalid patterns. All options are session-only (not persisted). New testids: `find-replace-bar`, `find-input`, `replace-input`, `find-next-btn`, `find-prev-btn`, `replace-btn`, `replace-all-btn`, `find-close-btn`, `find-match-count`, `find-regex-toggle`, `find-case-toggle`, `find-word-toggle`.
18
+ - **Outline panel for H1–H3 navigation (closes #569)** — Settings → Appearance now offers a "Left Panel" radio (Side / Outline). When Outline is selected, the side/annotations panel is replaced with a compact heading navigator. Click any heading to jump the cursor. Roving tabindex for keyboard navigation. Disabled with explanatory text when the Tabbed layout (no left panel) is active. New testids: `outline-panel`, `outline-heading-{level}-{index}`, `left-slot-kind-radio-{side|outline}`.
19
+ - **Root-scoped editor font (closes #568)** — `--tandem-editor-font-family` is now applied to `document.documentElement` so the chosen font propagates to all surfaces (editor, tab labels, toolbar) rather than only the editor container. `applyEditorFontToRoot` and `createRootEditorFont` added alongside the existing per-element helpers.
20
+ - **Redesigned format badge on document tabs (closes #568)** — the 1-letter format icon is replaced with a styled pill badge (`MD`, `TXT`, `HTML`, `DOCX`) using format-specific semantic token colours. The dirty-dot slot is now always in layout with `visibility` toggled (prevents tab-width shift between dirty/clean states). New testids: `tab-format-badge-{id}`.
21
+ - **Temporary scratchpad (closes #475)** — `Ctrl+N` or "New Scratchpad" in the command palette / tab bar `+` menu opens an ephemeral in-memory document. Content is discarded when the tab is closed. Scratchpad paths use synthetic `upload://` URIs and are excluded from session restore and channel events. New `tandem_scratchpad` MCP tool lets Claude create scratchpads programmatically. Editor auto-focuses on mount for editable documents so the cursor is ready immediately.
22
+ - **Relative markdown link navigation (closes #479)** — Clicking relative `.md`, `.txt`, and `.html` links in the editor opens them as new Tandem tabs. External and non-supported links open in the default browser.
23
+ - **Documentation button in Settings (closes #457)** — A "View Documentation" button in the Settings popover About section opens `docs/workflows.md` as a read-only tab.
24
+ - **Store read-only warning banner (closes #506)** — When the annotation store is locked (read-only), a dismissible warning banner appears in the side panel. Dismiss state persists across sessions.
25
+ - **Claude Code automation hooks, agents, and skills (PR #591)** — 6 new hooks (stdout guard, svelte-check, token scanner, related-test runner, --no-verify blocker, E2E port-kill blocker), 2 specialized review agents (annotation-model, svelte-migration), 2 skills (`/changelog`, `/e2e-debug`), and `settings.json` wiring. Block hooks fail-closed on parse error; warn hooks include env-var opt-outs.
26
+ - **Theme-color meta tag sync** — `<meta name="theme-color">` updates reactively when the app theme changes, improving desktop PWA and mobile browser chrome appearance.
27
+
28
+ ### Changed
29
+
30
+ - **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`.
31
+ - **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.
32
+ - **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.
33
+ - **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.
34
+ - **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.
35
+ - **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.
36
+ - **Automated WCAG AA gate** — `tests/e2e/accessibility.spec.ts` uses `@axe-core/playwright` to verify zero contrast violations in both light and dark mode on every CI run; editor content area excluded (user-authored content has arbitrary contrast).
37
+ - **Inline link input replaces browser prompt (closes #548, #589)** — The FormattingToolbar's Link button now opens an inline popover instead of `window.prompt()`. The input pre-populates with the existing href when editing a link; submitting empty unsets the link. New testids: `toolbar-link-input`, `toolbar-link-submit`, `toolbar-link-cancel`.
38
+
39
+ ### Changed
40
+
41
+ - **Updater dialogs are now parented to the main window** — "Update Available", "No Updates Available", and "Update Error" dialogs attach to the Tandem window via `MessageDialogBuilder::parent()`, centering them over the app and inheriting Windows 11 dark-mode chrome from the `tauri-plugin-decorum` shell (closes #561, #553)
42
+ - **Custom window chrome via tauri-plugin-decorum** — native OS title bar replaced with a themed custom title bar that re-themes with the rest of the app; preserves Windows Aero Snap, Snap Layouts, resize border, and macOS traffic-light positioning (#554)
43
+ - **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)
44
+ - **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/`.
45
+ - **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.
46
+
47
+ ### Tests
48
+
49
+ - **Plugin state machine unit tests for slash command menu (#517)** — added 7 Vitest tests in `tests/client/slash-command.test.ts` that exercise the ProseMirror plugin via a real Tiptap Editor in happy-dom: active state on `/` insertion, close meta, select meta, non-empty selection guard, query filtering with index clamping, ArrowDown wrap-around, and Enter-to-execute.
50
+
51
+ ### Removed
52
+
53
+ - **`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.
54
+
55
+ ### Fixed
56
+
57
+ - **Annotation console flood eliminated (closes #585)** — Deriving `audience` from annotation type is now silent; the `audience-derived` event type has been removed from the sanitization event system. New annotations also carry an explicit `audience` field at creation time.
58
+ - **Audience conflict guard (closes #584)** — User-authored notes and highlights can no longer be stored with `audience: outbound`. The sanitization layer enforces this invariant and emits an `audience-conflict-resolved` event when a conflict is detected.
59
+ - **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)
60
+ - **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.
61
+ - **Toolbar**: HighlightColorPicker border now uses `--tandem-border` token, correctly adapting to light/dark theme switching (#536)
62
+ - **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.
63
+ - **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).
64
+ - **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.
65
+ - **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)`.
66
+
67
+ ## \[0.10.1] - Unreleased
68
+
69
+ Plugin URL and auth resolution for custom-port and network-remote setups.
70
+
71
+ ### Changed
72
+
73
+ - **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`.
74
+ - **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.
75
+
76
+ ## \[0.10.0] - 2026-05-03
77
+
78
+ 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.
79
+
80
+ ### Removed
81
+
82
+ - **`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.
83
+ - **`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.
84
+
85
+ ### Changed
86
+
87
+ - **React → Svelte 5 migration (#472, #508)** — all client components rewritten with Svelte 5 runes (`$state`, `$derived`, `$effect`). Component APIs, data-testid selectors, and observable behavior are unchanged; only the rendering layer is new.
88
+ - **Note annotation actions** — note cards in the side panel now show **Archive** and **Send to Claude** instead of Remove. "Send to Claude" promotes the note to a comment and fires an `annotation:created` channel event so Claude is notified immediately.
89
+
90
+ ### Fixed
91
+
92
+ - **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.
93
+ - **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).
94
+ - **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).
95
+ - **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).
96
+ - **`annotation:edited` channel event deduplication** — rapid successive edits no longer emit duplicate events to the channel (Codex security review).
97
+ - **`svelte-check --fail-on-warnings` now gates the build** — 26 pre-existing Svelte type warnings cleared; CI enforces zero-warning policy going forward.
98
+
99
+ ### Added
100
+
101
+ - **Keyboard-accessible panel resize handles (#511, #524)** — Arrow keys resize by ±16 px, Page Up/Down by ±80 px, Home/End snap to the minimum/maximum width. `aria-valuenow` reflects the live panel width.
102
+ - **ARIA dialog focus management (#511, #524)** — HelpModal and ReviewSummary now trap Tab focus, restore focus on close, and close on Escape. Backdrops carry `role="presentation"`; dialog containers carry `role="dialog" aria-modal="true" tabindex="-1"`.
103
+ - **Form label associations (#511, #524)** — AnnotationEditForm inputs are now properly associated with their `<label>` elements.
104
+ - **AnnotationCard role corrected (#511, #524)** — changed from `role="button"` (nested-button violation) to `role="listitem"`.
105
+
106
+ ## \[0.9.1] - 2026-05-01
107
+
108
+ 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.
109
+
110
+ ### Fixed
111
+
112
+ - **Imported Word reviewer comments now surface to Claude by default (#482)** — `.docx` reviewer comments are imported as `author: "import"`, `type: "comment"` (was `type: "note"` in the unreleased PR #474 plan). Reverts the `tandem_getAnnotations` `includeImports` opt-in introduced in PR #474 — Claude can read imported comments alongside its own without an explicit flag, which matches the .docx review workflow. The opt-in plumbing (`includeImports` parameter, `importsExcluded` response field) is removed. Existing on-disk records with `author: "import", type: "note"` migrate transparently on read via `sanitizeAnnotation`; on next import the durable record is rewritten in place. Safe because PR #474 was never tagged in a release.
113
+ - **Markdown tables preserved across Tiptap round-trip (#379)** — bidirectional MDAST↔Y.Doc table conversion added to `mdast-ydoc.ts`. Tables with mixed column alignment, inline marks in cells, and empty cells all survive load/save cycles. Flat-offset alignment preserved so annotations anchored after a table resolve correctly.
114
+ - **HTML blocks and insertion-order fixed in mdast-ydoc (#496)** — raw HTML blocks (`<div>`, `<details>`, etc.) now round-trip as `html` nodes instead of being dropped. Insertion-order bug in `blockToYxml` fixed — the two-pass Y.XmlText attach-before-populate pattern now applied uniformly to all block types.
115
+ - **Channel shim per-request timeouts (#364)** — event bridge and `run.ts` now use bounded request-response fetches with split SSE handshake/body watchdogs and a 1 MB SSE frame buffer cap. `tandem_reply` returns a structured timeout error instead of hanging indefinitely.
116
+ - **Sanitize coercions routed to migration-log (#483)** — lossy ADR-027 type coercions in `sanitize.ts` (e.g. `flag` → `note`) now emit a `migration-log.ts` entry (once per doc/kind) instead of silently rewriting records, restoring the forensic trail for ADR-027 transitions.
117
+ - **Doc hash required for collection logs (#495)** — annotation collection log entries now require a `docHash` field, preventing cross-document log pollution from unkeyed writes.
118
+ - **Standalone monitor gated on backend readiness (#491)** — `dev:standalone` waits for the backend health endpoint before starting the monitor, eliminating the startup race that caused spurious connection errors.
119
+
120
+ ### Tests
121
+
122
+ - **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).
9
123
 
10
124
  ## \[0.9.0] - 2026-04-28
11
125
 
@@ -17,6 +131,9 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
17
131
  - **`tandem_getContent` removed (#259)** — superseded by `tandem_getTextContent`.
18
132
  - **`tandem_getSelections` removed (#259)** — superseded by `tandem_checkInbox`.
19
133
  - **`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.
134
+ - **`tandem_flag` deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Use `tandem_comment` instead. Hard-remove in v0.10.0.
135
+ - **`tandem_highlight` deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Highlights are user-only. Hard-remove in v0.10.0.
136
+ - **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.
20
137
 
21
138
  ### Added
22
139
 
@@ -29,6 +146,8 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
29
146
  - **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.
30
147
  - **CI stdio smoke test (#341)** — GitHub Actions step validates the Cowork stdio bridge (`scripts/ci/stdio-smoke.mjs`) on every push.
31
148
  - **`__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.
149
+ - **`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.
150
+ - **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.
32
151
 
33
152
  ### Fixed
34
153
 
@@ -39,11 +158,16 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
39
158
  - **Annotation recovery guard hardening** — narrowed guard scope for edge cases in session restore.
40
159
  - **Event-bridge error handling** — uncaught errors in SSE delivery no longer crash the event loop.
41
160
  - **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.
161
+ - **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.
162
+ - **`normalizeReply` validation (#473)** — replies are now Zod-validated before being merged; malformed entries are dropped + logged instead of poisoning the envelope.
42
163
 
43
164
  ### Changed
44
165
 
45
166
  - **Redesign gap audit resolved (#439)** — 7 product decisions documented in ADR-026. Design response prompt at `docs/claude-design-response-prompt.md`.
46
167
  - **Distribution items deferred** — #316 (macOS/Linux Cowork auto-setup), #317 (cross-platform firewall scoping), #322 (network-type detection) moved to v0.13.0. Requires macOS/Linux validation hardware.
168
+ - **Annotation type model unified to audience-based (ADR-027, #473)** — `flag` → `note`. Three types now: `highlight` (visual marker), `note` (private), `comment` (sent to Claude). Channel observer filters notes from SSE — they never reach Claude. `checkInbox` returns only `comment` annotations.
169
+ - **Note toolbar UX (#480)** — Note button now opens an inline input mirroring the Comment flow.
170
+ - **Note card visual distinction (#481)** — amber border + warning-bg tint distinguishes notes from comments in the side panel.
47
171
 
48
172
  ### Internal
49
173
 
@@ -132,14 +256,14 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
132
256
 
133
257
  - **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.
134
258
  - **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.
135
- - **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.
259
+ - **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.
136
260
  - **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.
137
261
 
138
262
  ### Changed
139
263
 
140
264
  - **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.
141
- - **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.
142
- - **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.
265
+ - **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.
266
+ - **`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.
143
267
 
144
268
  ### Internal
145
269
 
@@ -184,9 +308,9 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
184
308
 
185
309
  ### Fixed
186
310
 
187
- - **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.
311
+ - **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.
188
312
  - **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.
189
- - **/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.
313
+ - **`/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.
190
314
  - **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.
191
315
  - **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.
192
316
  - **Defensive exit on monitor fallthrough** — the retry loop exits 1 if it ever terminates without hitting the explicit exhaustion path.
@@ -196,7 +320,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
196
320
  ### Added
197
321
 
198
322
  - **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`.
199
- - **--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.
323
+ - **`--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.
200
324
 
201
325
  ### Changed
202
326
 
@@ -210,7 +334,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
210
334
  - **Exponential backoff on reconnect** — monitor reconnects use 2s/4s/8s/16s/30s backoff instead of a fixed 2s delay.
211
335
  - **SIGINT/SIGTERM clears awareness** — monitor posts a final `clearAwareness` before exit so the "Claude is active" indicator doesn't hang in the browser.
212
336
  - **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.
213
- - **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.
337
+ - **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.
214
338
  - **SKILL.md corrected** — `question` annotation guidance now uses `type === 'comment' && directedAt === 'claude' && author === 'user'`; all 5 highlight colors listed (yellow, red, green, blue, purple).
215
339
 
216
340
  ## \[0.5.0] - 2026-04-13
@@ -277,7 +401,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
277
401
  - **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)
278
402
  - **Toolbar:** Three annotation buttons (Comment, Suggest, Ask Claude) replaced with a single Comment button with "Replace" and "@Claude" toggles (#193)
279
403
  - **Side panel filters:** "Suggestions" → "With replacement", "Questions" → "For Claude" (#193)
280
- - **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)
404
+ - **`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)
281
405
 
282
406
  ### Added
283
407
 
package/LICENSE CHANGED
@@ -1,21 +1,58 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Bryan Kolb
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
2
+ "Business Source License" is a trademark of MariaDB Corporation Ab.
3
+
4
+ Parameters
5
+
6
+ Licensor: Bryan Kolb
7
+ Licensed Work: Tandem (tandem-editor npm package).
8
+ The Licensed Work is (c) 2026 Bryan Kolb.
9
+ Additional Use Grant: Personal use and individual self-hosting are permitted;
10
+ commercial hosting or resale of the Licensed Work is not.
11
+ Change Date: The earlier of 2029-06-10 or two years after the public
12
+ general availability release of Tandem v1.0.
13
+ Change License: MIT License
14
+
15
+ Notice
16
+
17
+ Business Source License 1.1
18
+
19
+ Terms
20
+
21
+ The Licensor hereby grants you the right to copy, modify, create derivative
22
+ works, redistribute, and make non-production use of the Licensed Work. The
23
+ Licensor may make an Additional Use Grant, above, permitting limited production use.
24
+
25
+ Effective on the Change Date, or the fourth anniversary of the first publicly
26
+ available distribution of a specific version of the Licensed Work under this
27
+ License, whichever comes first, the Licensor hereby grants you rights under
28
+ the terms of the Change License, and the rights granted in the paragraph
29
+ above terminate.
30
+
31
+ If your use of the Licensed Work does not comply with the requirements
32
+ currently in effect as described in this License, you must purchase a
33
+ commercial license from the Licensor, its affiliated entities, or authorized
34
+ resellers, or you must refrain from using the Licensed Work.
35
+
36
+ All copies of the original and modified Licensed Work, and derivative works
37
+ of the Licensed Work, are subject to this License. This License applies
38
+ separately for each version of the Licensed Work and the Change Date may vary
39
+ for each version of the Licensed Work released by Licensor.
40
+
41
+ You must conspicuously display this License on each original or modified copy
42
+ of the Licensed Work. If you receive the Licensed Work in original or
43
+ modified form from a third party, the terms and conditions set forth in this
44
+ License apply to your use of that work.
45
+
46
+ Any use of the Licensed Work in violation of this License will automatically
47
+ terminate your rights under this License for the current and all other
48
+ versions of the Licensed Work.
49
+
50
+ This License does not grant you any right in any trademark or logo of
51
+ Licensor or its affiliates (provided that you may use a trademark or logo of
52
+ Licensor as expressly required by this License).
53
+
54
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
55
+ AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
56
+ EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
57
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
58
+ TITLE.
package/README.md CHANGED
@@ -99,7 +99,7 @@ claude
99
99
  Then pick one of two ways to keep the conversation flowing:
100
100
 
101
101
  1. **Just chat in the terminal (simplest).** Every time you send Claude a message, it has a chance to call `tandem_checkInbox` and pick up your latest selection, any annotations you accepted or dismissed, and any chat messages from the Tandem sidebar. Zero setup — this is how it works out of the box. With Tandem and the terminal snapped side by side, the loop feels surprisingly natural; Claude just reacts when you nudge it rather than spontaneously.
102
- 2. **Background polling with `/loop` (hands-off).** Ask Claude to check in on its own using the `/loop` skill:
102
+ 2. **Background polling with&#x20;****/loop****&#x20;(hands-off).** Ask Claude to check in on its own using the `/loop` skill:
103
103
  ```
104
104
  /loop 30s check tandem inbox and respond to any new messages
105
105
  ```
@@ -129,10 +129,10 @@ curl http://localhost:3479/health
129
129
  git clone https://github.com/bloknayrb/tandem.git
130
130
  cd tandem
131
131
  npm install
132
- npm run dev:standalone # starts server (:3478/:3479) + editor client (:5173)
132
+ npm run dev:standalone # starts backend (:3478/:3479), editor client (:5173), and monitor
133
133
  ```
134
134
 
135
- 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.
135
+ 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.
136
136
 
137
137
  </details>
138
138
 
@@ -197,7 +197,7 @@ See the full [Roadmap](docs/roadmap.md) and [Known Limitations](docs/roadmap.md#
197
197
  ## Documentation
198
198
 
199
199
  - **[User Guide](docs/user-guide.md)** — How to use Tandem: editor UI, annotations, chat, review mode, keyboard shortcuts
200
- - [MCP Tool Reference](docs/mcp-tools.md) — 28 MCP tools + channel API endpoints
200
+ - [MCP Tool Reference](docs/mcp-tools.md) — 28 MCP tools (25 active, 3 deprecated stubs) + channel API endpoints
201
201
  - [Architecture](docs/architecture.md) — System design, data flows, coordinate systems, channel push
202
202
  - [Workflows](docs/workflows.md) — Claude Code usage patterns: text iteration, cross-referencing, multi-model
203
203
  - [Roadmap](docs/roadmap.md) — Phase 2+ roadmap, known issues, future extensions
@@ -206,17 +206,17 @@ See the full [Roadmap](docs/roadmap.md) and [Known Limitations](docs/roadmap.md#
206
206
 
207
207
  ## CLI Commands
208
208
 
209
- | Command | What it does |
210
- |---------|-------------|
211
- | `tandem` | Start server and open editor (global install) |
212
- | `tandem setup` | Register MCP tools with Claude Code / Claude Desktop |
213
- | `tandem setup --force` | Register to default paths regardless of auto-detection |
214
- | `tandem --version` | Show installed version |
215
- | `tandem --help` | Show usage |
216
- | `tandem setup --with-channel-shim` | Also register the stdio channel shim |
217
- | `tandem rotate-token` | Rotate auth token (60-second grace window) |
218
- | `tandem mcp-stdio` | Run as stdio MCP server (proxy to local HTTP, for plugin bridge) |
219
- | `tandem channel` | Run the channel shim (stdio MCP for plugin's tandem-channel entry) |
209
+ | Command | What it does |
210
+ | ---------------------------------- | ------------------------------------------------------------------ |
211
+ | `tandem` | Start server and open editor (global install) |
212
+ | `tandem setup` | Register MCP tools with Claude Code / Claude Desktop |
213
+ | `tandem setup --force` | Register to default paths regardless of auto-detection |
214
+ | `tandem --version` | Show installed version |
215
+ | `tandem --help` | Show usage |
216
+ | `tandem setup --with-channel-shim` | Also register the stdio channel shim |
217
+ | `tandem rotate-token` | Rotate auth token (60-second grace window) |
218
+ | `tandem mcp-stdio` | Run as stdio MCP server (proxy to local HTTP, for plugin bridge) |
219
+ | `tandem channel` | Run the channel shim (stdio MCP for plugin's tandem-channel entry) |
220
220
 
221
221
  ## MCP Configuration
222
222
 
@@ -248,21 +248,21 @@ Both entries are cross-platform — no platform-specific configuration needed.
248
248
 
249
249
  All optional — defaults work out of the box.
250
250
 
251
- | Variable | Default | Description |
252
- |----------|---------|-------------|
253
- | `TANDEM_PORT` | `3478` | Hocuspocus WebSocket port |
254
- | `TANDEM_MCP_PORT` | `3479` | MCP HTTP + REST API port |
255
- | `TANDEM_URL` | `http://localhost:3479` | Channel shim server URL |
256
- | `TANDEM_TRANSPORT` | `http` | Transport mode (`http` or `stdio`) |
257
- | `TANDEM_NO_SAMPLE` | unset | Set to `1` to skip auto-opening `sample/welcome.md` |
258
- | `TANDEM_CLAUDE_CMD` | `claude` | Claude Code executable name (for `tandem setup` auto-detection) |
259
- | `TANDEM_BIND_HOST` | `127.0.0.1` | Bind address for MCP HTTP (`0.0.0.0` for LAN) |
260
- | `TANDEM_AUTH_TOKEN` | auto-generated | Override auth token (set by Tauri; manual use rare) |
261
- | `TANDEM_ALLOW_UNAUTHENTICATED_LAN` | unset | Set to `1` to skip token requirement on LAN bind |
262
- | `TANDEM_LAN_IP` | auto-detected | Explicit LAN IP for multi-homed machines |
263
- | `TANDEM_REQUEST_TIMEOUT_MS` | `30000` | Per-request timeout in stdio bridge (ms) |
264
- | `TANDEM_APP_DATA_DIR` | platform default | Override app-data root (sessions, auth-token, annotations) |
265
- | `TANDEM_ANNOTATION_STORE` | unset | Set to `off` to disable durable annotation persistence |
251
+ | Variable | Default | Description |
252
+ | ---------------------------------- | ----------------------- | --------------------------------------------------------------- |
253
+ | `TANDEM_PORT` | `3478` | Hocuspocus WebSocket port |
254
+ | `TANDEM_MCP_PORT` | `3479` | MCP HTTP + REST API port |
255
+ | `TANDEM_URL` | `http://localhost:3479` | Channel shim server URL |
256
+ | `TANDEM_TRANSPORT` | `http` | Transport mode (`http` or `stdio`) |
257
+ | `TANDEM_NO_SAMPLE` | unset | Set to `1` to skip auto-opening `sample/welcome.md` |
258
+ | `TANDEM_CLAUDE_CMD` | `claude` | Claude Code executable name (for `tandem setup` auto-detection) |
259
+ | `TANDEM_BIND_HOST` | `127.0.0.1` | Bind address for MCP HTTP (`0.0.0.0` for LAN) |
260
+ | `TANDEM_AUTH_TOKEN` | auto-generated | Override auth token (set by Tauri; manual use rare) |
261
+ | `TANDEM_ALLOW_UNAUTHENTICATED_LAN` | unset | Set to `1` to skip token requirement on LAN bind |
262
+ | `TANDEM_LAN_IP` | auto-detected | Explicit LAN IP for multi-homed machines |
263
+ | `TANDEM_REQUEST_TIMEOUT_MS` | `30000` | Per-request timeout in stdio bridge (ms) |
264
+ | `TANDEM_APP_DATA_DIR` | platform default | Override app-data root (sessions, auth-token, annotations) |
265
+ | `TANDEM_ANNOTATION_STORE` | unset | Set to `off` to disable durable annotation persistence |
266
266
 
267
267
  See `.env.example` for a copy-paste template.
268
268
 
@@ -279,6 +279,8 @@ Tandem kills stale processes on :3478/:3479 at startup. If another app uses thos
279
279
  **Channel shim fails to start**
280
280
  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.
281
281
 
282
+ If the shim starts but logs `/api/events timed out after 10000ms`, `SSE inactivity timeout`, or `/api/channel-reply timed out after 5000ms`, the Tandem server accepted a connection but stopped responding on that path. Restart Tandem; the shim reports the timeout instead of hanging silently.
283
+
282
284
  **Editor shows "Cannot reach the Tandem server"**
283
285
  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.
284
286
 
@@ -287,18 +289,18 @@ On first run, `sample/welcome.md` auto-opens. If you've cleared sessions or dele
287
289
 
288
290
  ## Development
289
291
 
290
- | Command | What it does |
291
- |---------|-------------|
292
- | `npm run dev:standalone` | **Recommended** — both frontend + backend (via concurrently) |
293
- | `npm run dev:server` | Backend only: Hocuspocus (:3478) + MCP HTTP (:3479) |
294
- | `npm run dev:client` | Frontend only: Vite dev server (:5173) |
295
- | `npm run build` | Production build (`dist/server/` + `dist/channel/` + `dist/cli/` + `dist/client/`) |
296
- | `npm test` | Run vitest (unit tests) |
297
- | `npm run test:e2e` | Run Playwright E2E tests |
298
- | `npm run test:e2e:ui` | Playwright UI mode |
299
- | `cargo tauri dev` | Tauri desktop app (dev mode with hot-reload) |
300
- | `cargo tauri build` | Tauri production build (installer output) |
292
+ | Command | What it does |
293
+ | ------------------------ | ---------------------------------------------------------------------------------- |
294
+ | `npm run dev:standalone` | **Recommended** — frontend + backend + monitor |
295
+ | `npm run dev:server` | Backend only: Hocuspocus (:3478) + MCP HTTP (:3479) |
296
+ | `npm run dev:client` | Frontend only: Vite dev server (:5173) |
297
+ | `npm run build` | Production build (`dist/server/` + `dist/channel/` + `dist/cli/` + `dist/client/`) |
298
+ | `npm test` | Run vitest (unit tests) |
299
+ | `npm run test:e2e` | Run Playwright E2E tests |
300
+ | `npm run test:e2e:ui` | Playwright UI mode |
301
+ | `cargo tauri dev` | Tauri desktop app (dev mode with hot-reload) |
302
+ | `cargo tauri build` | Tauri production build (installer output) |
301
303
 
302
304
  **Tauri development** requires the [Rust toolchain](https://www.rust-lang.org/tools/install) and [Tauri CLI](https://v2.tauri.app/start/prerequisites/). Web-only development (`npm run dev:standalone`) does not require Rust.
303
305
 
304
- **Tech Stack:** React 19, Tiptap, Vite, TypeScript | Node.js, Hocuspocus (Yjs WebSocket), MCP SDK, Express | Yjs (CRDT), y-prosemirror | mammoth.js (.docx), unified/remark (.md)
306
+ **Tech Stack:** Svelte 5, Tiptap, Vite, TypeScript | Node.js, Hocuspocus (Yjs WebSocket), MCP SDK, Express | Yjs (CRDT), y-prosemirror | mammoth.js (.docx), unified/remark (.md)