June 20, 2026

Mobile pairing ships, transcript GC repaired, interface polished

TKT-1099 mobile pairing/unlock flow ships end-to-end

TKT-1099 mobile pairing/unlock flow ships end-to-end. A Brain “Link device” page mints a one-shot bearer and renders a PairingPayload QR; the interface detects Tauri+no-token and diverts to /pairing/; an UnlockVault overlay polls /auth/status and unseals via /auth/login. Backend lifts cookie-only auth to two consumers and adds GET /auth/username so the QR embeds the login username. The WebSocket /ws handshake accepts ?token= for mobile, and ApiClient + WS attach bearer tokens (no-op when absent). Shared gains a tauriPlatformAdapter skeleton with a window.TAURI selector, lazy-imported @tauri-apps plugins (kept out of the web bundle), a canonical PairingPayload contract, and host.ts localStorage accessors.

Transcript GC gets two surgical fixes (TKT-1097 + TKT-1102). The cleanup_unlinked_entries query looked for tool_calls/$.status=‘success’ rows the redesigned compactor never wrote, so all-channels decay silently deleted nothing; now it reads transcript WHERE role=‘compaction’, the same source the compactor uses. Separately, the standalone 7-day tool_calls purge is folded into the transcript cleaner (children before parent), repairing a latent FK constraint bug where PRAGMA foreign_keys=ON made DELETE FROM transcript fail for any assistant turn that had called a tool, silently swallowed by the outer except. One cleanup path instead of two.

Security hardening closes several LAN attack surfaces. /voice/{health,synthesize,transcribe} now requires auth (cookie-session with bearer fallback), blocking unauthenticated STT/TTS compute burn and the /voice/health absolute path leak. The /api/snapshot/import handler splits SnapshotError (curated rejection messages) from generic exceptions (full detail logged server-side, generic message to client), stopping pyzipper bad-password detail and absolute paths from leaking. The MCP policy DELETE used LIKE ‘mcp_%’ without ESCAPE, so the underscore wildcard nuked colliding servers’ lazily-provisioned policies (deleting taskie wiped taskie2/taskieX); now reads the server’s tool_names and runs one exact-match delete per tool.

The interface gets a polish pass: a new Processes panel splits the tasks drawer into reminders + active delegates (each delegate showing a live elapsed timer and Stop button that flips cancel_event through the existing chain); backend stores per-delegate metadata and broadcasts subagent_start/end over WS. Live “thinking…” anchor + breathing PresenceBar logo replace the dead presence store. Consecutive collapsed ACT cycles fold into a collapsible group. Many small fixes land: turn-scoped remember/speak on the last Chalie row, voice player anchored to top, light-theme transform leak that made the presence bar scroll away, Recall overlay using real themed tokens, login card widened to 420px, textarea-only send (Enter inserts newline), attachment list + image lightbox, right-aligned user message at 90%, mobile gutters.

Frontend refactors: a lean-cleanup pass removes 312 LOC of single-use locals and grep-verified dead code across 53 files; a comment-strip pass removes 2049 more LOC (TS transpile-compare proves byte-identical output). @lucide/vue replaces every hand-rolled inline icon SVG across 25 components (-203 LOC), dropping the brain BrainIcon.vue v-html icon system. Brain UI restoration ports three regressions lost in the Vue cutover (provider-delete guard, full-width panels, micro-alignment) and reveals the app-shell via a data-ready ref.

Type system and cleanup: mypy --strict passes cleanly across 473 files after typing 5 production packages and 45 feature-test files in place (zero logic change, AST-equivalent narrowing only). TKT-800 deletes the dormant mode-gate classifier + shipped ONNX head (~796KB) + tests. Capability tool handlers drop the legacy 4-arg signature; dispatch_capability_handler and three dead consumers are removed. markdown-it-py and brotli are dropped (TTS text-cleaner now uses 6 local regexes). Transcript converts from free functions to a Transcript static class with turn-based feed pagination.

  • TKT-1099 mobile pairing/unlock ships end-to-end: QR on Brain /brain/link-device, UnlockVault overlay polling /auth/status, /pairing/ auth gate, bearer token on HTTP+WS, ?token= on /ws handshake, tauriPlatformAdapter with lazy @tauri-apps imports kept out of the web bundle

  • Transcript GC fixes (TKT-1097, TKT-1102): cleanup_unlinked_entries repointed at role=‘compaction’ watermark (was silent no-op); 7-day tool_calls purge folded into transcript cleaner, repairing latent FK constraint bug that silently swallowed DELETE FROM transcript failures

  • Security: /voice/* routes require @require_auth (were LAN-open STT/TTS compute + path leak); /api/snapshot/import sanitizes non-SnapshotError exceptions; MCP policy DELETE switches from LIKE ‘mcp_%’ prefix to exact-match per-tool_name (was wiping taskie2 when deleting taskie)

  • Interface: Processes panel with live delegates + Stop button (cancel_event through MessageProcessor chain, subagent_start/end WS events); “thinking…” anchor + breathing PresenceBar logo; collapsible ACT-cycle group; turn-scoped remember/speak on last Chalie row; light-theme :global(…) & transform leak that pinned fixed-position elements to the wrong containing block

  • Frontend refactors: -312 LOC lean-cleanup, -2049 LOC comment-strip (transpile-compare verified byte-identical), @lucide/vue across 25 components (-203 LOC), brain app-shell reveal restored via data-ready ref bound in onMounted

  • mypy --strict clean across 473 files with zero # type: ignore; TKT-800 deletes dormant mode-gate classifier + ~796KB shipped ONNX head; markdown-it-py + brotli dropped; Transcript converted to static class with turn-based feed pagination