June 25, 2026
Thread system ships end-to-end + security & cleanup sweep
Threading lands across the full stack in six coordinated workstreams
Threading lands across the full stack in six coordinated workstreams. Workstream A deletes the old moments/star/recall-dialog feature (-1649 lines) — the memory.recall engine and turn-0 flashback are preserved. Workstream B redefines turn_id as the thread id: starters allocate, replies append with the existing id. Workstream C re-keys the compaction watermark to (channel, turn_id) so each thread carries its own checkpoint, with 12 new feature tests covering thread isolation. Workstream D adds a dedicated async gist memory profile plus cross-thread recall. Workstream E ships the /threads/search endpoint with a vec+FTS index. Workstream F rebuilds the frontend around a thread-list-driven feed: collapsed gist rows expand on click, per-thread reply boxes compose into the existing thread, and a new thread-search dialog replaces the deleted recall dialog. Pagination is 50 threads/page.
Two interface wiring fixes accompany the rebuild. Live turns now carry their allocated turn_id through the WS ‘done’ event and get tagged on each finished turn’s forms, so the reply box, collapse control, and per-thread grouping appear; active threads (<1h) auto-expand on load. The redundant in-feed ‘Search threads’ button was removed — the navbar icon already opens the search dialog. The committed dist bundle is rebuilt to match the new source.
The user message bubble stops collapsing newlines. The .speech-form__text wrapper had been orphaned by an earlier markup change, so white-space: pre-wrap, font-weight, and word-break all stopped matching; folding them onto .speech-form–user where the text now lives restores multi-line rendering. A separate SonarCloud fix replaces the deprecated word-break: break-word with overflow-wrap: break-word on the same rule.
HyDE comes out of turn-0 memory seeding — zero generative LLM calls remain in the path. The flashback query is the raw user message verbatim, and terse messages (<8 tokens) skip recall outright since the model already holds the active thread in context. Net -196 LOC; service drops from 520 to 311 lines.
Several backend hardening fixes land. The native WS handshake stops putting the bearer token in the ?token= query string (long-lived credentials were leaking into proxy/access logs, Referer, and history) — the token is now sent in the first WebSocket frame as {“type”:“auth”,“token”:…} and validated server-side before registration; the web cookie path and internal-dev gate are unchanged. /auth/login is rate-limited by client IP. The Dockerfile installer fetch is pinned to the build’s TAG (preferred) or BRANCH, falling back to main only for the API-resolved latest case, so tagged builds no longer silently drift from the installer that shipped with that tag. The Ubiquiti REST handler stops silently downgrading verify_ssl=True to verify_ssl=False on SSLError — credentials and CSRF token now never traverse an unverified connection after an explicit verify request. A feature test spins a real self-signed HTTPS server to lock in the regression.
Code-quality pass: thread_gist dedups retrieval against data_graph_service, drops the never-called ThreadGistService.get(), removes the unused hasLiveTurn() conversation getter, and parses the naive-UTC SQLite timestamp as UTC for isThreadActive. A broader remediation trims cognitive complexity across data_graph_service, llm_clients, providers, carddav_handler, and schedule/message_processor, deletes dead wall_ms timing, and switches ConversationFeed to semantic
-
Thread system ships across workstreams A-F: moments/star deletion, turn=thread data model, thread-scoped compaction, async gist MP + cross-thread recall, /threads/search endpoint, and thread-list frontend with per-thread reply box
-
Live turns now carry turn_id through the WS ‘done’ event; active threads (<1h) auto-expand; redundant in-feed search button removed
-
User message bubble preserves newlines by folding pre-wrap/font-weight/word-break onto .speech-form–user where the text now lives; word-break: break-word swapped for the non-deprecated overflow-wrap: break-word
-
HyDE removed from turn-0 flashback — zero LLM calls, raw user message as query, terse messages (<8 tokens) skip recall; -196 LOC
-
WS bearer token moved from ?token= query string to first {“type”:“auth”} frame; /auth/login rate-limited by IP; Dockerfile installer pinned to TAG/BRANCH
-
Ubiquiti REST stops silently downgrading verify_ssl=True to False on SSLError, with a self-signed-HTTPS feature test capturing the regression