From 38db3827870960f466be89afbc49f91238d46144 Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Thu, 25 Jun 2026 10:55:51 +0900 Subject: feat: workspaces shell + cwd-lsp rename + mcp/settings/system-prompt 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. --- src/features/chat/index.ts | 9 +++++++-- src/features/chat/store.svelte.ts | 33 ++++++++++++++++++++++++++++++--- src/features/chat/ui/ChatView.svelte | 32 +++++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 6 deletions(-) (limited to 'src/features/chat') diff --git a/src/features/chat/index.ts b/src/features/chat/index.ts index 1596c53..c120916 100644 --- a/src/features/chat/index.ts +++ b/src/features/chat/index.ts @@ -1,5 +1,10 @@ -export type { RenderedChunk, RenderGroup, ToolBatchEntry } from "../../core/chunks"; -export { groupRenderedChunks } from "../../core/chunks"; +export type { + ProviderRetryView, + RenderedChunk, + RenderGroup, + ToolBatchEntry, +} from "../../core/chunks"; +export { groupRenderedChunks, viewProviderRetry } from "../../core/chunks"; export type { TurnMetricsEntry } from "../../core/metrics"; export type { ChatTransport, HistorySync, HistoryWindow, MetricsSync } from "./ports"; export type { diff --git a/src/features/chat/store.svelte.ts b/src/features/chat/store.svelte.ts index 9beabfc..a31fe55 100644 --- a/src/features/chat/store.svelte.ts +++ b/src/features/chat/store.svelte.ts @@ -4,7 +4,7 @@ import type { ChatQueueMessage, ChatSendMessage, } from "@dispatch/transport-contract"; -import type { ChatMessage, StoredChunk } from "@dispatch/wire"; +import type { ChatMessage, StoredChunk, TurnProviderRetryEvent } from "@dispatch/wire"; import type { RenderedChunk, TranscriptState } from "../../core/chunks"; import { appendUserMessage, @@ -19,6 +19,7 @@ import { selectGenerating, selectHasEarlier, selectMessages, + selectProviderRetry, trimTranscript, unloadCount, windowTranscript, @@ -41,6 +42,12 @@ export interface ChatStoreDependencies { readonly historySync: HistorySync; readonly metricsSync: MetricsSync; readonly cache: ConversationCache; + /** + * The workspace this conversation belongs to (its URL slug). Sent on + * `chat.send`/`chat.queue` so the backend stamps the conversation's workspace + * at creation (default "default"). Absent → omitted (legacy behavior). + */ + readonly workspaceId?: string; /** * The chat limit: max loaded chunks before the oldest quarter is unloaded * (see `core/chunks/trim.ts`). Normalized via `normalizeChatLimit`; absent → @@ -54,6 +61,12 @@ export interface ChatStoreDependencies { * to the bottom (the next fold retries). Absent → always allowed. */ readonly canUnload?: () => boolean; + /** + * Called when a swallowed error should be surfaced to the user (e.g. a + * metrics sync failure). Wired by the composition root to the app store's + * `reportError` → full-screen error modal. Absent → errors are logged only. + */ + readonly onError?: (context: string, err: unknown) => void; } export interface ChatStore { @@ -73,6 +86,13 @@ export interface ChatStore { * turn was replayed. Drives the composer's "generating…" indicator. */ readonly generating: boolean; + /** + * The latest `provider-retry` event for the current turn, or `null` when no + * retry is pending. Drives the transient yellow "retrying…" warning banner — + * never persisted (never sent to the model or replayed on reload). Coalesces + * to the newest attempt + delay; cleared when content resumes or the turn ends. + */ + readonly providerRetry: TurnProviderRetryEvent | null; readonly pendingSync: boolean; readonly error: string | null; readonly model: string | undefined; @@ -183,9 +203,11 @@ export function createChatStore(deps: ChatStoreDependencies): ChatStore { try { const res = await deps.metricsSync(deps.conversationId); metrics = applyDurableMetrics(metrics, res.turns); - } catch { + } catch (err) { // Metrics fetch failure must not block history sync or throw; - // live-folded metrics remain intact. + // live-folded metrics remain intact. Surface via onError (modal). + console.error("[syncMetrics] failed:", err); + deps.onError?.("Failed to sync conversation metrics", err); } } @@ -251,6 +273,9 @@ export function createChatStore(deps: ChatStoreDependencies): ChatStore { get generating(): boolean { return selectGenerating(transcript); }, + get providerRetry(): TurnProviderRetryEvent | null { + return selectProviderRetry(transcript); + }, get pendingSync(): boolean { return _pendingSync; }, @@ -295,6 +320,7 @@ export function createChatStore(deps: ChatStoreDependencies): ChatStore { conversationId: deps.conversationId, message: text, ...(_model !== undefined ? { model: _model } : {}), + ...(deps.workspaceId !== undefined ? { workspaceId: deps.workspaceId } : {}), }; deps.transport.send(msg); }, @@ -306,6 +332,7 @@ export function createChatStore(deps: ChatStoreDependencies): ChatStore { type: "chat.queue", conversationId: deps.conversationId, text: trimmed, + ...(deps.workspaceId !== undefined ? { workspaceId: deps.workspaceId } : {}), }; deps.transport.send(msg); }, diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte index 2b55eb3..f2899c7 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -1,5 +1,6 @@