tandem-editor 0.9.1 → 0.11.1

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
@@ -2,10 +2,131 @@
2
2
 
3
3
  All notable changes to Tandem will be documented in this file.
4
4
 
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
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
+ ## [Unreleased]
9
+
10
+ ## [0.11.1] - 2026-05-13
11
+
12
+ ### Added
13
+
14
+ - **Settings sidebar redesign (PR #600)** — pulls the Claude Design redesign's settings sidebar pattern into the existing tabbed popover. Each nav item gains an inline SVG icon; the sidebar header carries a live version chip from `/api/info`; a new persistent sidebar footer surfaces Changelog, Report-a-bug, and an MCP-connected status dot from every section instead of hiding them under About. `TANDEM_REPO_URL` and `TANDEM_ISSUES_NEW_URL` extracted to `shared/constants` and the three repo links routed through them.
15
+ - **Single titlebar consolidating all app chrome (PR #602)** — Tandem brand, Solo/Tandem mode toggle, Claude-active dot, authorship toggle, panel toggles, theme cycle, help, and settings are now in one titlebar row; the secondary toolbar row is deleted. Comment and Note now live exclusively in the floating selection popup (`popup-annotation-input`). Titlebar background uses `--tandem-surface-muted` so it blends with the formatting bar instead of looking like a lighter Windows chrome strip. `THEME_NEXT`/`THEME_LABEL` lookup tables replace the nested-ternary theme rotation; the orphan `useTabDirty` hook and stale `TitleBar.test.ts` are deleted. New testid: `title-bar`.
16
+
17
+ ### Changed
18
+
19
+ - **`@tauri-apps/api` bumped to 2.11.x** — Tauri CLI v2.11+ enforces major.minor parity between the Rust crate and the npm package. The crate is at 2.11.1; the npm package was at 2.10.1, causing all platform builds to fail. This restores Tauri build parity.
20
+
21
+ ### Fixed
22
+
23
+ - **Release-mode sidecar always spawns its own server (PR #601)** — a stale `tsx watch src/server/index.ts` from a `npm run dev:server` session was squatting on ports 3478/3479 and answering `/health`, so `start_sidecar` reused it and left the installed app's UI stuck on "Disconnected" — hover and right-click working, but every action that needed the server failing silently because the auth/session state didn't match. The `check_health` early-return is now gated on `cfg!(debug_assertions)` so the dev workflow (`cargo tauri dev` + `npm run dev:standalone`) still benefits from health-reuse, but installed builds never reuse a foreign sidecar. Also drops `stdio: "ignore"` from the Windows `taskkill` path so any future port-bind failure surfaces in logs instead of silently failing; `freePortWindows` now uses `execFileSync` (no shell).
24
+ - **CHANGELOG.md no longer rewritten on upgrade (PR #603)** — on version upgrade the app auto-opens `CHANGELOG.md` so the user can see what changed. Previously this opened writable, and the 60-second autosave timer would round-trip the file through `remark-stringify` with default escaping, leaving cosmetic backslash-escape noise on disk (`[1.0.0]` → `\[1.0.0]`, escaped underscores and backticks). The upgrade auto-open path now passes `readOnly: true`, matching the existing "View Changelog" button in Settings; autosave skips read-only documents so the file is not re-serialized. The underlying `remark-stringify` over-escape (which affects every `.md` round-tripped through Tandem) is tracked as #605 for v0.12.0.
25
+ - **Display name truncated to `USER_NAME_MAX_LEN` (PR #604, closes #599)** — `resolveUserName` now slices to the 40-char limit, plugging the gap where `persistUserName` and `subscribeToUserName`'s storage handler could persist or broadcast unbounded names from programmatic `setUserName` calls or legacy localStorage values. The `<input maxlength>` cap on the typed-input path remains; this fix covers the non-UI entry points. One slice in `resolveUserName` covers all three callsites.
26
+ - **Tutorial annotations now actually appear in `welcome.md` (PR #607)** — commit `acb81fd` (Mar 2026) rewrote welcome.md's intro paragraph but didn't update the matching `targetText` anchors in `tutorial-annotations.ts`. `indexOf("collaborative document editor")` and `indexOf("review your documents")` both returned -1 and 2 of 4 tutorial annotations silently failed to inject. Re-pointed the anchors to phrases that exist in current welcome.md (each occurs exactly once).
27
+ - **Titlebar review fixes (part of PR #602)** — async `onMount` listener-leak guard (in-flight Tauri-API awaits self-clean if the component unmounts mid-resolve), window controls correctly show disabled state when Tauri init fails, action-oriented theme button labels for WCAG 4.1.2 ("Switch to dark theme" describes the click outcome, not the current state), and `setup_overlay_titlebar` failure logged at error severity. New regression test `titlebar-listener-leak.test.ts` covers the unmount-mid-await path.
28
+ - **Settings changelog error surfacing (part of PR #600)** — the Changelog button lives in the always-rendered sidebar footer, but its error message used to live inside the About panel's `{:else}` arm — users on any other section got no feedback when a fetch failed. The error display now sits adjacent to the button and clears on section change.
29
+
30
+ ### Internal
31
+
32
+ - **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`.
33
+
34
+ ## \[0.11.0] - 2026-05-11
35
+
36
+ ### Added
37
+
38
+ - **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}`.
39
+ - **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).
40
+ - **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.
41
+ - **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`.
42
+ - **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`.
43
+ - **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}`.
44
+ - **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.
45
+ - **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}`.
46
+ - **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.
47
+ - **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.
48
+ - **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.
49
+ - **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.
50
+ - **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.
51
+ - **Theme-color meta tag sync** — `<meta name="theme-color">` updates reactively when the app theme changes, improving desktop PWA and mobile browser chrome appearance.
52
+
53
+ ### Changed
54
+
55
+ - **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`.
56
+ - **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.
57
+ - **Redesign bundle checked into&#x20;****docs/redesign-bundle/****&#x20;(#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.
58
+ - **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.
59
+ - **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.
60
+ - **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.
61
+ - **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).
62
+ - **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`.
63
+
64
+ ### Changed
65
+
66
+ - **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)
67
+ - **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)
68
+ - **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)
69
+ - **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/`.
70
+ - **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.
71
+
72
+ ### Tests
73
+
74
+ - **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.
75
+
76
+ ### Removed
77
+
78
+ - **ReviewSummary****&#x20;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.
79
+
80
+ ### Fixed
81
+
82
+ - **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.
83
+ - **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.
84
+ - **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)
85
+ - **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.
86
+ - **Toolbar**: HighlightColorPicker border now uses `--tandem-border` token, correctly adapting to light/dark theme switching (#536)
87
+ - **Theme system: Tauri shell now reads Windows app-mode preference (****AppsUseLightTheme****) for&#x20;****theme: "system"****&#x20;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.
88
+ - **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).
89
+ - **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.
90
+ - **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)`.
91
+
92
+ ## \[0.10.1] - Unreleased
93
+
94
+ Plugin URL and auth resolution for custom-port and network-remote setups.
95
+
96
+ ### Changed
97
+
98
+ - **Monitor and channel honor&#x20;****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`.
99
+ - **Monitor and channel honor&#x20;****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.
100
+
101
+ ## \[0.10.0] - 2026-05-03
102
+
103
+ 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.
104
+
105
+ ### Removed
106
+
107
+ - **react****,&#x20;****react-dom****,&#x20;****@tiptap/react****&#x20;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.
108
+ - **tandem\_suggest****,&#x20;****tandem\_flag****,&#x20;****tandem\_highlight****&#x20;hard-removed** — stub tools deprecated in v0.9.0 (ADR-027) are now fully removed. MCP tool count: 28 → 25.
109
+
110
+ ### Changed
111
+
112
+ - **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.
113
+ - **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.
114
+
115
+ ### Fixed
116
+
117
+ - **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.
118
+ - **Note privacy —&#x20;****tandem\_getAnnotations****&#x20;and channel events never surface notes to Claude** — `type: "note"` entries are filtered from MCP tool responses and SSE channel events (Codex security review).
119
+ - **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).
120
+ - **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).
121
+ - **annotation:edited****&#x20;channel event deduplication** — rapid successive edits no longer emit duplicate events to the channel (Codex security review).
122
+ - **svelte-check --fail-on-warnings****&#x20;now gates the build** — 26 pre-existing Svelte type warnings cleared; CI enforces zero-warning policy going forward.
123
+
124
+ ### Added
125
+
126
+ - **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.
127
+ - **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"`.
128
+ - **Form label associations (#511, #524)** — AnnotationEditForm inputs are now properly associated with their `<label>` elements.
129
+ - **AnnotationCard role corrected (#511, #524)** — changed from `role="button"` (nested-button violation) to `role="listitem"`.
9
130
 
10
131
  ## \[0.9.1] - 2026-05-01
11
132
 
@@ -31,26 +152,26 @@ Hotfix patch bundling ADR-027 surface cleanup and file-I/O correctness fixes bef
31
152
 
32
153
  This is the last breaking-change window before semver lock. MCP tool count: 31 → 28.
33
154
 
34
- - **`tandem_suggest` deprecated (#259)** — returns a structured error stub pointing to `tandem_comment` with `suggestedText`. Hard-remove in v0.10.0.
35
- - **`tandem_getContent` removed (#259)** — superseded by `tandem_getTextContent`.
36
- - **`tandem_getSelections` removed (#259)** — superseded by `tandem_checkInbox`.
37
- - **`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.
38
- - **`tandem_flag` deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Use `tandem_comment` instead. Hard-remove in v0.10.0.
39
- - **`tandem_highlight` deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Highlights are user-only. Hard-remove in v0.10.0.
40
- - **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.
155
+ - **tandem\_suggest****&#x20;deprecated (#259)** — returns a structured error stub pointing to `tandem_comment` with `suggestedText`. Hard-remove in v0.10.0.
156
+ - **tandem\_getContent****&#x20;removed (#259)** — superseded by `tandem_getTextContent`.
157
+ - **tandem\_getSelections****&#x20;removed (#259)** — superseded by `tandem_checkInbox`.
158
+ - **tandem\_setStatus****&#x20;merged into&#x20;****tandem\_status****&#x20;(#259)** — `tandem_status` now accepts optional write params (`text`, `focusParagraph`, `focusOffset`). When params are present it writes to awareness; when absent it reads.
159
+ - **tandem\_flag****&#x20;deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Use `tandem_comment` instead. Hard-remove in v0.10.0.
160
+ - **tandem\_highlight****&#x20;deprecated (ADR-027, #473)** — returns a `DEPRECATED` error stub. Highlights are user-only. Hard-remove in v0.10.0.
161
+ - **Annotation&#x20;****directedAt****&#x20;field removed (ADR-027, #473)** — silently ignored on input; stripped from on-disk records via `sanitizeAnnotation` and the `normalizeAnnotation` fast path on read.
41
162
 
42
163
  ### Added
43
164
 
44
- - **`/api/info` endpoint (#441)** — returns app version, MCP SDK version, tool count, data directory, and platform. Serves the Settings panel About footer.
165
+ - **/api/info****&#x20;endpoint (#441)** — returns app version, MCP SDK version, tool count, data directory, and platform. Serves the Settings panel About footer.
45
166
  - **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`.
46
167
  - **App version in Settings (#435)** — `useAppInfo` hook fetches `/api/info` and displays version + MCP SDK version in the Settings popover footer.
47
168
  - **View Changelog button (#437)** — Settings panel button opens `CHANGELOG.md` as a read-only document tab via `POST /api/open` with `readOnly: true`.
48
- - **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.
169
+ - **Authorship&#x20;****data-tandem-author****&#x20;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.
49
170
  - **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%.
50
171
  - **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.
51
172
  - **CI stdio smoke test (#341)** — GitHub Actions step validates the Cowork stdio bridge (`scripts/ci/stdio-smoke.mjs`) on every push.
52
- - **`__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.
53
- - **`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.
173
+ - **\_\_MCP\_SDK\_VERSION\_\_****&#x20;build-time injection** — tsup reads the real SDK version from the package root (not the CJS type marker) and injects it at build time.
174
+ - **tandem\_getAnnotations****&#x20;****includeImports****&#x20;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.
54
175
  - **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.
55
176
 
56
177
  ### Fixed
@@ -63,7 +184,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
63
184
  - **Event-bridge error handling** — uncaught errors in SSE delivery no longer crash the event loop.
64
185
  - **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.
65
186
  - **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.
66
- - **`normalizeReply` validation (#473)** — replies are now Zod-validated before being merged; malformed entries are dropped + logged instead of poisoning the envelope.
187
+ - **normalizeReply****&#x20;validation (#473)** — replies are now Zod-validated before being merged; malformed entries are dropped + logged instead of poisoning the envelope.
67
188
 
68
189
  ### Changed
69
190
 
@@ -86,9 +207,8 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
86
207
  ### Added
87
208
 
88
209
  - **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.
89
-
90
210
  - **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.
91
- - **`--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.
211
+ - **--tandem-suggestion-\*****&#x20;token family (#340)** — violet semantic tokens for replacement/suggestion annotations (`--tandem-suggestion`, `-fg-strong`, `-bg`, `-border`), visually distinct from the indigo accent family.
92
212
  - **Annotation drop count surfacing (#351)** — `normalizeAnnotation` now returns drop counts in snapshot metadata so callers can detect lossy session migrations.
93
213
  - **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.
94
214
  - **Persistent annotation undo (#415)** — undo state survives panel switches and scrolling; persists until page reload instead of clearing on the next render cycle.
@@ -135,11 +255,11 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
135
255
 
136
256
  - **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.
137
257
  - **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.
138
- - **`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.
139
- - **`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).
258
+ - **TANDEM\_BIND\_HOST****&#x20;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.
259
+ - **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).
140
260
  - **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.
141
261
  - **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.
142
- - **`/health` session-presence guard** — `hasSession` is omitted from `/health` responses on non-loopback requests, preventing session-presence leakage on LAN binds.
262
+ - **/health****&#x20;session-presence guard** — `hasSession` is omitted from `/health` responses on non-loopback requests, preventing session-presence leakage on LAN binds.
143
263
 
144
264
  ### Security
145
265
 
@@ -160,14 +280,14 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
160
280
 
161
281
  - **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.
162
282
  - **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.
163
- - **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.
283
+ - **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.
164
284
  - **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.
165
285
 
166
286
  ### Changed
167
287
 
168
288
  - **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.
169
- - **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.
170
- - **`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.
289
+ - **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.
290
+ - **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.
171
291
 
172
292
  ### Internal
173
293
 
@@ -212,9 +332,9 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
212
332
 
213
333
  ### Fixed
214
334
 
215
- - **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.
335
+ - **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.
216
336
  - **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.
217
- - **`/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.
337
+ - **/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.
218
338
  - **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.
219
339
  - **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.
220
340
  - **Defensive exit on monitor fallthrough** — the retry loop exits 1 if it ever terminates without hitting the explicit exhaustion path.
@@ -224,7 +344,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
224
344
  ### Added
225
345
 
226
346
  - **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`.
227
- - **`--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.
347
+ - **--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.
228
348
 
229
349
  ### Changed
230
350
 
@@ -238,7 +358,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
238
358
  - **Exponential backoff on reconnect** — monitor reconnects use 2s/4s/8s/16s/30s backoff instead of a fixed 2s delay.
239
359
  - **SIGINT/SIGTERM clears awareness** — monitor posts a final `clearAwareness` before exit so the "Claude is active" indicator doesn't hang in the browser.
240
360
  - **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.
241
- - **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.
361
+ - **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.
242
362
  - **SKILL.md corrected** — `question` annotation guidance now uses `type === 'comment' && directedAt === 'claude' && author === 'user'`; all 5 highlight colors listed (yellow, red, green, blue, purple).
243
363
 
244
364
  ## \[0.5.0] - 2026-04-13
@@ -305,7 +425,7 @@ This is the last breaking-change window before semver lock. MCP tool count: 31
305
425
  - **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)
306
426
  - **Toolbar:** Three annotation buttons (Comment, Suggest, Ask Claude) replaced with a single Comment button with "Replace" and "@Claude" toggles (#193)
307
427
  - **Side panel filters:** "Suggestions" → "With replacement", "Questions" → "For Claude" (#193)
308
- - **`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)
428
+ - **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)
309
429
 
310
430
  ### Added
311
431
 
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
@@ -303,4 +303,8 @@ On first run, `sample/welcome.md` auto-opens. If you've cleared sessions or dele
303
303
 
304
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.
305
305
 
306
- **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)
307
+
308
+ ## License
309
+
310
+ Tandem is licensed under the [Business Source License 1.1](LICENSE). See `LICENSE` for full terms.