tandem-editor 0.14.0 → 0.14.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.14.2] - 2026-06-14
11
+
12
+ ### Changed
13
+
14
+ - **Motion — hover-reveal float reads as the extended rail and slides both ways (Phase 4 / #798, scenes A8·A12 follow-up)** — polish on the v0.14.1 hover-reveal floating mode. (1) **Reads as the same rail, not a square overlay** — the floated panel now carries the extended rail's **rounded inner corner** and paints its own opaque `--tandem-surface-muted` surface (without it the 14px collapsed shell shows through as a minimized-rail strip and the editor shows behind the rest); the edge-collapse grab handle is hidden while floating (its accent bar otherwise reads as a stray sliver, and "collapse" is the wrong verb — the zone PINS); and pinning **snaps** the shell to the floated width for one frame (a transient `pin-snap` transition-suppressor cleared on the next rAF) so the panel stays put instead of flashing away and replaying the 14→full open from collapsed. (2) **Drop shadow no longer clipped at the editor edge** — the directional side shadow is cast by a dedicated empty `.rail-float-shadow` layer painted behind the panel rather than as a `box-shadow` on the panel itself, so the panel's own `overflow: hidden` (needed to clip content to the rounded corner) can't slice the shadow on the rounded side. And **both pinned rails now carry an at-rest `z-index: 1`** so the editor column — a later DOM sibling whose opaque background would otherwise occlude the rail's outset shadow, cutting it off dead at the panel's inside edge — paints below it; the left rail had been missing this (the right rail always had it), so only the left rail's pinned shadow was being clipped. (3) **Slides in and out** — the float-in is retimed to a `360ms` rigid-body `translateX` slide that reads as the minimized sliver sliding open (the leading edge and its drop shadow travel together; `translateX` never clips the shadow), and on hide the panel now **slides back into the edge** instead of vanishing: a `float-closing` retreat phase keeps it mounted for a `300ms` reverse slide (the slide-out keyframe's `forwards` fill holds it tucked away until a matched timer drops the flag and `display:none` returns), and re-hovering mid-retreat cancels the retreat and snaps straight back to floating. Pinned rails don't retreat; reduced motion (the in-app `reduceMotion` setting OR the OS `prefers-reduced-motion` query — the retreat path checks the OS query in JS too, so it drops straight to the sliver instead of lingering for the closing timer) disables both slides. `.svelte`-only (`src/client/App.svelte`): no `src/server/` changes, no annotation-model change, every rail testid (`rail-float-left`/`rail-float-right`, `left-outline-rail`, `peek-strip-*`, `panel-edge-collapse-*`, `*-resize-handle`) preserved. Diff reviewed by `svelte-migration-reviewer` (CLEAR — the new `maybeHideFloat` idempotency guard prevents a double-schedule; timer cleanup is symmetric).
15
+
16
+ ### Fixed
17
+
18
+ - **Cowork enable no longer promises a UAC prompt that never appears, or scares users about an "unprotected" port (interim honesty fixes; full transport redesign pending an empirical matrix + ADR-045).** Enabling Cowork on Windows added a Windows Firewall rule via a plain `netsh` spawn that **never** triggers UAC elevation (elevation only happens via `ShellExecuteEx`+`runas`), so a non-elevated Tandem always failed and mislabeled the failure as "UAC declined." The fail-closed deny-rule it then tried to write needed the *same* elevation and always failed too, producing a false "Port 3479 may be unprotected — add a deny rule manually" scare (the server binds `127.0.0.1`, so the port was never network-exposed). Detected workspaces were also shown as **"Failed"** even when no write was ever attempted (the flow aborts before any plugin write). This interim fix: drops the false "Windows will prompt for admin permission to modify firewall rules" copy from the Settings toggle, the onboarding step, and the integration wizard; removes the unwritable deny-rule attempt and its alarming error; rewrites the admin-declined hint to honestly explain that firewall changes need administrator rights Tandem doesn't have (and that documents stay local); reports never-attempted workspaces as a neutral **"Not configured"** (new `notConfigured` status, info-colored) instead of "Failed"; and corrects the "Token provisioned" mislabel (it only ever reflected the enabled flag). The underlying transport (whether Cowork can reach Tandem at all, and whether the firewall/per-workspace machinery is even needed) is being settled by a test matrix; see the cowork-detection plan / forthcoming ADR-045. No `netsh`/elevation behavior beyond the deny-rule removal changed.
19
+
20
+ ## [0.14.1] - 2026-06-11
21
+
22
+ ### Added
23
+
24
+ - **Motion — rail open/close + hover-reveal floating mode (Phase 4 / #798, scenes A8·A12)** — the side rails (left outline, right annotations/chat) now **ease** between open and closed instead of snapping, and gain an optional **hover-reveal** mode that lands the A8 rail-reveal + A12 peek-collapse work previously deferred from the mode-toggle and tab-close clusters. (1) **Width/shadow transition** (`360ms`/`280ms` on the `--tandem-ease-out`-shaped bezier) on the always-mounted dual-layer shell. Because `display:none` can't transition and is load-bearing (it kills the editor scroll-pop and drops the rail from the Tab order at rest), the collapse-direction `display:none` is deferred by a per-side `animating` flag until the width transition ends (cleared on the shell's *own* `width` transitionend — guarded `e.target === e.currentTarget` so the floating panel's bubbled transform/opacity transitionends can't clear it early — with a 400ms timeout backstop), so the content clips away with the shrinking width then drops out cleanly. (2) **Hover-reveal floating mode** (new `railHoverReveal` setting, default on; `appearance-rail-hover-reveal` toggle): hovering a *closed* rail floats its full panel **over** the editor while the 14px collapsed shell stays in flow, so the editor never reflows; the panel slides in from the window edge (`@keyframes`, 280ms). Clicking the edge/peek zone while floating **pins** it (reusing the existing `toggle*Panel`, which pins when the rail isn't visible; the transient float flag is cleared before the visibility commit so there's no intermediate frame). The float is transient — it auto-hides when *neither* the pointer nor focus is inside, with a `focusout` path that closes the focus-sticky case (e.g. the outline `jumpTo` moving focus into the editor) so it can't strand open. Hover timers are plain non-`$state` refs (clear-before-schedule, unmount-only cleanup). When the setting is off, the classic 14→28px peek-grow returns (CSS-gated on a `body.tandem-rail-hover-reveal` class). Both behaviors fully honor reduced motion (the in-app `reduceMotion` setting via `body.tandem-reduce-motion` + OS `prefers-reduced-motion`): the width snaps (the JS also skips the `animating` flag, so `display:none` applies synchronously) and the float-in keyframe is disabled. (3) **Rail drop shadows** strengthened slightly in light + dark (`--tandem-rail-shadow-left/right`) so the floating chrome reads with more depth; a new `--tandem-z-rail-float` token sits the float above the editor and the right rail's at-rest `z-index`, below dropdowns so in-panel menus still escape. Schema v14→v15 (pure version bump; missing field defaults on, explicit `false` preserved). New testids `rail-float-left`/`rail-float-right` (present only while floating); every existing rail testid (`left-outline-rail`, `peek-strip-*`, `panel-edge-collapse-*`, `*-resize-handle`) preserved. `.svelte`/`index.html`/settings-only: no `src/server/` changes, no annotation-model change. Plan reviewed pre-code by svelte-migration + general adversarial agents (which caught the transitionend bubble guard, the explicit `normalizeKnownFields` edit + schema-bump convention, the plain-`let` timer discipline, and the focus-sticky stuck-open hole before any code).
25
+
26
+ ### Fixed
27
+
28
+ - **"Relaunch Claude" works on the desktop app — the reaper binary is now bundled, and launcher errors are no longer swallowed** — the auto-launcher spawns Claude Code *through* the `tandem-reaper` binary (so Claude dies when Tandem does), but the reaper was never packaged into the Tauri desktop app: it was absent from `externalBin` and no build step compiled it. So **every** desktop build since the launcher shipped (PR #800) failed "Relaunch Claude" / "Start fresh" with `tandem-reaper binary not found` — and the failure was invisible because the launcher API returned a hardcoded `"relaunch failed"` to the UI while logging the real reason only to stderr (the toast read *"Relaunch failed: relaunch failed."*). Now: (1) a new `scripts/build-reaper.mjs` compiles the `reaper/` crate into `src-tauri/binaries/tandem-reaper-<triple>[.exe]`, wired into the release/webdriver workflows, the CI `tauri_build` stubs, `npm run dev:tauri`, and a new `npm run build:reaper`; `tandem-reaper` is added to `externalBin` and the macOS release verification now asserts it's bundled (the check that would have caught this). (2) The launcher API surfaces the **real** error reason (bounded to 300 chars) instead of the static label; for the missing-reaper case it returns a friendly message (*"Claude launcher binary missing — reinstall Tandem to restore it."*, which the toast renders verbatim) plus a stable `REAPER_NOT_FOUND` code for programmatic use. `reaperPath()` already resolves adjacent to the sidecar, which is exactly where `externalBin` lands the binary. (3) **Async spawn failures now surface too** — a reaper that exists at check time but can't actually exec (wrong arch, not executable, a swap after the check) reports its failure via `spawn()`'s *asynchronous* `error` event, which fired *after* the relaunch route had already returned `{ ok: true }`; the supervisor now awaits the immediate spawn outcome (the `spawn` event, or an early `error`/`exit`) so that class of failure reaches the toast — mapped to the same `REAPER_NOT_FOUND` hint — instead of looking like success. **Also fixed:** `tandem doctor` no longer false-flags a valid `store.lock` as "unparseable content" — it now reads both the bare-integer-PID and JSON-object (`{"pid":…}`) lock formats, and tolerates a malformed JSON lock (wrong-typed or absent `pid`) as unparseable rather than mis-reading it. **macOS:** the reaper's kqueue watch was ported to the `nix` 0.29 `Kqueue::kevent` method (the old free-function + `as_raw_fd` form no longer compiles, and a `0` timeout would have busy-polled a core), so it now blocks on `EVFILT_PROC`/`NOTE_EXIT` correctly — the v0.14.1 macOS build is the first to bundle a reaper that compiles. (Desktop end-to-end relaunch click is a manual Tauri pass.)
29
+ - **Cowork now detects a direct-download Claude Desktop install and self-heals workspaces that appear after enable (Windows; ADR-044)** — Cowork detection only scanned the MSIX Store layout (`%LOCALAPPDATA%\Packages\Claude_*\…`), so the common direct-download Claude Desktop install — whose sessions live at `%APPDATA%\Claude\local-agent-mode-sessions` — always reported "Not detected on this computer". Detection now scans **both roots** (MSIX + Roaming), dedupes exact aliases, and caps workspaces per root. The MSIX package match is publisher-anchored (`Claude_*` | `AnthropicPBC.Claude*`) rather than a loose `contains("Claude")`, so a foreign package sharing the sandbox can never receive the token, and a workspace-shape guard (UUID-or-`cowork_plugins`-marker) keeps plugin-registry writes off `skills-plugin\…` siblings under the Roaming root. `check_acl` now **fails closed** and allows only the Roaming Claude sessions subtree (where the token already lives via `claude_desktop_config.json` — no new exposure class). A 5-minute background **heal pass** installs into workspaces that appear after Cowork is enabled (read-only precheck, never any firewall/UAC work), recording its per-process attempt only on **terminal** outcomes (success / insecure-ACL) so a transiently-failing workspace (locked / schema-drift) stays retryable instead of being stranded until restart. Write-time path revalidation now covers the non-handle write paths too (enable / rescan / heal / apply-to-all / orphan-reconcile), closing the residual #433 scan→write TOCTOU. The wizard's undetected state carries an honest sub-reason (no Claude / no workspaces yet / blocked), and **Check again** refetches Cowork. Windows-only; no `src/server/` or annotation-model change.
30
+ - **`tandem doctor` no longer flags a valid lockfile as "unparseable content"** — the annotation store-lock check still parsed the lock as a bare PID (`parseInt`), but locks have been v2 JSON (`{pid, startedAtMs, app}`) since #1077. A healthy lock therefore reported a spurious warning ("unparseable content: `{…}`"). The doctor now reuses the store's `parseLockfile` (which reads both v2 JSON and legacy raw-PID formats) so a live lock reports the holder PID and liveness correctly. The pure lock format/parsing moved to `src/server/annotations/lockfile.ts` so the CLI doctor can read a lock without pulling in the store's file-io/notifications/platform dependency graph; `store.ts` re-exports it unchanged.
31
+
10
32
  ## [0.14.0] - 2026-06-10
11
33
 
12
34
  ### Added
package/LICENSE CHANGED
@@ -6,10 +6,13 @@ Parameters
6
6
  Licensor: Bryan Kolb
7
7
  Licensed Work: Tandem (tandem-editor npm package).
8
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.
9
+ Additional Use Grant: Personal use and individual self-hosting are permitted
10
+ solely for evaluation purposes for up to 30 days; continued
11
+ or production use requires a paid license from the Licensor.
12
+ Commercial hosting or resale of the Licensed Work is not permitted.
11
13
  Change Date: The earlier of 2029-06-10 or two years after the public
12
- general availability release of Tandem v1.0.
14
+ general availability release of this specific version of the
15
+ Licensed Work.
13
16
  Change License: MIT License
14
17
 
15
18
  Notice
package/README.md CHANGED
@@ -115,7 +115,7 @@ See [docs/security.md](docs/security.md) for the full security model.
115
115
 
116
116
  ## Where Tandem is headed
117
117
 
118
- Tandem is on the way to a v1.0 release. Recent releases added support for multiple AI providers and in-app configuration for connections and models. Work still in progress covers improvements to how Word documents round-trip through the editor, turnkey setup on macOS and Linux, and final polish. Tandem is free during the public beta; at v1.0 it moves to a one-time paid license, and beta users are grandfathered with a free license. The full plan lives in [docs/roadmap.md](docs/roadmap.md).
118
+ Tandem is on the way to a v1.0 release. Today the supported AI integration is Claude (Claude Code / Claude Desktop) over MCP, set up with a one-click in-app wizard. Local models (Ollama, LM Studio) are committed for v1.0 and in active development (#1123) — they'll use the same one-time license as everything else; cloud API-key providers (OpenAI, Gemini) follow in v1.1. Recent releases added Word document write-back (edit a `.docx` and save it back as a real Word file — comments you've sent to your AI are written back as native Word comments) and pre-overwrite backups with in-product restore. Work still in progress covers turnkey setup on macOS and Linux, licensing, and final polish. Tandem is free during the public beta; at v1.0 it moves to a one-time paid license, and beta users are grandfathered with a free license. The full plan lives in [docs/roadmap.md](docs/roadmap.md).
119
119
 
120
120
  ## Documentation
121
121
 
@@ -163,7 +163,7 @@ Client compatibility:
163
163
  | **Claude Desktop** (local app) | Supported via the Cowork bridge. Channel push N/A. |
164
164
  | **claude.ai web chat** | Not supported. Would require exposing the local server publicly via a tunnel, which is outside scope. |
165
165
  | **Other MCP-capable clients** (Cursor, Continue.dev, LM Studio, Ollama, …) | Best-effort, MCP-contract-compatible, not validated. |
166
- | **Non-MCP AIs** (ChatGPT direct, Gemini direct, etc.) | Not supported today. Multi-provider support is in progress via the Agent SDK adapter ([ADR-038 §3](docs/decisions.md#adr-038-mcp-first-integration-policy-claude-as-default-integration)); not yet shippable. |
166
+ | **Non-MCP AIs** | Not supported today. **Local models** (Ollama / LM Studio via OpenAI-compatible endpoints) are committed for v1.0 ([ADR-039](docs/decisions.md#adr-039-non-mcp-model-providers-local-slice-v10-cloud-slice-v11), tracked in #1123); cloud providers (ChatGPT direct, Gemini direct) follow in v1.1. |
167
167
 
168
168
  ### MCP tools at a glance
169
169