T3 Chat UX, Resumable Streaming & Local-First Sync: thom-chat & helioschat (2026)
This document provides the line-verified specification of TGlide/thom-chat (~/dev/thom-chat, commit 062193d792303007b500e35c0c50ea4ceefeff2d) and helioschat/app (~/dev/helioschat, commit 08e2c3fc784c4ed5bf58b38516182387f218bf26). It covers thom-chat’s server-detached OpenRouter stream continuation into Convex (api.messages.updateContent), 2-tier cost provenance (estimateTokenCount vs GET /api/v1/generation), /api/enhance-prompt, @ rule mentions, 3-mode fuzzy search (fuzzy-search.ts), and keyboard shortcuts, alongside helioschat’s IndexedDB v3 (llmchat) schema, chunked lazy loading (CHUNK_SIZE = 16), [REASONING] + JSON stream multiplexing, 10-round client tool loop (manage_memories, exa_web_search, math_evaluate), and 5-point zero-retention Temporary Chat mode.
1. TGlide/thom-chat (~/dev/thom-chat — SvelteKit 5 + Convex + OpenRouter)
Section titled “1. TGlide/thom-chat (~/dev/thom-chat — SvelteKit 5 + Convex + OpenRouter)”1.1 Convex Database Schema (src/lib/backend/convex/schema.ts)
Section titled “1.1 Convex Database Schema (src/lib/backend/convex/schema.ts)”conversations:title,user_id,pinned?: boolean,generating?: boolean,cost?: number,branched_from?: Id<'conversations'>(by_user).messages:conversation_id,role,content,reasoning?: string,model_id?,provider?,token_count?,cost_usd?,generation_id?,web_search_enabled?,reasoning_effort?,annotations?: any[],images?(by_conversation).user_keys(user_keys.ts&chat.ts):user_id,service('openrouter' | 'huggingface'),key. Saving an OpenRouter BYOK key inuser_keys.set(user_keys.ts:93-123) automatically enables and pins 4 models (google/gemini-2.5-flash,anthropic/claude-sonnet-4,openai/o3-mini,deepseek/deepseek-chat-v3-0324:free), whileuser_enabled_models.enable_initial(user_enabled_models.ts:132-177) seeds 3 defaults (google/gemini-2.5-flash,deepseek/deepseek-chat-v3-0324:free,microsoft/phi-4:free).user_settings:user_id,privacy_mode,free_messages_used,Name,uid_for_ai,occupation,traits.user_enabled_models:user_id,provider,model_id,pinned?: boolean(by_user,by_user_provider,by_model_id).user_rules:user_id,name,attach('always' | 'manual'),rule(by_user,by_user_attach,by_user_name).- Storage & Auth (
storage.ts,betterAuth.ts):storage.tsprovidesgenerateUploadUrl,getUrl, anddeleteFile;betterAuth.tswires@better-auth-kit/convex/handler(ConvexHandler+publicGetSession).
1.2 Detached Server Stream & 2-Tier Cost Reconciliation (src/routes/api/generate-message/+server.ts)
Section titled “1.2 Detached Server Stream & 2-Tier Cost Reconciliation (src/routes/api/generate-message/+server.ts)”- Free-Tier Gate (
+server.ts:333-339): Users without an OpenRouter BYOK key are restricted to< 10non-:freemessages; exceeding returns HTTP403with exact message'Free message limit reached (10/10). Please add your own OpenRouter API key to continue chatting, or use a free model ending in ":free".'. - Rule Injection Placement (
+server.ts:396-451): Collects allattach: 'always'rules plus any@<rule_name>manualrules matched viaparseRulesFromTextacross all conversation messages, deduplicates byr._id, and pushes{ role: 'system', content: ... }at the end ofmessagesToSend. - Detached Background Continuation: Returns
{ ok: true, conversation_id }immediately while the async OpenRouter SSE reader runs on the server, writing live deltas viaclient.mutation(api.messages.updateContent, { message_id, content, reasoning })and registering anAbortControllerinactiveGenerations(POST /api/cancel-generation). - Title Generation Quirk (
+server.ts:74-115, 392): Whenmessages.length === 1,generateConversationTitlecallsgoogle/gemini-2.0-flash-lite-001and unconditionally overwritesconversation.title(replacing the initial first-sentence title set byconversations.createAndAddMessageinconversations.ts:122-125). - 2-Tier Cost Provenance:
- Tier 1: Estimates tokens via
estimateTokenCount(tokenx) and multiplies bymodel.pricing.prompt/completion. - Tier 2: Queries
GET https://openrouter.ai/api/v1/generation?id=${generationId}to reconcile authoritativeusageUSD cost andtokens_completionintomessagesandconversations.cost.
- Tier 1: Estimates tokens via
- Branch-and-Regenerate Order (
src/routes/chat/[id]/message.svelte:80-95): Callsconversations.createBranched, awaitscallGenerateMessage(...)first (L80–89, omittingreasoning_effort), and then navigates viaawait goto(\/chat/${cid}`)(L91`).
1.3 Client Cache, 3-Mode Fuzzy Search & Keyboard Shortcuts
Section titled “1.3 Client Cache, 3-Mode Fuzzy Search & Keyboard Shortcuts”- Two-Layer Cache (
src/lib/cache/):LRUCache<K, V>(lru-cache.ts,maxSize = 50) +SessionStorageCache<T>(session-cache.ts:16, 94, defaultstorageKey = 'query-cache',ttlMs = undefined).useCachedQuery(cached-query.svelte.ts:20, 29) configuresstorageKey = 'convex-query-cache'andttl = 7 * 24 * 60 * 60 * 1000(7 days). - 3-Mode Fuzzy Search (
src/lib/utils/fuzzy-search.ts:66-214&conversations.ts:174-239): SupportssearchMode: 'exact' | 'words' | 'fuzzy'(minScore = 0.3).calculateMatchScoreweights exact (1.0), substring (0.8 + (searchLen / targetLen) * 0.2), all-words (0.9), partial-words ((exactWords / total) * 0.7 + (matchedWords / total) * 0.3), prefix (0.6), and character-order fuzzy (max(0.1, (searchLen / (targetLen + totalDistance)) * 0.5)). - Keyboard Shortcuts:
Ctrl/Cmd+Shift+O(New Chat),Ctrl/Cmd+B(Toggle Sidebar),Ctrl/Cmd+K(Search Modal),Ctrl/Cmd+D(Scroll to bottom),Ctrl/Cmd+Shift+M(Model Picker; inside picker:Ctrl/Cmd+ArrowRight4-col gridw-[620px],Ctrl/Cmd+ArrowLeftfavoritesw-[320px],Ctrl/Cmd+Upin/unpin).
2. helioschat/app (~/dev/helioschat — SvelteKit 5 + IndexedDB v3 + 4 Browser SDKs)
Section titled “2. helioschat/app (~/dev/helioschat — SvelteKit 5 + IndexedDB v3 + 4 Browser SDKs)”2.1 IndexedDB v3 (llmchat) & Chunked Lazy Loading
Section titled “2.1 IndexedDB v3 (llmchat) & Chunked Lazy Loading”- 5 Object Stores (
src/lib/database/connection.ts):threads,messages,attachments,folders, andmemories. - Chunked Startup & Streaming Write Suppression (
src/lib/stores/chat.ts:50-157):initializeStoreloads the firstCHUNK_SIZE = 16threads immediately and loads remaining threads in chunks of 16 with a50msdelay (L51–91), deduplicating concurrentloadChat(id)calls vialoadMutex(L161).- During token streaming, the
chats.subscribepersistence listener (L135–157) skips IndexedDB writes (!activeChat.temporary && !isStreaming) to prevent write amplification;StreamingController(controller.ts:227, 297) executes a singlesaveChatAsThreadAndMessagesinfinally.
2.2 Multiplexed Stream Protocol, StreamMetrics & Provider Registry
Section titled “2.2 Multiplexed Stream Protocol, StreamMetrics & Provider Registry”- Known Providers (
src/lib/providers/known.ts:11-800): DefinesKNOWN_PROVIDERSforopenai(gpt-5-mini),openrouter(openai/gpt-5-mini),featherless(Qwen/Qwen3-32B),groq(moonshotai/kimi-k2-instruct-0905), andcerebras(gpt-oss-120b), withdetectKnownProvider,defaultDisabledPatterns, andmodelOverrides. - Dual OpenAI Wire Paths (
src/lib/providers/openai-compatible.ts:304-680):streamChatCompletions(/v1/chat/completions) passesweb_search_options: { search_context_size }, parses four reasoning tag pairs (<think>,<thinking>,<reason>,<reasoning>) plusdelta.reasoning_content/delta.reasoning, and runs up toMAX_TOOL_ROUNDS = 10(L367).streamResponsesEndpoint(/v1/responses,L679) passestools: [{ type: 'web_search_preview' }](withoutsearch_context_size).
StreamMetrics(src/lib/streaming/types.ts:3-12&messageProcessor.ts:31-40):- Fields:
{ startTime: number, endTime?: number, totalTime?: number, promptTokens?: number, completionTokens?: number, totalTokens?: number, tokensPerSecond?: number, thinkingTime?: number }(first-content latency is stored inthinkingTimewhen no[REASONING]chunk occurred,messageProcessor.ts:64-66).
- Fields:
2.3 Dynamic System Variables, Client Tool Suite, Regenerate/Edit & 5-Point Temporary Mode
Section titled “2.3 Dynamic System Variables, Client Tool Suite, Regenerate/Edit & 5-Point Temporary Mode”- Dynamic System Prompt Variables (
src/lib/utils/systemPromptVariables.ts:26-88): Resolves$current_datetime,$user_timezone,$user_locale,$user_language, and$user_platform. - Client-Side Tools (
src/lib/tools/):ExaSearchTool(exa_web_search,numResults: 1..10,highlights.maxCharacters: 4000),MathEvaluatorTool(math_evaluateviamathjs,precision: 14), andManageMemoriesTool(manage_memoriesmode: 'search' | 'save' | 'delete'with keyword extraction>= 3chars). - Regenerate vs Edit-Message (
src/routes/[chatId]/+page.svelte:132-297):handleRegeneratereuses the assistant message’s storedproviderInstanceId,model, and toggles; editing an assistant message (allowAssistantMessageEditing) mutates it in-place without truncation. - Prompt History (
promptHistory.ts), Global Shortcuts & JSON Export (dataExport.ts): Stores 100 prompts insessionStorage(ArrowUp/ArrowDownnavigation), registers global shortcuts (Shift+Esc,Ctrl/Cmd+Shift+O,Ctrl/Cmd+U,Ctrl/Cmd+K,Ctrl/Cmd+B,Ctrl/Cmd+Shift+Backspace,Ctrl/Cmd+ArrowUp/Down,Ctrl/Cmd+Shift+;), and exports all IndexedDB stores +localStoragetollmchat-export-<timestamp>.json. - 5-Point Temporary Mode: In-memory only (
!chat.temporary), omitsmanage_memoriestool, suppresses memory prompt injection, skips LLM title generation, and auto-purges on route leave.