summaryrefslogtreecommitdiffhomepage
path: root/src/app
AgeCommit message (Collapse)Author
2026-06-12feat(cache-warming): consume CR-4 lifecycle — tab-close cancel + ↵Adam Malczewski
scope-aware subscriptions - closeTab now POSTs /conversations/:id/close (abort in-flight turn + stop/disable warming server-side); disconnect still leaves both running ([email protected]) - syncSubscriptions honors catalog scope ([email protected]): global surfaces are not re-subscribed on conversation switch - fix(ws): the surface-message parser dropped the conversationId echo (CR-4d was ours, not the backend's) — preserved + unit-tested - secondsUntilNext: 3s stale guard — a past nextWarmAt renders as waiting, not 0s - re-pinned + re-mirrored [email protected] / [email protected] - scripts/probe-cache-warming.ts: live CR-4 probe (default-off, future nextWarmAt, repeated warms, mid-turn close abort, idempotent re-close) — 17/17 against bin/up
2026-06-12feat(chat): multi-client live view — watch in-flight turns + user prompt ↵Adam Malczewski
on stream - subscribe every open conversation on load + WS reconnect (resync), unsubscribe on tab close - derive a stream-based 'generating' state for watchers (Composer running indicator) - fold the user-message turn event so watchers render the prompt mid-turn (de-dup vs sender's optimistic echo) - re-pin [email protected] / [email protected]; re-mirror contracts; add user-message to the exhaustiveness guard
2026-06-12feat(chat): old-Dispatch composer layout — textarea + send + status barAdam Malczewski
Restore the ergonomic composer from old Dispatch: an auto-resizing textarea (1→7 lines) with a fixed-width Send button beside it, and a status bar BELOW holding a status icon · context-window fill bar (escalating success/warning/ error color) · compact token count (current / limit · pct%). The bar reuses the latest turn's contextSize as current usage and HARDCODES a 1,000,000-token window limit as a placeholder (real per-model limit is the next backend ask). Absorbs the standalone ContextSizeBadge (removed). Pure helpers computeContextUsage + formatCompactTokens added to core/metrics (tested). 540 tests green.
2026-06-12feat(metrics): consume contextSize — current context-usage readoutAdam Malczewski
Backend context-size handoff: re-pin [email protected] / [email protected] (+ re-mirror .dispatch reference snapshots). Thread the optional contextSize through core/metrics (done fold + durable + selectCurrentContextSize: latest turn's defined value, undefined=>unknown never 0, durable-wins-over-live). Chat store exposes currentContextSize; ContextSizeBadge renders "N tokens in context" / "context size unknown" above the composer. GLOSSARY: add context size / context window. 533 tests green.
2026-06-12feat(workspace,smart-scroll): per-conversation cwd + LSP view; smart auto-scrollAdam Malczewski
workspace ([email protected]): a cwd field in the Model sidebar view (GET/PUT /conversations/:id/cwd) + a new 'Language Servers' view (GET /conversations/:id/lsp) with per-server connected/starting/error badges, spinner, error text, and refresh. Store-owned reactive cwd, re-seeded on focus change; works for DRAFTS too (targets the draft's client-minted id, which survives promotion, so turn 1 runs in the chosen cwd). Network seam normalizes the untyped LSP body. smart-scroll: pure stick-to-bottom reducer + injected controller shell (scroll/scrollend + a ResizeObserver on the content so the view follows async height changes — markdown/highlight, images, collapses, viewport reflow), plus a floating scroll-to-bottom button. FIX: restore the transcript scrollbar — the refactor moved overflow-y-auto to an inner child, so the flex-1 container needed min-h-0 to constrain instead of growing to content. harness: vitest-setup polyfills Element.scrollTo + ResizeObserver (jsdom implements neither), fixing App component tests. docs: backend-handoff pruned (CR-3 resolved/removed); added cwd/LSP verification courier (backend confirmed all 6 asks ✅); removed the resolved cache-warming-timer courier. Verified: svelte-check 0 errors, biome clean, 523 tests pass, vite build OK.
2026-06-11feat(cache-warming,surfaces,metrics,markdown): conversation-scoped surfaces, ↵Adam Malczewski
cache warming + retention, markdown Consumes the backend cache-warming + cache-rate handoffs end-to-end and adds supporting infra: - protocol/transport: conversation-scoped surfaces (conversationId on subscribe/invoke/surface + staleness routing); store auto-subscribes the catalog with the focused conversation and re-scopes on switch. - surface-host: generic Number field renderer + custom rendererId dispatch (graceful skip on unknown). - cache-warming feature: enabled toggle, min+sec interval, AUTHORITATIVE countdown from the surface's cache-warming-timer nextWarmAt, manual Warm now (POST /chat/warm), lastWarmAt-keyed history, cache-retention stat, expectedCacheRate headline. - metrics: cross-turn expected-cache (retention) derivation + bubble badge; cache-rate fix needs no code change (inputTokens now total). - markdown feature: marked + marked-highlight + highlight.js + dompurify, rendered in ChatView. - fixes (gemini review): {#key activeConversationId} remount of CacheWarmingView to stop history/feedback leaking across tabs; guard NaN interval inputs from committing 0. - docs/contracts: regenerated transport/ui-contract mirrors; backend-handoff updated (CR-3 resolved). Verified: svelte-check 0 errors, biome clean, 494 tests pass, vite build OK.
2026-06-10feat(chat,app): Model view in sidebar + split key/model selectorsAdam Malczewski
- move the model picker out of the chat header into a dedicated "Model" sidebar view; sidebar now seeds two default panels (Model on top, Extensions below) - split the single model dropdown into two stacked selects: a key selector (distinct credential keys) + a model selector (models under the current key) - pure model-select helpers (splitModelName/joinModelName/modelKeys/modelsForKey), split on the FIRST slash so multi-slash model names stay intact - onSelect still emits the full `<key>/<model>` string (ChatRequest.model unchanged)
2026-06-10feat(views,surface-host): Extensions sidebar view — auto-expanded surfaces ↵Adam Malczewski
+ tables views (new feature): - pure panel-stack reducer + thin generic ViewSidebar (dropdown picker + add/remove), switches on view KIND, never a surface id Extensions view (composition root): - folds frontend modules + backend surfaces into one "Extensions" view - frontend module list AGGREGATED from each feature's public `manifest` export (can't drift); no per-module version (FE features are internal to dispatch-web) - surfaces are AUTO-SUBSCRIBED on catalog + rendered expanded (no catalog buttons) surface-host: - consecutive `stat` fields coalesce into one aligned label/value table (StatTable) - generic custom-field renderer: dispatch on rendererId === "table" → SurfaceTable (pure parseTablePayload), so a backend `custom`/table field renders generically - shared presentational components/Table.svelte (used by both, neither feature depends on the other) store: - auto-subscribe every catalog entry, unsubscribe vanished ones, re-subscribe all on reconnect; expose all received specs via `surfaces` (drops single-selection) backend-handoff: CR-1 — emit Loaded Extensions as a custom/table field; notes what's already covered FE-side (renderer shipped, stat-table fallback works).
2026-06-10feat(tabs,app): tab id handles, fixed-width tabs-lift, slim shell + ↵Adam Malczewski
full-height sidebar Tabs: - short-handle ID badge per tab (shortest unique conversationId prefix, min 4) - fixed-width (w-48) tabs with tabs-lift folder borders Shell (composition root): - drop the Dispatch title bar; tabs sit at the very top with a 5px gap - big faded "Dispatch" watermark centered on an empty chat - collapsible right sidebar (empty shell) spanning full window height: a permanently right-pinned hamburger in the tab row toggles it; in-flow push that shrinks the whole left column (tabs included) at >=lg, overlay + backdrop below lg; open-by-default on wide / closed on narrow - main is overflow-hidden with a min-w-0 shrink chain; app.css pins html/body/#app height + body overflow hidden so the page never overflows
2026-06-10feat(metrics): per-turn + per-step token/timing metrics bubblesAdam Malczewski
Consume [email protected] / [email protected] metrics: usage.stepId, step-complete (ttft/decode/genTotal), done.durationMs/usage, and the durable GET /conversations/:id/metrics endpoint. - core/metrics: pure live-fold + durable-merge reducer; decode-rate TPS; head-aligned, stable placement; progressive per-step rows (each shown as its step ends) with the turn-total row gated on the done event. - features/chat: store folds metric events + hydrates durable TurnMetrics; ChatView renders inline step bubbles + a turn-total bubble. - app: MetricsSync HTTP effect (tolerates 404) injected into chat stores. - scripts/live-probe: drives the metrics path; live-verified 17/17 vs bin/up. - docs: regenerate .dispatch wire/transport mirrors to 0.4.0; glossary terms (turn/step metrics, TTFT, decode time, TPS, metrics bubble); trim handoff.
2026-06-07Revert "feat(chat): live turn metrics — telemetry reducer + rendering"Adam Malczewski
This reverts commit 48c6d85c3cc5a57a729f14068e2346b17ed62088.
2026-06-07feat(chat): live turn metrics — telemetry reducer + renderingAdam Malczewski
Consume wire/transport-contract 0.3.0 (step-complete event + timing fields on usage/tool-result/done). Pure core/telemetry module: foldMetricEvent (reducer) + derived selectors (stepTps, turnTps, etc). TelemetryState is pure data, no active-turn tracking — consumers pass turnId to selectors. ChatStore wires foldMetricEvent into handleDelta and exposes telemetry + currentTurnId. ChatView shows step-metrics footer (time/TPS/tokens) on assistant text bubbles and durationMs badge on tool cards. New TurnSummary component renders turn-level stats (wall-clock, tokens, steps, TPS) in a DaisyUI stats block. Extended live-probe to verify telemetry events against bin/up (pending backend restart). 336 tests, typecheck 0, biome clean, build ok.
2026-06-07feat(chat): restyle thinking — visible bubble, collapse, title swap, ↵Adam Malczewski
persisted open Thinking renders inside a visible rounded-card bubble (like tool calls), capped to the same max-w-5xl column as assistant text. Uses a DaisyUI checkbox collapse (no arrow/plus icon) with smooth animation. Title reads "Thinking" + loading-dots while the model is actively generating, then flips to "Thoughts" with no dots once done. Open/closed state persists across the generating→completed→sealed transition via stable ordinal keys (per-conversation isolation via {#key} in App). Added optional streaming flag to RenderedChunk (pure selector, only on the accumulating chunk).
2026-06-07feat(tabs): extract TabBar component with horizontal scroll + sticky end '+'Adam Malczewski
Move inline tab-bar markup from the composition root into a thin presentational TabBar in the tabs feature (feature-as-a-library: pure reducer -> reactive store -> UI). Adds overflow-x scroll (min-w-max strip) and a sticky right-pinned new-chat '+' that floats over scrolling tabs. Draft-on-select / create-on-send behavior unchanged.
2026-06-07fix: optimistic user message echo + tabs persistenceAdam Malczewski
Bug 1 (sent message didn't appear until turn end): the transcript only folded assistant AgentEvents, so the user's own message showed only after turn-sealed resync. Add core/chunks appendUserMessage() (provisional user chunk, superseded on history sync) and call it in chat send() — the message now renders instantly. Bug 2 (tabs didn't persist on refresh): the app passed { storage: undefined } to createLocalStore, which the adapter treats as a no-op store, so nothing was saved. Default to globalThis.localStorage. Regression test exercises the non-injected path. Also updated app store tests for the echo (assistant-vs-user chunk filtering). Verified: svelte-check 0/0, vitest 288 (stable x2), biome clean, build ok.
2026-06-07Slice 3 wave B: tabbed multi-conversation app + model selector (DaisyUI)Adam Malczewski
- store.svelte.ts: tabs store over injected localStorage; one chat store per conversation (Map); single WS routes chat.delta/error by conversationId; draft (null active) mints a conversationId and becomes a tab on first send (title from deriveTitle); GET /models catalog; default model flash; close tab = dispose + cache.delete (local forget) + neighbour activation; restore tabs from storage + load() on construct - App.svelte: DaisyUI tab strip (+ / close), model selector, chat, surfaces - AppStore: tabs/activeConversationId/activeChat/models/activeModel + send/selectModel/newDraft/selectTab/closeTab; +localStorage inject opt Verified: svelte-check 0/0, vitest 281 (stable x2), biome clean, build ok.
2026-06-07fix: blank page on non-localhost HTTP (secure-context crypto.randomUUID)Adam Malczewski
crypto.randomUUID() is secure-context-only — undefined on plain-HTTP non-localhost origins (e.g. http://arch-razer:24204), so createAppStore threw during mount and nothing rendered. Add src/app/uuid.ts randomId(): prefer crypto.randomUUID when present, else build a v4 from crypto.getRandomValues (available in insecure contexts), else Math.random fallback. Use it for the conversation id. Verified: svelte-check 0/0, vitest 221, build ok.
2026-06-07Slice 2 wave 3: wire chat end-to-end at the composition rootAdam Malczewski
- app/store.svelte.ts: one WebSocket carries surfaces AND chat (onChat -> chatStore.handleDelta); build the conversation cache over the IndexedDB adapter; createChatStore wired to transport (socket.send), injected HTTP historySync, and the cache; load() on construct - app/resolve-http-url.ts: host-relative HTTP base (port 24203), mirrors resolve-ws-url; injected fetch - App.svelte: render ChatView + Composer alongside the surface picker - createAppStore gains optional injection points (httpUrl/fetchImpl/indexedDB/ conversationId) for tests - vitest-setup.ts: fake-indexeddb/auto for jsdom IndexedDB (orchestrator-owned config; agent change adopted) Verified green (x2, stable): svelte-check 0/0, vitest 218, biome clean, build ok. Slice 2 (conversation transcript: cache + delta streaming) feature-complete.
2026-06-06Slice 1 follow-up: component-render interaction tests (CR-1/CR-2)Adam Malczewski
- vite: add @testing-library/svelte's svelteTesting() plugin so component render()/mount() resolves Svelte's browser build under vitest/jsdom - dep: @testing-library/user-event for realistic interaction tests - app: 7 component-render tests driving App.svelte through a fake socket (catalog render, subscribe-on-click, unsub/sub ordering, aria-current, error banner, action invoke) Verified green: svelte-check 0/0, vitest 91 passed, biome clean, vite build ok.
2026-06-06Slice 1: surface system + WS transport + composition rootAdam Malczewski
Pure-core feature libraries assembled at the composition root: - core/protocol: pure reducer over surface catalog/spec/error messages - features/surface-host: generic field-kind interpreter (toggle/progress/ selector/stat/button) + pure plan logic; no surface-id special-casing - adapters/ws: injected WebSocket client (effects at the edge) - app: composition root store (Svelte 5 runes over the pure reducer), host-relative surface WS URL resolution (resolveWsUrl), root App.svelte Verified green: svelte-check 0/0, vitest 84 passed, biome clean, vite build ok.