summaryrefslogtreecommitdiffhomepage
AgeCommit message (Collapse)Author
2026-06-27fix(sidebar-tabs): inset top-bar title 8px left to match parent px-2 spacingAdam Malczewski
2026-06-27feat(sidebar-tabs): add 2px horizontal spacing to top-bar titleAdam Malczewski
2026-06-27feat(sidebar-tabs): add active tab title to top bar (New Tab for unstarted ↵Adam Malczewski
chats)
2026-06-27feat(vision): image paste + transcript image rendering + vision badgeAdam Malczewski
Vision & vision-handoff frontend (consumes the backend's additive [email protected] / [email protected] image types — no version bump). Contracts mirrored: - .dispatch/wire.reference.md: ImageChunk added to the Chunk union + ImageChunk/ImageInput interfaces. - .dispatch/transport-contract.reference.md: ChatRequest.images, ModelMetadata.vision, + ImageChunk/ImageInput re-exports. Core (core/chunks): - conformance: assertChunkExhaustive handles the new 'image' variant (the guard caught it — its purpose). - appendUserMessage(state, text, images?) echoes a [text, image, ...] user run; the user-message event dedup scans the trailing user run (not just the last chunk) so an image-bearing echo doesn't duplicate the text; applyHistory's during-gen dedup matches a multi-chunk echo by content equality (chunkContentEquals + trailingRun helpers). UI: - ChatView renders user 'image' chunks as lazy <img> bubbles; a non-vision model's persisted [image, analysis-text] both render. read_image tool renders generically (no special-casing). - Composer: clipboard paste / file picker / drag-drop of images -> base64 data URLs, thumbnail previews with remove, forwarded on chat.send (omitted when none). Image-only sends allowed; steering (chat.queue) never forwards images. - ModelSelector: vision badge (isVisionModel) marks vision-capable models; indicator shows native-vision vs vision-handoff hint. Store wiring: ChatStore.send + AppStore.send + App.svelte handleSend thread images through; chat.send still omits cwd (only images added). Verification: svelte-check 0/0; vitest 901/901 (run twice, +34 new); biome clean; vite build OK. See backend-handoff.md §2j. Not merged or pushed.
2026-06-27docs(concurrency): expand backend handoff §2j + drop unused importAdam Malczewski
- backend-handoff.md: full §2j slice (API surface, contract note, FE summary, verification, the single-provider GET note, worktree symlink note) + bump the pinned transport-contract version 0.22.0 → 0.23.0 in the header/packages/ mirror/endpoints sections. - ConcurrencyView.svelte: remove an unused ConcurrencyLimitView type import (the type flows via viewConcurrencyLimits' return type; svelte-check clean).
2026-06-27feat(concurrency): add provider concurrency limits UI — settings + live statusAdam Malczewski
2026-06-27feat(sidebar-tabs): move tab bar from top into sidebar as vertical listAdam Malczewski
2026-06-27Merge branch 'dev' into feature/heartbeatAdam Malczewski
# Conflicts: # src/app/App.svelte # src/app/store.svelte.ts # src/app/store.test.ts # src/features/workspaces/ui/WorkspaceCard.test.ts
2026-06-27Merge branch 'feature/indent-change' into devAdam Malczewski
2026-06-27chore: update path references for directory rename (dispatch-backend → ↵Adam Malczewski
backend, dispatch-web → frontend)
2026-06-27feat(heartbeat): show next heartbeat timer + backend handoff for timestamp ↵Adam Malczewski
endpoint
2026-06-26fix(heartbeat): hide runs loading state to prevent sidebar flickerAdam Malczewski
2026-06-26fix(heartbeat): PromptEditor save not persisting — flickers and revertsAdam Malczewski
2026-06-26style: switch from tabs to 2-space indentation (incl. svelte)Adam Malczewski
2026-06-26feat(heartbeat): default system prompt to workspace default + reset buttonAdam Malczewski
2026-06-26fix(heartbeat): make PromptEditor modal truly fullscreenAdam Malczewski
2026-06-26feat(heartbeat): prompt editor modal + hours/minutes timer + backend handoffAdam Malczewski
2026-06-26feat(heartbeat): heartbeat view with config, run list, live chat modal, and ↵Adam Malczewski
sidebar wiring
2026-06-25Merge branch 'feature/ssh-support' into devAdam Malczewski
# Conflicts: # src/features/workspaces/ui/WorkspaceCard.svelte # src/features/workspaces/ui/WorkspaceCard.test.ts # src/features/workspaces/ui/WorkspacesHome.svelte
2026-06-25fix(computer): show ✓ check after a successful Test (not a stuck spinner)Adam Malczewski
When the user clicks Test, the button now transitions spinner → ✓ (green) on success / ✗ (red) on failure (persisting until the next test or computer change), instead of reverting to plain 'Test' text with no success indicator. Also refresh the connection-status badge immediately after a test completes, so a stale 'connecting' spinner caught by the 4s poll mid-test is cleared and the badge reflects the real post-test state. Clear the test result when the selected computer changes so a stale ✓ from alias A doesn't persist for alias B.
2026-06-25docs(handoff): mark §2d provider-retry divergence RESOLVED (backend merge ↵Adam Malczewski
de022ce) Backend merged dev into feature/ssh-support (de022ce): TurnProviderRetryEvent is now present alongside the SSH types. FE re-synced both file: deps; typecheck is GREEN (0/0) with ZERO FE code changes (the consumer was already complete). Full suite: 795/795 tests, biome clean, build OK. §2e verified post-merge.
2026-06-25feat(computer): SSH computer selection + status + workspace default (handoff #2)Adam Malczewski
Mirrors the cwd/workspace UI for the SSH-computer feature: - New feature library src/features/computer/: - logic/view-model.ts (pure): viewComputer/viewComputerStatus/viewTestResult/ summarizeComputers/formatHost/knownHostLabel + state->badge for the 4 ComputerStatusResponse states + SaveComputer/LoadComputerStatus/ TestComputer/LoadComputers ports. 20 view-model tests. - ui/ComputerField.svelte: per-conversation selector (dropdown + connection-status badge + Test-connection, polling the selected alias). - ui/ComputerSelect.svelte: reusable Local/computers dropdown, shared with the workspace default-computer control. - AppStore: computerId state + refreshComputer (at every focus site, parallel to refreshCwd) + setComputer (PUT /conversations/:id/computer, null=clear) + global computers catalog (GET /computers on boot, like models) + computerStatus(alias) + testComputer(alias). chat.send UNCHANGED (resolved server-side like cwd). - App.svelte: ComputerField in the Model sidebar view next to CwdField; adapted ports wrap the store. - workspaces: setDefaultComputer on WorkspaceHttp+WorkspaceStore (PUT /workspaces/:id/default-computer); default-computer selector in WorkspaceCard (reuses ComputerSelect); router passes store.computers through. - Re-mirrored .dispatch/transport-contract.reference.md (Computers section + ChatRequest.computerId); updated .dispatch/wire.reference.md (Computer/ ComputerEntry/defaultComputerId + the provider-retry divergence note from handoff #1); GLOSSARY + backend-handoff.md (handoff #2, §2e). Transparency invariant: the computer is USER-facing only (a tool-execution target, never part of the model prompt); the agent never sees it. Verify: 795/795 tests green; biome clean; vite build succeeds; 0 typecheck errors from the computer feature. (11 pre-existing svelte-check errors remain from the open §2d provider-retry divergence — backend feature/ssh-support still lacks TurnProviderRetryEvent; not from this feature.)
2026-06-25feat(workspaces): open workspace in a new browser tabAdam Malczewski
The 'Open' button on a workspace card did client-side navigation (onNavigate -> pushState), opening the workspace in the SAME tab. Replace the JS click handler with a native anchor using target="_blank" (+ rel=noopener noreferrer) so clicking Open opens the workspace in a NEW browser tab. The new tab does a full load and routes to /<id> via the existing parsePath -> App wiring. The card no longer needs the onNavigate prop (its only navigation was the Open button), so drop it from WorkspaceCard + WorkspacesHome and update the card tests to assert the new-tab link attributes instead of the onNavigate call.
2026-06-25feat(tabs): show full title in tab hover tooltipAdam Malczewski
The conversation tab's title span was truncated with a static 'Double-click to rename' tooltip. Replace the static title with the full tab.title so hovering reveals the complete conversation title (the truncate class still clips the visible label).
2026-06-25feat: workspaces shell + cwd-lsp rename + mcp/settings/system-prompt ↵Adam Malczewski
features + app wiring - workspaces: URL-driven conversation grouping (home listing at /, routing, store, http adapter, WorkspaceCard) wired into the App.svelte shell - rename features/workspace -> features/cwd-lsp (the cwd/lsp status feature) - new features: mcp (status view), settings (chat-limit field), system-prompt (prompt builder), all rendered via the generic surface host - chat: store + ChatView updates - tabs: tabs-store updates - app wiring: ErrorModal (full-screen error surface), app/App.svelte + store.svelte This commit makes HEAD typecheck clean for the first time: the prior HEAD (c95cc77) imported features/settings from app/App.svelte but never committed the feature, so only the full working tree was green.
2026-06-25feat(core): chunks retry-banner, wire conformance, ws logic, history adapterAdam Malczewski
- core/chunks: add retry-banner view-model (+test), update reducer/selectors/types - core/wire: update conformance checks (+test) - adapters/ws: update reconnect logic (+tests) - adapters/history: new client-side routing adapter (history + popstate wrapper)
2026-06-25chore: regenerate contract mirrors, bump deps, update docs/notes/scriptsAdam Malczewski
- Regenerate .dispatch/{wire,transport-contract}.reference.md mirrors - Bump deps (package.json, bun.lock) - Update AGENTS.md, GLOSSARY.md, README.md, ROADMAP.md - Add workspaces backend-handoff exchange + notes/assumptions-log.md - Add scripts/fix-dist-perms.sh (root-owned dist/ from Docker build) - Add scripts/live-probe-provider-retry.ts
2026-06-22fix: duplicate user message on first send in a new tabAdam Malczewski
When a draft is promoted to a real conversation, send() appends the user message as a provisional chunk (optimistic echo). But load() also fires syncTail, which fetches the CR-6 persisted user message as a committed chunk — showing the message twice until turn seal. Fix: applyHistory now removes provisional chunks that duplicate the last committed chunk (matching role + text content) when new committed chunks arrive during generation. The optimistic echo is dropped once the authoritative committed version arrives. 684 tests green.
2026-06-22feat: double-click tab to rename (PUT /conversations/:id/title)Adam Malczewski
Double-click a tab's title to enter inline edit mode. Enter or click away (blur) saves; Escape cancels. The rename is optimistic — the local tab updates immediately and PUT /title fires in the background. 683 tests green.
2026-06-22feat: switch theme from dracula to monokai (custom)Adam Malczewski
Replace dracula with a custom monokai theme (from dachinat/daisyui-themes, with modifications: all -content colors set to black, base-300 changed to #3b3a3a). Dracula is unloaded; monokai is the sole bundled theme and default. Also adds .env.development (dev ports 24203/24205) and .env.production (arch ports 24991/24990) so Vite uses the correct backend per mode. Excludes src/themes/ from biome (third-party CSS). 683 tests green.
2026-06-22feat: consume context window + percentage-based compact handoffAdam Malczewski
1. Real context window: GET /models now returns modelInfo[model].contextWindow. The Composer uses this instead of the hardcoded MAX_CONTEXT = 1,000,000. Falls back to 1M when modelInfo is absent or the model has no contextWindow. 2. Percentage-based auto-compact: the compact-threshold endpoint is renamed to compact-percent. The CompactionView now shows a percent input (0-100, default 85, 0 = manual) instead of a token count input. Types renamed: CompactThresholdResponse → CompactPercentResponse, SetCompactThresholdRequest → SetCompactPercentRequest. Note: the field name in the backend types is still 'threshold' (not 'percent') — the FE maps between them. Re-mirrored .dispatch/transport-contract.reference.md. 686 tests green. 0 svelte-check errors + warnings.
2026-06-22feat: persist sidebar layout + open/closed state between refreshesAdam Malczewski
Sidebar panel layout (which views are open and their order) and the sidebar open/closed toggle are now persisted to localStorage. Default layout is just the Model view at the top. - ViewSidebar accepts an onChange callback that reports panel kinds - App.svelte creates two createLocalStore instances (dispatch.sidebar.views + dispatch.sidebar.open) using the store's storage adapter - AppStore exposes its storage instance so the shell persists via the same adapter (test-injectable, not globalThis.localStorage) - Tests pre-populate fake storage with ["extensions"] for the 4 tests that need the Extensions view visible 686 tests green. 0 svelte-check warnings (2 pre-existing errors from missing transport-contract exports, unchanged).
2026-06-22feat: collapsible tool output (collapsed by default like thinking)Adam Malczewski
Tool calls and results now use the same DaisyUI collapse pattern as thinking blocks — collapsed by default, click to expand. Each card shows the tool name + a wrench icon in the title; expanding reveals the input/output with overflow-x-auto for long lines and max-h-96 overflow-y-auto for very long output. Batched tool calls: each entry is its own collapse card (was a DaisyUI list). Pending results show a spinner in the title. Errors show a red badge. 686 tests green.
2026-06-22fix: tool output scrolls within its container instead of bleeding outAdam Malczewski
Add overflow-hidden to tool card containers and overflow-x-auto to <pre> elements so long tool output (file contents, JSON, etc.) gets its own scrollbar instead of expanding the chat width and creating a horizontal scrollbar on the entire transcript. 686 tests green.
2026-06-22fix: trim provisional chunks during long turns (browser stays responsive)Adam Malczewski
trimTranscript now drops oldest provisional chunks (the in-flight turn) when committed chunks are exhausted. Previously it bailed with drop=0 when committed was empty, allowing unbounded provisional growth during long generating turns (300+ chunks → browser crawls). Root cause of the syncTail approach failing: the kernel emits step-complete (line 360) BEFORE calling onStepComplete (line 542) — chunks are persisted only after tool results come back, not when step-complete fires. So syncTail on step-complete found nothing. Reverted the applyHistory + syncTail-on-step-complete changes from 4e1d041. The new approach is simpler: trim provisional directly in trimTranscript. Dropped chunks are lost temporarily (no Show Earlier) but come back as committed when the turn seals and syncTail fetches everything from the server. 686 tests green.
2026-06-22feat: trim chunks during generation via step-complete syncTail (CR-6)Adam Malczewski
The backend now persists chunks at step boundaries during generation (CR-6). The FE calls syncTail on each step-complete event to fetch the newly committed chunks. applyHistory clears the provisional array when new committed chunks arrive mid-generation (they're duplicates of what was folded from live events). The accumulating chunk (current in-progress step) is kept. This means trimTranscript can now drop oldest committed chunks uniformly during a long turn — no unbounded provisional growth. The browser never holds more than chatLimit chunks, even mid-generation. 3 new tests: clears provisional on new committed during generation, keeps provisional when no new chunks, keeps accumulating when clearing. 689 tests green.
2026-06-22docs: update backend-handoff — CR-6 resolved by backend, trim for relevanceAdam Malczewski
Backend shipped incremental seq assignment at step boundaries during generation. Marked CR-6 as RESOLVED with the FE adoption plan (option c: fold events for current step only, syncTail for sealed steps). Trimmed the verbose resolved-CR history to a compact table — the code is the source of truth now. 686 tests green.
2026-06-22fix(metrics): show turn/step metrics for compacted conversationsAdam Malczewski
Compacted conversations start with a system summary (role: "system") instead of a user message (role: "user"). The interleaveTurnMetrics function only detected segments by role === "user", so compacted conversations got zero segments and no metrics were emitted. Fix: when no user messages are found but metrics entries exist, treat the entire transcript as one segment. This places turn-metrics at the end and anchors step-metrics to any tool-batch groups by stepId. 686 tests green.
2026-06-22fix(metrics): tail-align when stepId matching fails — prevent misaligned turnsAdam Malczewski
When stepIds are absent on persisted chunks (or don't match), the sequential fallback was HEAD-aligning — matching the OLDEST entries (trimmed turns) to the NEWEST segments. This showed 'turn 1' on turn 20's content and placed the wrong metrics on the wrong segments. Fix: when stepId matching produces ZERO matches, use TAIL-ALIGNMENT instead — match the LAST T entries to the T segments (the loaded chunks are always the newest). The oldest entries (trimmed turns) are unmatched and emit standalone turn-metrics rows at the top. 686 tests green.
2026-06-22fix(metrics): preserve turn-metrics for trimmed turnsAdam Malczewski
When the chat limit unloads a turn's content (user message + chunks), the segment disappears and the turn-metrics row was lost. Now unmatched entries (fully trimmed turns) emit a standalone turn-metrics row at the top of the transcript, so the user still sees 'turn N · X tok' for unloaded turns. Note: trimming during generation only affects COMMITTED chunks (old turns). Provisional chunks (the in-flight turn) are never trimmed — the big trim happens at seal when provisional → committed. This is by design.
2026-06-22feat(metrics): show turn number in metrics bubble (turn N · ...)Adam Malczewski
The turn number comes from the entry's position in the metrics array (1-based), which is correct regardless of trimming since stepId matching aligns segments to the right entry. Now displays 'turn 3 · 12k tok' instead of just 'turn · 12k tok'.
2026-06-22fix(metrics): skip unanchored step-metrics — no more empty bubbles at tailAdam Malczewski
Step-metrics are only shown when anchored to their tool content (inline after the tool-call/result group). Steps whose chunks were trimmed (or text-only steps with no tool chunks) are now SKIPPED instead of piling up at the segment tail as empty 'step N · X tok' bubbles. The turn-total metrics row still shows the aggregate (tokens, duration, cache rate), so the conversation-level summary is preserved. Updated place.test.ts + ui.test.ts to anchor steps with tool-call groups where step-metrics are expected.
2026-06-22feat: show 5-char git hash next to hamburger for cache-bust detectionAdam Malczewski
Vite define bakes __APP_VERSION__ (git rev-parse --short=5 HEAD) at build time — survives bundling into the arch package deploy. Falls back to 'dev' when not in a git repo. Also fixes two noNonNullAssertion warnings in place.ts.
2026-06-22fix(metrics): stepId-based segment matching — robust against chat-limit ↵Adam Malczewski
trimming When the chat limit trims old chunks, head-aligning turn metrics to segments by position breaks (a trimmed user message removes a segment boundary, shifting all subsequent alignments by one). Fix: match segments to metrics entries by stepId overlap (pass 1), falling back to sequential matching for text-only segments with no stepId-bearing groups (pass 2). This prevents step/turn metrics from being placed on the wrong segment after trimming, while preserving the original behavior for text-only turns. 686 tests green.
2026-06-22fix(composer): single context-aware button — Send/Queue/StopAdam Malczewski
One button to the right of the text input: - idle → Send (starts a turn) - generating + text → Queue (steers via chat.queue) - generating + empty → Stop (aborts via POST /stop)
2026-06-22feat(chat): stop generation button — abort without closingAdam Malczewski
Consume the stop-generation handoff (no version bumps, no new types). - App store: stopGeneration() → POST /conversations/:id/stop (fire-and-forget) - Composer: stop button (square, error color) visible only while generating, next to the send/queue button - Existing event flow handles the rest: done with reason 'aborted' clears generating; conversation.statusChanged: idle updates the tab spinner 686 tests green.
2026-06-22feat(compaction): conversation compacting + auto-compact thresholdAdam Malczewski
Consume the compaction handoff ([email protected], [email protected]). Re-pinned file: deps + re-mirrored .dispatch/*.reference.md. - New 'Compaction' sidebar view (CompactionView.svelte): - 'Compact now' button → POST /conversations/:id/compact (loading indicator + result: 'N messages summarized, M kept') - Auto-compact threshold number input → GET/PUT /conversations/:id/compact-threshold (0 = disabled, default 350000) - Re-mounts per conversation via {#key} - App store: compactNow() + compactThreshold reactive state + setCompactThreshold(), seeded on focus change (like reasoning-effort + cwd) - conversation.compacted WS handler: reloads the SAME conversation's history (ID unchanged — old history forked to an archive, not a tab switch) - WS adapter parses newConversationId field on ConversationCompactedMessage - conformance guards + tests cover the new type 686 tests green.
2026-06-22feat(tabs): cross-device tab sync via conversation lifecycleAdam Malczewski
Consume the conversation lifecycle handoff ([email protected], [email protected]). Re-pinned file: deps + re-mirrored .dispatch/*.reference.md. - fetchOpenConversations() on connect: GET /conversations?status=active,idle restores the tab bar across devices (merges with localStorage — opens new tabs, removes closed ones, updates titles from backend) - conversation.statusChanged WS handler: closed → removeTabLocally (no re-POST); active → open tab + spinner; idle → update status map - conversation.compacted WS handler: dispose stale store + cache, reload history from server - TabBar shows a spinner on active conversations (statusFor prop) - closeTab refactored to use removeTabLocally (extracted cleanup) - conformance guards + WS adapter tests cover all 3 new WsServerMessage types 686 tests green.
2026-06-21fix: conversation.open opens tab without auto-switchingAdam Malczewski
CLI --open should add the tab to the strip but leave the user on their current tab. Add openTab reducer (add without selecting) + use it in openConversation instead of createTab+selectTab.
2026-06-21docs: add ROADMAP.md — shipped features, next up, backlogAdam Malczewski