| Age | Commit message (Collapse) | Author |
|
# Conflicts:
# .dispatch/transport-contract.reference.md
# backend-handoff.md
# src/app/App.svelte
# src/features/chat/ui/Composer.svelte
|
|
Both pertain to compaction (message compaction + image compaction), so they
share one sidebar panel. Removes the standalone "Vision" view kind; the
VisionSettingsView now renders inside the Compaction panel, below
CompactionView (with an "Image compaction" divider).
CompactionView stays keyed per conversation (its percent is per-conversation);
VisionSettingsView stays un-keyed (its settings are global). The vision
feature library + manifest are unchanged (still a composed module).
A persisted "vision" sidebar panel gracefully renders empty (the kind is no
longer in viewKinds) until the user re-selects Compaction.
Verification: svelte-check 0/0; vitest 959/959; biome clean; build OK.
Not merged or pushed.
|
|
Images are now stored on disk under tmp (not SQLite) and served via
GET /images/:conversationId/:imageId. Persisted ImageChunk.url is a compact
relative HTTP path (/images/<conv>/<uuid>.png) instead of a base64 data URL.
No wire/transport-contract type change (behavior only) — re-mirrored the
delta notes.
- New pure resolveImageUrl(url, apiBase) helper (core/chunks/image-url.ts,
+8 tests): data/absolute URLs pass through; relative paths are prepended
with the API base (no double slash; empty base -> root-relative). Exported
from core/chunks + re-exported from features/chat.
- ChatView: new apiBaseUrl prop; <img src> uses resolveImageUrl. The
optimistic echo's data URL passes through; persisted relative paths
resolve against the base. +3 tests.
- AppStore exposes httpBase (getter); App.svelte passes apiBaseUrl into
ChatView and the heartbeat RunModal (also renders image chunks).
Verification: svelte-check 0/0; vitest 959/959 (run twice, +11); biome
clean; vite build OK. See backend-handoff.md §2j-update-2.
Not merged or pushed.
|
|
Backend now persists concurrency limits across reboots (no API contract change
— same endpoints/shapes, so no re-pin/re-mirror needed). Taking the suggested
optional UX hint: after a successful Save the limit row shows a brief green
'Saved.' that clears on the next edit (mirrors the ChatLimitField pattern).
backend-handoff.md §2j: notes the persistence change + the two earlier
backend-only changes (200ms release cooldown; 'queued' status — already shipped
as CR-13). typecheck 0/0, 926 tests green, biome clean, build OK.
|
|
The 'Concurrency limits' and 'Live status' section headings are clear on their
own; the explanatory paragraphs under each were overbearing. Removed both.
|
|
The 'queued' (waiting-for-a-concurrency-slot) indicator on the tab + composer
corner used loading-spinner; switch to DaisyUI's loading-ring. The 'active'
(generating) indicator stays loading-dots. typecheck 0/0, 926 tests green,
biome clean, build OK.
|
|
The 2s status poll (and post-mutation limit reloads) toggled a visible
loading state, but since the refresh is near-instant the spinner flashed
for <1 frame — a visible flicker/jerk every poll. The polling is now
SILENT (mirrors the heartbeat runs list):
- refreshLimits/refreshStatus: re-entrancy guard (limitsInFlight/statusInFlight,
no UI), no loading-state toggle. Error cleared only on success so it stays
visible + stable during an in-flight retry (no vanish-mid-poll).
- Removed limitsLoading/statusLoading: the Refresh buttons are plain-text
(no spinner, not disabled); the empty-state guards use hasLoaded* only
(no && !loading), so the list area never reflows mid-refresh.
+1 regression-guard test (Refresh buttons never surface a spinner). typecheck
0/0, 926 tests green (x2), biome clean, build OK.
|
|
Backend vision update (additive to [email protected] / [email protected],
no version bump).
Contracts mirrored (.dispatch/transport-contract.reference.md):
- VisionSettingsResponse + SetVisionSettingsRequest (GET/PUT /settings/vision).
- Delta note: read_image -> consult_vision; numbered placeholders; compaction.
Tool rendering (ChatView):
- read_image test -> consult_vision (rendering is generic by toolName).
- +2 tests: numbered-placeholder text chunk + [Compacted image] text chunk
(both regular text chunks, render as-is — no special handling).
New vision feature library (src/features/vision/):
- logic/view-model.ts (32 tests): VisionSettings/VisionSettingsPatch types
(consumer-defines-port), LoadVisionSettings/SaveVisionSettings ports +
results, normalizeVisionSettings (network-seam coercion), parseImageLimit/
imageLimitChanged, compactionModelOptions (vision-capable models via chat's
public isVisionModel + Auto sentinel), round-trip helpers, imageLimitLabel.
- ui/VisionSettingsView.svelte (9 tests): imageLimit input + Save,
compactionModel dropdown (Auto + vision-capable models), load-on-mount,
save-on-change, error/saved feedback.
- index.ts.
Cross-unit seam: isVisionModel added to features/chat public index.ts
(additive); imported through the public surface, not internals.
Store wiring (src/app/store.svelte.ts):
- visionSettings state + refreshVisionSettings (GET /settings/vision,
normalized at the seam) + setVisionSettings (PUT, partial, returns merged) +
VisionSettingsResult; seeded on boot; exposed on AppStore. +4 store tests.
Mounted in App.svelte: new "Vision" sidebar view kind + VisionSettingsView
in viewContent (not conversation-scoped); load/save adapters; visionManifest.
Verification: svelte-check 0/0; vitest 948/948 (run twice, +47 since the
prior vision commit); biome clean; vite build OK. See backend-handoff.md §2j.
Not merged or pushed.
|
|
Backend shipped "queued" ConversationStatus (additive to [email protected]): when a
request blocks on a concurrency slot, conversation.statusChanged broadcasts
"queued" (broadcast-only, never persisted); "active" on slot grant.
FE consumes it:
- WS parser (adapters/ws/logic.ts): accepts "queued" in the status set.
- Store handler: "queued" updates the status map + opens a tab for a new
cross-device queued conversation (like "active").
- TabList: status === "queued" -> loading-ring (spinner, aria-label "Queued");
"active" -> loading-dots (unchanged).
- Composer: status type widened to ComposerStatus (idle|running|queued|error),
exported from features/chat. "queued" -> a loading-ring status icon +
placeholder "Queued for a slot…"; behaves like "running" for the send
button (steer/stop — the turn is in flight, just waiting for a slot).
- App.svelte: composerStatus derived (error > queued > running > idle) —
conversationStatus === "queued" wins over generating so the corner shows a
ring during the wait (turn-start fires before the slot is granted, so
generating is already true while status === "queued").
- Re-mirrored .dispatch/wire.reference.md (ConversationStatus widened + header).
Tests: WS parser accepts queued; store handler sets status + opens a cross-device
tab + transitions queued->active->idle; TabList renders a ring for queued + dots
for active. typecheck 0/0, 925 tests green (x2), biome clean, build OK.
backend-handoff.md CR-13 marked RESOLVED.
|
|
concurrency queue
A small UX ask (queued chat -> loading ring, generating -> loading dots, in the
tab + composer corner) needs a backend change: there is no FE-derivable per-
conversation 'queued' signal (turn-start sets generating before the queue wait;
concurrency status is per-provider aggregate; background tabs only get the
conversation.statusChanged broadcast). Add 'queued' to ConversationStatus
(@dispatch/wire) + emit it from the concurrency extension when acquire() blocks.
Sent to backend agent e1d7; FE side fully designed, blocked on the wire bump.
Header notes the dev merge (e81df4c, 921 tests green).
|
|
|
|
Replace the Add-form provider text input with a <select> dropdown populated
from the available models' provider prefixes (<provider>/<model>), unioned
with providers already carrying a configured limit (so a limit set out-of-band
but whose model list is empty still appears). The selection auto-defaults to
the first option and falls back when an option disappears.
- logic/view-model.ts: pure providerFromModel + providerOptions(models, limits)
(distinct provider ids, first-seen order) + 7 tests.
- ConcurrencyView.svelte: models prop + <select> bound to newProviderId;
disabled + 'No providers available' when there are no models.
- App.svelte: pass models={store.models}; component test updated to the
dropdown (selectOptions) + a no-models case (6 component tests).
Verified: typecheck 0/0, 922 tests green, biome clean, build OK.
|
|
|
|
|
|
against sidebar
|
|
|
|
swapping text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
chats)
|
|
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.
|
|
- 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).
|
|
|
|
|
|
# Conflicts:
# src/app/App.svelte
# src/app/store.svelte.ts
# src/app/store.test.ts
# src/features/workspaces/ui/WorkspaceCard.test.ts
|
|
|
|
backend, dispatch-web → frontend)
|
|
endpoint
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sidebar wiring
|
|
# Conflicts:
# src/features/workspaces/ui/WorkspaceCard.svelte
# src/features/workspaces/ui/WorkspaceCard.test.ts
# src/features/workspaces/ui/WorkspacesHome.svelte
|
|
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.
|
|
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.
|
|
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.)
|
|
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.
|
|
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).
|
|
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.
|
|
- 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)
|
|
- 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
|
|
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.
|
|
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.
|