From 90aed7928b9fee906aef5f6c753584c8c78f20aa Mon Sep 17 00:00:00 2001
From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com>
Date: Tue, 3 Mar 2026 21:12:34 +0000
Subject: [PATCH 01/22] docs: high-priority screenshot test coverage gaps
---
.../screenshot-test-coverage-high-priority.md | 64 +++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 docs/screenshot-test-coverage-high-priority.md
diff --git a/docs/screenshot-test-coverage-high-priority.md b/docs/screenshot-test-coverage-high-priority.md
new file mode 100644
index 00000000000..0ad089a18c4
--- /dev/null
+++ b/docs/screenshot-test-coverage-high-priority.md
@@ -0,0 +1,64 @@
+# High-Priority Screenshot Test Coverage Gaps
+
+> cc @markijbema
+
+The visual regression suite auto-generates one Playwright screenshot test per Storybook story.
+The components below have **no story** and therefore **no screenshot coverage**.
+These are the highest-priority gaps to address.
+
+---
+
+## `packages/kilo-ui` — primitive component library
+
+| Component | File | Why it matters |
+|---|---|---|
+| DockPrompt | `packages/kilo-ui/src/components/dock-prompt.tsx` | Prompt dock shown at the bottom of the chat surface — core UX surface |
+| DockSurface | `packages/kilo-ui/src/components/dock-surface.tsx` | Container/surface for the dock area — wraps DockPrompt |
+
+**Fix:** Add `packages/kilo-ui/src/stories/dock-prompt.stories.tsx` and `packages/kilo-ui/src/stories/dock-surface.stories.tsx`. No changes to the test runner are needed.
+
+---
+
+## `packages/kilo-vscode` webview — chat components
+
+| Component | File | Why it matters |
+|---|---|---|
+| ChatView | `packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx` | Top-level chat panel — the main user-facing surface |
+| MessageList | `packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx` | Scrollable list of all session messages |
+| PromptInput | `packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx` | Rich text prompt input with file mentions and attachments |
+| QuestionDock | `packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx` | Bottom dock for agent questions (non-inline variant) |
+
+## `packages/kilo-vscode` webview — history
+
+| Component | File | Why it matters |
+|---|---|---|
+| SessionList | `packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx` | List of past sessions — primary navigation surface |
+
+## `packages/kilo-vscode` webview — settings
+
+| Component | File | Why it matters |
+|---|---|---|
+| Settings | `packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx` | Settings panel shell with tab navigation |
+| ProvidersTab | `packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx` | AI provider configuration — frequently changed by users |
+
+## `packages/kilo-vscode` webview — shared controls
+
+| Component | File | Why it matters |
+|---|---|---|
+| ModelSelector | `packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx` | Model picker dropdown — appears in every session |
+| ModeSwitcher | `packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx` | Agent mode switcher — prominent control in the chat header |
+
+## `packages/kilo-vscode` webview — Agent Manager
+
+| Component | File | Why it matters |
+|---|---|---|
+| AgentManagerApp | `packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx` | Root Agent Manager panel — entire multi-agent orchestration UI |
+| FileTree | `packages/kilo-vscode/webview-ui/agent-manager/FileTree.tsx` | File tree showing worktree changes per agent |
+| DiffPanel | `packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx` | Inline diff panel for reviewing agent changes |
+| FullScreenDiffView | `packages/kilo-vscode/webview-ui/agent-manager/FullScreenDiffView.tsx` | Full-screen diff review — critical review surface |
+
+---
+
+## How to add coverage
+
+For each component above, add a Storybook story file alongside the component. The test runner in `packages/kilo-ui/tests/visual-regression.spec.ts` and `packages/kilo-vscode` pick up all stories automatically — no changes to the test runner are needed.
From cc3a6fd922b27c744b0075dd8a47e04decb921a2 Mon Sep 17 00:00:00 2001
From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com>
Date: Tue, 3 Mar 2026 21:36:07 +0000
Subject: [PATCH 02/22] feat: add Storybook stories for all high-priority
screenshot test coverage gaps
- packages/kilo-ui: add dock-prompt.stories.tsx and dock-surface.stories.tsx
- packages/kilo-vscode: add chat.stories.tsx (ChatView, MessageList, QuestionDock)
- packages/kilo-vscode: add history.stories.tsx (SessionList)
- packages/kilo-vscode: add settings.stories.tsx (Settings, ProvidersTab)
- packages/kilo-vscode: add shared.stories.tsx (ModelSelector, ModeSwitcher)
- packages/kilo-vscode: add agent-manager.stories.tsx (AgentManagerApp, FileTree, DiffPanel, FullScreenDiffView)
- update StoryProviders to wrap with VSCodeProvider, ServerProvider, ConfigProvider, ProviderProvider
---
.../src/stories/dock-prompt.stories.tsx | 60 ++++++
.../src/stories/dock-surface.stories.tsx | 60 ++++++
.../webview-ui/src/stories/StoryProviders.tsx | 64 +++---
.../src/stories/agent-manager.stories.tsx | 188 ++++++++++++++++++
.../webview-ui/src/stories/chat.stories.tsx | 162 +++++++++++++++
.../src/stories/history.stories.tsx | 152 ++++++++++++++
.../src/stories/settings.stories.tsx | 38 ++++
.../webview-ui/src/stories/shared.stories.tsx | 103 ++++++++++
8 files changed, 802 insertions(+), 25 deletions(-)
create mode 100644 packages/kilo-ui/src/stories/dock-prompt.stories.tsx
create mode 100644 packages/kilo-ui/src/stories/dock-surface.stories.tsx
create mode 100644 packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
create mode 100644 packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
create mode 100644 packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
create mode 100644 packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
create mode 100644 packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
diff --git a/packages/kilo-ui/src/stories/dock-prompt.stories.tsx b/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
new file mode 100644
index 00000000000..df4b32ae6da
--- /dev/null
+++ b/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
@@ -0,0 +1,60 @@
+/** @jsxImportSource solid-js */
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
+import { Button } from "@opencode-ai/ui/button"
+
+const meta: Meta = {
+ title: "Components/DockPrompt",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+export const Question: Story = {
+ name: "Question kind",
+ render: () => (
+ Which testing framework should I use?}
+ footer={
+
+ Dismiss
+ Submit
+
+ }
+ >
+
+
Choose one of the following options:
+
+ Vitest
+ Jest
+ Playwright
+ Bun test
+
+
+
+ ),
+}
+
+export const Permission: Story = {
+ name: "Permission kind",
+ render: () => (
+ Permission required — write}
+ footer={
+
+ Deny
+ Always Allow
+ Allow Once
+
+ }
+ >
+
+ src/main.tsx
+
+ src/utils.ts
+
+
+ ),
+}
diff --git a/packages/kilo-ui/src/stories/dock-surface.stories.tsx b/packages/kilo-ui/src/stories/dock-surface.stories.tsx
new file mode 100644
index 00000000000..e602659c5de
--- /dev/null
+++ b/packages/kilo-ui/src/stories/dock-surface.stories.tsx
@@ -0,0 +1,60 @@
+/** @jsxImportSource solid-js */
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { DockShell, DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
+
+const meta: Meta = {
+ title: "Components/DockSurface",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+export const Shell: Story = {
+ name: "DockShell",
+ render: () => (
+
+ DockShell — main container body for the dock surface.
+
+ ),
+}
+
+export const Tray: Story = {
+ name: "DockTray",
+ render: () => (
+
+ DockTray — footer tray area (attach=none).
+
+ ),
+}
+
+export const TrayAttachTop: Story = {
+ name: "DockTray attach=top",
+ render: () => (
+
+ DockTray — header tray area (attach=top).
+
+ ),
+}
+
+export const ShellWithTray: Story = {
+ name: "DockShell + DockTray",
+ render: () => (
+
+
+ Body content goes here inside DockShell.
+
+
+ Footer actions slot
+
+
+ ),
+}
+
+export const ShellForm: Story = {
+ name: "DockShellForm",
+ render: () => (
+
+ DockShellForm — shell variant using a form element.
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
index dbbe0f64bea..2f2610e1cae 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/StoryProviders.tsx
@@ -4,10 +4,16 @@
*
* Instead of instantiating the full VSCodeProvider → ServerProvider → SessionProvider
* chain (which requires a real extension host / SSE connection), we provide mock
- * context values directly.
+ * context values directly. Where a real provider is safe to instantiate without an
+ * extension host (VSCodeProvider, ServerProvider, ProviderProvider), we use the real
+ * thing so components that call useVSCode()/useServer()/useProvider() don't throw.
*/
import { createSignal, type ParentComponent } from "solid-js"
+import { VSCodeProvider } from "../context/vscode"
+import { ServerProvider } from "../context/server"
+import { ProviderProvider } from "../context/provider"
+import { ConfigProvider } from "../context/config"
import { DataProvider } from "@kilocode/kilo-ui/context/data"
import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
@@ -144,29 +150,37 @@ export const StoryProviders: ParentComponent = (props) => {
const [locale] = createSignal<"en">("en")
return (
-
- "" as any,
- t,
- }}
- >
- "en", t }}>
-
-
-
-
-
- {props.noPadding ? props.children : {props.children}
}
-
-
-
-
-
-
-
-
+
+
+
+
+
+ "" as any,
+ t,
+ }}
+ >
+ "en", t }}>
+
+
+
+
+
+ {props.noPadding ? props.children : {props.children}
}
+
+
+
+
+
+
+
+
+
+
+
+
)
}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
new file mode 100644
index 00000000000..0c75731604d
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -0,0 +1,188 @@
+/** @jsxImportSource solid-js */
+/**
+ * Stories for Agent Manager components:
+ * AgentManagerApp, FileTree, DiffPanel, FullScreenDiffView
+ *
+ * FileTree, DiffPanel, and FullScreenDiffView accept data-only props and only
+ * require a LanguageContext, so they are storied with StoryProviders.
+ * AgentManagerApp sets up its own full provider chain and is rendered directly.
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { StoryProviders } from "./StoryProviders"
+import { AgentManagerApp } from "../../agent-manager/AgentManagerApp"
+import { FileTree } from "../../agent-manager/FileTree"
+import { DiffPanel } from "../../agent-manager/DiffPanel"
+import { FullScreenDiffView } from "../../agent-manager/FullScreenDiffView"
+import type { WorktreeFileDiff } from "../types/messages"
+
+// ---------------------------------------------------------------------------
+// Shared mock data
+// ---------------------------------------------------------------------------
+
+const mockDiffs: WorktreeFileDiff[] = [
+ {
+ file: "src/components/chat/ChatView.tsx",
+ status: "modified",
+ additions: 12,
+ deletions: 4,
+ before: `import { Component } from "solid-js"\n\nexport const ChatView: Component = () => {\n return
\n}\n`,
+ after: `import { Component, createSignal } from "solid-js"\n\nexport const ChatView: Component = () => {\n const [open, setOpen] = createSignal(false)\n return
\n}\n`,
+ },
+ {
+ file: "src/components/chat/MessageList.tsx",
+ status: "modified",
+ additions: 3,
+ deletions: 1,
+ before: `export const MessageList = () =>
\n`,
+ after: `export const MessageList = () => (\n
\n)\n`,
+ },
+ {
+ file: "src/stories/chat.stories.tsx",
+ status: "added",
+ additions: 80,
+ deletions: 0,
+ before: "",
+ after: `/** @jsxImportSource solid-js */\nimport type { Meta } from "storybook-solidjs-vite"\nconst meta: Meta = { title: "Chat" }\nexport default meta\n`,
+ },
+]
+
+// ---------------------------------------------------------------------------
+// Meta
+// ---------------------------------------------------------------------------
+
+const meta: Meta = {
+ title: "AgentManager",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+// ---------------------------------------------------------------------------
+// FileTree
+// ---------------------------------------------------------------------------
+
+export const FileTreeWithChanges: Story = {
+ name: "FileTree — with modifications and additions",
+ render: () => (
+
+
+ {}}
+ showSummary
+ />
+
+
+ ),
+}
+
+export const FileTreeEmpty: Story = {
+ name: "FileTree — no changes",
+ render: () => (
+
+
+ {}} />
+
+
+ ),
+}
+
+// ---------------------------------------------------------------------------
+// DiffPanel
+// ---------------------------------------------------------------------------
+
+export const DiffPanelLoading: Story = {
+ name: "DiffPanel — loading",
+ render: () => (
+
+
+ {}}
+ onClose={() => {}}
+ />
+
+
+ ),
+}
+
+export const DiffPanelWithDiffs: Story = {
+ name: "DiffPanel — with diffs (unified)",
+ render: () => (
+
+
+ {}}
+ comments={[]}
+ onCommentsChange={() => {}}
+ onClose={() => {}}
+ onExpand={() => {}}
+ />
+
+
+ ),
+}
+
+// ---------------------------------------------------------------------------
+// FullScreenDiffView
+// ---------------------------------------------------------------------------
+
+export const FullScreenDiffLoading: Story = {
+ name: "FullScreenDiffView — loading",
+ render: () => (
+
+
+ {}}
+ comments={[]}
+ onCommentsChange={() => {}}
+ onClose={() => {}}
+ />
+
+
+ ),
+}
+
+export const FullScreenDiffWithChanges: Story = {
+ name: "FullScreenDiffView — with changes",
+ render: () => (
+
+
+ {}}
+ comments={[]}
+ onCommentsChange={() => {}}
+ onClose={() => {}}
+ />
+
+
+ ),
+}
+
+// ---------------------------------------------------------------------------
+// AgentManagerApp
+// ---------------------------------------------------------------------------
+
+export const AgentManagerRoot: Story = {
+ name: "AgentManagerApp — initial connecting state",
+ render: () => (
+ // AgentManagerApp sets up its own VSCodeProvider + ThemeProvider + layout.
+ // In Storybook it renders its empty/connecting state (no extension host).
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
new file mode 100644
index 00000000000..d34da6baa86
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -0,0 +1,162 @@
+/** @jsxImportSource solid-js */
+/**
+ * Stories for high-priority chat components:
+ * ChatView, MessageList, QuestionDock
+ *
+ * These render with mocked session/server/provider contexts — the components
+ * will show their "idle / empty" states since no real extension host is connected.
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { StoryProviders } from "./StoryProviders"
+import { ChatView } from "../components/chat/ChatView"
+import { MessageList } from "../components/chat/MessageList"
+import { QuestionDock } from "../components/chat/QuestionDock"
+import type { QuestionRequest } from "../types/messages"
+
+const SESSION_ID = "story-session-chat-001"
+
+// ---------------------------------------------------------------------------
+// Question fixtures
+// ---------------------------------------------------------------------------
+
+const singleQuestion: QuestionRequest = {
+ id: "q-single-001",
+ sessionID: SESSION_ID,
+ questions: [
+ {
+ question: "Which testing framework should I use for this project?",
+ header: "Choose a framework",
+ options: [
+ { label: "Vitest", description: "Fast, Vite-native unit testing" },
+ { label: "Jest", description: "Widely adopted, rich ecosystem" },
+ { label: "Playwright", description: "End-to-end browser testing" },
+ { label: "Bun test", description: "Built-in, zero config" },
+ ],
+ },
+ ],
+ tool: { messageID: "asst-msg-001", callID: "call-question-001" },
+}
+
+const multiQuestion: QuestionRequest = {
+ id: "q-multi-001",
+ sessionID: SESSION_ID,
+ questions: [
+ {
+ question: "Which testing framework?",
+ options: [{ label: "Vitest" }, { label: "Jest" }, { label: "Bun test" }],
+ },
+ {
+ question: "Should I include coverage reporting?",
+ options: [{ label: "Yes, Istanbul" }, { label: "Yes, V8" }, { label: "No" }],
+ },
+ ],
+ tool: { messageID: "asst-msg-001", callID: "call-question-002" },
+}
+
+// ---------------------------------------------------------------------------
+// Meta
+// ---------------------------------------------------------------------------
+
+const meta: Meta = {
+ title: "Chat",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+// ---------------------------------------------------------------------------
+// ChatView stories
+// ---------------------------------------------------------------------------
+
+export const ChatViewIdle: Story = {
+ name: "ChatView — idle (empty)",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const ChatViewReadonly: Story = {
+ name: "ChatView — readonly",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const ChatViewWithQuestion: Story = {
+ name: "ChatView — with QuestionDock",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+// ---------------------------------------------------------------------------
+// MessageList stories
+// ---------------------------------------------------------------------------
+
+export const MessageListEmpty: Story = {
+ name: "MessageList — empty",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const MessageListLoading: Story = {
+ name: "MessageList — loading",
+ render: () => {
+ const loadingSession = {
+ loading: () => true,
+ }
+ // Render empty state — the real "loading" state requires overriding session.loading()
+ // which we approximate here: the component will show its empty-state logo instead.
+ return (
+
+
+
+
+
+ )
+ },
+}
+
+// ---------------------------------------------------------------------------
+// QuestionDock stories
+// ---------------------------------------------------------------------------
+
+export const QuestionDockSingle: Story = {
+ name: "QuestionDock — single question",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const QuestionDockMulti: Story = {
+ name: "QuestionDock — multi-question wizard",
+ render: () => (
+
+
+
+
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
new file mode 100644
index 00000000000..82ea82fa7f6
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
@@ -0,0 +1,152 @@
+/** @jsxImportSource solid-js */
+/**
+ * Stories for the SessionList component (history panel).
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { createSignal, type ParentComponent } from "solid-js"
+import { DialogProvider } from "@kilocode/kilo-ui/context/dialog"
+import { DataProvider } from "@kilocode/kilo-ui/context/data"
+import { DiffComponentProvider } from "@kilocode/kilo-ui/context/diff"
+import { CodeComponentProvider } from "@kilocode/kilo-ui/context/code"
+import { MarkedProvider } from "@kilocode/kilo-ui/context/marked"
+import { I18nProvider } from "@kilocode/kilo-ui/context"
+import { Diff } from "@kilocode/kilo-ui/diff"
+import { Code } from "@kilocode/kilo-ui/code"
+import { VSCodeProvider } from "../context/vscode"
+import { ServerProvider } from "../context/server"
+import { ConfigProvider } from "../context/config"
+import { ProviderProvider } from "../context/provider"
+import { SessionContext } from "../context/session"
+import { LanguageContext } from "../context/language"
+import { dict as uiEn } from "@kilocode/kilo-ui/i18n/en"
+import { dict as appEn } from "../i18n/en"
+import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
+import { SessionList } from "../components/history/SessionList"
+
+const dict: Record = { ...appEn, ...uiEn, ...kiloEn }
+function t(key: string) { return dict[key] ?? key }
+function noop() {}
+
+const now = new Date().toISOString()
+const yesterday = new Date(Date.now() - 86400000).toISOString()
+const weekAgo = new Date(Date.now() - 7 * 86400000).toISOString()
+
+const mockSessions = [
+ { id: "s1", title: "Refactor authentication module", createdAt: now, updatedAt: now },
+ { id: "s2", title: "Add screenshot test coverage", createdAt: yesterday, updatedAt: yesterday },
+ { id: "s3", title: "Fix TypeScript errors in webview", createdAt: weekAgo, updatedAt: weekAgo },
+ { id: "s4", title: undefined, createdAt: weekAgo, updatedAt: weekAgo },
+]
+
+const WithSessions: ParentComponent<{ sessions?: typeof mockSessions }> = (props) => {
+ const [locale] = createSignal<"en">("en")
+ const sessions = props.sessions ?? []
+ const session = {
+ currentSessionID: () => "s1",
+ currentSession: () => sessions[0],
+ setCurrentSessionID: noop,
+ sessions: () => sessions as any,
+ status: () => "idle" as const,
+ statusInfo: () => ({ type: "idle" }),
+ statusText: () => undefined,
+ busySince: () => undefined,
+ loading: () => false,
+ messages: () => [],
+ userMessages: () => [],
+ allMessages: () => ({}),
+ allParts: () => ({}),
+ allStatusMap: () => ({}),
+ getParts: () => [],
+ todos: () => [],
+ permissions: () => [],
+ questions: () => [],
+ questionErrors: () => new Set(),
+ selected: () => ({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }),
+ selectModel: noop,
+ totalCost: () => 0,
+ contextUsage: () => undefined,
+ agents: () => [{ name: "code", description: "Code mode", mode: "primary" as const }],
+ selectedAgent: () => "code",
+ selectAgent: noop,
+ getSessionAgent: () => "code",
+ getSessionModel: () => ({ providerID: "anthropic", modelID: "claude-sonnet-4-20250514" }),
+ setSessionModel: noop,
+ setSessionAgent: noop,
+ variantList: () => [],
+ currentVariant: () => undefined,
+ selectVariant: noop,
+ sendMessage: noop,
+ abort: noop,
+ compact: noop,
+ respondToPermission: noop,
+ replyToQuestion: noop,
+ rejectQuestion: noop,
+ createSession: noop,
+ clearCurrentSession: noop,
+ loadSessions: noop,
+ selectSession: noop,
+ deleteSession: noop,
+ renameSession: noop,
+ syncSession: noop,
+ cloudPreviewId: () => null,
+ selectCloudSession: noop,
+ }
+
+ return (
+
+
+
+
+
+ "" as any, t }}>
+ "en", t }}>
+
+
+
+
+
+ {props.children}
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+const meta: Meta = {
+ title: "History/SessionList",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+export const Empty: Story = {
+ name: "Empty list",
+ render: () => (
+
+
+
+
+
+ ),
+}
+
+export const WithItems: Story = {
+ name: "With sessions",
+ render: () => (
+
+
+
+
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
new file mode 100644
index 00000000000..7bc720d822c
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
@@ -0,0 +1,38 @@
+/** @jsxImportSource solid-js */
+/**
+ * Stories for Settings and ProvidersTab components.
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { StoryProviders } from "./StoryProviders"
+import Settings from "../components/settings/Settings"
+import ProvidersTab from "../components/settings/ProvidersTab"
+
+const meta: Meta = {
+ title: "Settings",
+ parameters: { layout: "fullscreen" },
+}
+export default meta
+type Story = StoryObj
+
+export const SettingsPanel: Story = {
+ name: "Settings — full panel",
+ render: () => (
+
+
+ {}} />
+
+
+ ),
+}
+
+export const ProvidersConfigure: Story = {
+ name: "ProvidersTab — no providers configured",
+ render: () => (
+
+
+
+ ),
+}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
new file mode 100644
index 00000000000..1c17d51ab50
--- /dev/null
+++ b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
@@ -0,0 +1,103 @@
+/** @jsxImportSource solid-js */
+/**
+ * Stories for shared controls: ModelSelector and ModeSwitcher.
+ */
+
+import type { Meta, StoryObj } from "storybook-solidjs-vite"
+import { StoryProviders } from "./StoryProviders"
+import { ModelSelectorBase } from "../components/shared/ModelSelector"
+import { ModeSwitcherBase, ModeSwitcher } from "../components/shared/ModeSwitcher"
+
+const agents = [
+ { name: "code", description: "Write, edit and review code", mode: "primary" as const },
+ { name: "ask", description: "Answer questions without making changes", mode: "primary" as const },
+ { name: "architect", description: "Plan and design before implementation", mode: "primary" as const },
+ { name: "debug", description: "Diagnose and fix issues", mode: "primary" as const },
+]
+
+const meta: Meta = {
+ title: "Shared",
+ parameters: { layout: "padded" },
+}
+export default meta
+type Story = StoryObj
+
+// ---------------------------------------------------------------------------
+// ModelSelector
+// ---------------------------------------------------------------------------
+
+export const ModelSelectorNoProviders: Story = {
+ name: "ModelSelector — no providers",
+ render: () => (
+
+
+ {}}
+ placement="bottom-start"
+ />
+
+
+ ),
+}
+
+export const ModelSelectorAllowClear: Story = {
+ name: "ModelSelector — allow clear",
+ render: () => (
+
+
+ {}}
+ placement="bottom-start"
+ allowClear
+ clearLabel="Use default model"
+ />
+
+
+ ),
+}
+
+// ---------------------------------------------------------------------------
+// ModeSwitcher
+// ---------------------------------------------------------------------------
+
+export const ModeSwitcherSingle: Story = {
+ name: "ModeSwitcherBase — single agent (hidden)",
+ render: () => (
+
+ {}}
+ />
+
+ ),
+}
+
+export const ModeSwitcherMultiple: Story = {
+ name: "ModeSwitcherBase — multiple agents",
+ render: () => (
+
+ {}} />
+
+ ),
+}
+
+export const ModeSwitcherAskSelected: Story = {
+ name: "ModeSwitcherBase — ask mode selected",
+ render: () => (
+
+ {}} />
+
+ ),
+}
+
+export const ModeSwitcherFromSession: Story = {
+ name: "ModeSwitcher — wired to session context",
+ render: () => (
+
+
+
+ ),
+}
From d5b020f7b0b4a84fe6320e2acabfd1c2a2982ce2 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 09:14:38 +0100
Subject: [PATCH 03/22] set screenshot story widths to 420px
---
.../src/stories/dock-prompt.stories.tsx | 84 +++++++++++--------
.../src/stories/dock-surface.stories.tsx | 39 ++++++---
.../src/stories/agent-manager.stories.tsx | 29 ++-----
.../webview-ui/src/stories/chat.stories.tsx | 10 +--
.../src/stories/history.stories.tsx | 22 ++++-
.../src/stories/settings.stories.tsx | 4 +-
6 files changed, 109 insertions(+), 79 deletions(-)
diff --git a/packages/kilo-ui/src/stories/dock-prompt.stories.tsx b/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
index df4b32ae6da..a84b036e3ca 100644
--- a/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
+++ b/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
@@ -13,48 +13,62 @@ type Story = StoryObj
export const Question: Story = {
name: "Question kind",
render: () => (
- Which testing framework should I use?}
- footer={
-
-
Dismiss
-
Submit
+
+
Which testing framework should I use?}
+ footer={
+
+
+ Dismiss
+
+
+ Submit
+
+
+ }
+ >
+
+
Choose one of the following options:
+
+ Vitest
+ Jest
+ Playwright
+ Bun test
+
- }
- >
-
-
Choose one of the following options:
-
- Vitest
- Jest
- Playwright
- Bun test
-
-
-
+
+
),
}
export const Permission: Story = {
name: "Permission kind",
render: () => (
-
Permission required — write}
- footer={
-
-
Deny
-
Always Allow
-
Allow Once
+
+
Permission required — write}
+ footer={
+
+
+ Deny
+
+
+ Always Allow
+
+
+ Allow Once
+
+
+ }
+ >
+
+ src/main.tsx
+
+ src/utils.ts
- }
- >
-
- src/main.tsx
-
- src/utils.ts
-
-
+
+
),
}
diff --git a/packages/kilo-ui/src/stories/dock-surface.stories.tsx b/packages/kilo-ui/src/stories/dock-surface.stories.tsx
index e602659c5de..3e9296a96da 100644
--- a/packages/kilo-ui/src/stories/dock-surface.stories.tsx
+++ b/packages/kilo-ui/src/stories/dock-surface.stories.tsx
@@ -12,34 +12,45 @@ type Story = StoryObj
export const Shell: Story = {
name: "DockShell",
render: () => (
-
- DockShell — main container body for the dock surface.
-
+
+
+ DockShell — main container body for the dock surface.
+
+
),
}
export const Tray: Story = {
name: "DockTray",
render: () => (
-
- DockTray — footer tray area (attach=none).
-
+
+
+ DockTray — footer tray area (attach=none).
+
+
),
}
export const TrayAttachTop: Story = {
name: "DockTray attach=top",
render: () => (
-
- DockTray — header tray area (attach=top).
-
+
+
+ DockTray — header tray area (attach=top).
+
+
),
}
export const ShellWithTray: Story = {
name: "DockShell + DockTray",
render: () => (
-
+
Body content goes here inside DockShell.
@@ -53,8 +64,10 @@ export const ShellWithTray: Story = {
export const ShellForm: Story = {
name: "DockShellForm",
render: () => (
-
- DockShellForm — shell variant using a form element.
-
+
+
+ DockShellForm — shell variant using a form element.
+
+
),
}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
index 0c75731604d..bbc9232d96d 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -66,13 +66,8 @@ export const FileTreeWithChanges: Story = {
name: "FileTree — with modifications and additions",
render: () => (
-
-
{}}
- showSummary
- />
+
+ {}} showSummary />
),
@@ -82,7 +77,7 @@ export const FileTreeEmpty: Story = {
name: "FileTree — no changes",
render: () => (
-
+
{}} />
@@ -97,14 +92,8 @@ export const DiffPanelLoading: Story = {
name: "DiffPanel — loading",
render: () => (
-
-
{}}
- onClose={() => {}}
- />
+
+ {}} onClose={() => {}} />
),
@@ -114,7 +103,7 @@ export const DiffPanelWithDiffs: Story = {
name: "DiffPanel — with diffs (unified)",
render: () => (
-
+
(
-
+
(
-
+
(
// AgentManagerApp sets up its own VSCodeProvider + ThemeProvider + layout.
// In Storybook it renders its empty/connecting state (no extension host).
-
+
),
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
index d34da6baa86..7c66c1d50cb 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -73,7 +73,7 @@ export const ChatViewIdle: Story = {
name: "ChatView — idle (empty)",
render: () => (
-
+
@@ -84,7 +84,7 @@ export const ChatViewReadonly: Story = {
name: "ChatView — readonly",
render: () => (
-
+
@@ -95,7 +95,7 @@ export const ChatViewWithQuestion: Story = {
name: "ChatView — with QuestionDock",
render: () => (
-
+
@@ -110,7 +110,7 @@ export const MessageListEmpty: Story = {
name: "MessageList — empty",
render: () => (
-
+
@@ -127,7 +127,7 @@ export const MessageListLoading: Story = {
// which we approximate here: the component will show its empty-state logo instead.
return (
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
index 82ea82fa7f6..87423caba6d 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
@@ -25,7 +25,9 @@ import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
import { SessionList } from "../components/history/SessionList"
const dict: Record
= { ...appEn, ...uiEn, ...kiloEn }
-function t(key: string) { return dict[key] ?? key }
+function t(key: string) {
+ return dict[key] ?? key
+}
function noop() {}
const now = new Date().toISOString()
@@ -102,7 +104,19 @@ const WithSessions: ParentComponent<{ sessions?: typeof mockSessions }> = (props
"" as any, t }}>
"en", t }}>
-
+
@@ -133,7 +147,7 @@ export const Empty: Story = {
name: "Empty list",
render: () => (
-
+
@@ -144,7 +158,7 @@ export const WithItems: Story = {
name: "With sessions",
render: () => (
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
index 7bc720d822c..8de75b4c2c5 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/settings.stories.tsx
@@ -19,7 +19,7 @@ export const SettingsPanel: Story = {
name: "Settings — full panel",
render: () => (
-
+
{}} />
@@ -30,7 +30,7 @@ export const ProvidersConfigure: Story = {
name: "ProvidersTab — no providers configured",
render: () => (
-
+
From 78ec69aa35d92e62168ea0b843f0bef8c808fd24 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 09:18:59 +0100
Subject: [PATCH 04/22] remove screenshot test coverage doc
---
.../screenshot-test-coverage-high-priority.md | 64 -------------------
1 file changed, 64 deletions(-)
delete mode 100644 docs/screenshot-test-coverage-high-priority.md
diff --git a/docs/screenshot-test-coverage-high-priority.md b/docs/screenshot-test-coverage-high-priority.md
deleted file mode 100644
index 0ad089a18c4..00000000000
--- a/docs/screenshot-test-coverage-high-priority.md
+++ /dev/null
@@ -1,64 +0,0 @@
-# High-Priority Screenshot Test Coverage Gaps
-
-> cc @markijbema
-
-The visual regression suite auto-generates one Playwright screenshot test per Storybook story.
-The components below have **no story** and therefore **no screenshot coverage**.
-These are the highest-priority gaps to address.
-
----
-
-## `packages/kilo-ui` — primitive component library
-
-| Component | File | Why it matters |
-|---|---|---|
-| DockPrompt | `packages/kilo-ui/src/components/dock-prompt.tsx` | Prompt dock shown at the bottom of the chat surface — core UX surface |
-| DockSurface | `packages/kilo-ui/src/components/dock-surface.tsx` | Container/surface for the dock area — wraps DockPrompt |
-
-**Fix:** Add `packages/kilo-ui/src/stories/dock-prompt.stories.tsx` and `packages/kilo-ui/src/stories/dock-surface.stories.tsx`. No changes to the test runner are needed.
-
----
-
-## `packages/kilo-vscode` webview — chat components
-
-| Component | File | Why it matters |
-|---|---|---|
-| ChatView | `packages/kilo-vscode/webview-ui/src/components/chat/ChatView.tsx` | Top-level chat panel — the main user-facing surface |
-| MessageList | `packages/kilo-vscode/webview-ui/src/components/chat/MessageList.tsx` | Scrollable list of all session messages |
-| PromptInput | `packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx` | Rich text prompt input with file mentions and attachments |
-| QuestionDock | `packages/kilo-vscode/webview-ui/src/components/chat/QuestionDock.tsx` | Bottom dock for agent questions (non-inline variant) |
-
-## `packages/kilo-vscode` webview — history
-
-| Component | File | Why it matters |
-|---|---|---|
-| SessionList | `packages/kilo-vscode/webview-ui/src/components/history/SessionList.tsx` | List of past sessions — primary navigation surface |
-
-## `packages/kilo-vscode` webview — settings
-
-| Component | File | Why it matters |
-|---|---|---|
-| Settings | `packages/kilo-vscode/webview-ui/src/components/settings/Settings.tsx` | Settings panel shell with tab navigation |
-| ProvidersTab | `packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx` | AI provider configuration — frequently changed by users |
-
-## `packages/kilo-vscode` webview — shared controls
-
-| Component | File | Why it matters |
-|---|---|---|
-| ModelSelector | `packages/kilo-vscode/webview-ui/src/components/shared/ModelSelector.tsx` | Model picker dropdown — appears in every session |
-| ModeSwitcher | `packages/kilo-vscode/webview-ui/src/components/shared/ModeSwitcher.tsx` | Agent mode switcher — prominent control in the chat header |
-
-## `packages/kilo-vscode` webview — Agent Manager
-
-| Component | File | Why it matters |
-|---|---|---|
-| AgentManagerApp | `packages/kilo-vscode/webview-ui/agent-manager/AgentManagerApp.tsx` | Root Agent Manager panel — entire multi-agent orchestration UI |
-| FileTree | `packages/kilo-vscode/webview-ui/agent-manager/FileTree.tsx` | File tree showing worktree changes per agent |
-| DiffPanel | `packages/kilo-vscode/webview-ui/agent-manager/DiffPanel.tsx` | Inline diff panel for reviewing agent changes |
-| FullScreenDiffView | `packages/kilo-vscode/webview-ui/agent-manager/FullScreenDiffView.tsx` | Full-screen diff review — critical review surface |
-
----
-
-## How to add coverage
-
-For each component above, add a Storybook story file alongside the component. The test runner in `packages/kilo-ui/tests/visual-regression.spec.ts` and `packages/kilo-vscode` pick up all stories automatically — no changes to the test runner are needed.
From 8fe1dd29119ac4d741e08915cc02b57a120265a3 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 09:22:27 +0100
Subject: [PATCH 05/22] fix vscode visual regression spec to generate one test
per story
---
.../tests/visual-regression.spec.ts | 52 ++++++++-----------
1 file changed, 22 insertions(+), 30 deletions(-)
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.ts
index 6cf917ba004..7285adb2c80 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts
@@ -48,35 +48,27 @@ async function disableAnimations(page: Page) {
// Stories to skip from visual regression (add IDs here if needed)
const SKIP = new Set([])
-// Use test.describe + beforeAll to avoid top-level await which breaks
-// on Node.js versions with strict ESM/CJS boundaries.
-test.describe("Visual Regression", () => {
- let stories: Story[] = []
+// Generate one test() per story so Playwright's scheduler can distribute
+// them freely across workers — no manual sharding needed.
+// Skip fetching stories on macOS since test.skip() above already marks the file skipped.
+const stories = IS_DARWIN ? [] : (await fetchStories()).filter((s) => !SKIP.has(s.id))
- test.beforeAll(async () => {
- if (IS_DARWIN) return
- stories = (await fetchStories()).filter((s) => !SKIP.has(s.id))
+for (const story of stories) {
+ test(`${story.title} / ${story.name}`, async ({ page }) => {
+ // Narrow stories (IDs ending in "-200") use a 200px viewport
+ // The "-200" suffix comes from the export name convention (e.g. Default200, WithThinking200)
+ const narrow = story.id.endsWith("-200")
+ await page.setViewportSize({ width: narrow ? 200 : 420, height: 720 })
+
+ await page.goto(
+ `/iframe.html?id=${story.id}&viewMode=story&globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern`,
+ { waitUntil: "load" },
+ )
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+
+ const [component, variant] = story.id.split("--")
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot([component!, `${variant}.png`])
})
-
- test("all stories", async ({ page }) => {
- if (IS_DARWIN) return
- for (const story of stories) {
- // Narrow stories (IDs ending in "-200") use a 200px viewport
- // The "-200" suffix comes from the export name convention (e.g. Default200, WithThinking200)
- const narrow = story.id.endsWith("-200")
- await page.setViewportSize({ width: narrow ? 200 : 420, height: 720 })
-
- // Use kilo-vscode theme by default (matched to the preview initialGlobals)
- await page.goto(
- `/iframe.html?id=${story.id}&viewMode=story&globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern`,
- { waitUntil: "load" },
- )
- await disableAnimations(page)
- await page.waitForSelector("#storybook-root *", { state: "attached" })
-
- const [component, variant] = story.id.split("--")
- const root = page.locator("#storybook-root")
- await expect(root).toHaveScreenshot([component!, `${variant}.png`])
- }
- })
-})
+}
From 0f885631766aad8a2b6c61ec8502c6461bc3eef4 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 09:25:14 +0100
Subject: [PATCH 06/22] remove dock-prompt story
---
.../src/stories/dock-prompt.stories.tsx | 74 -------------------
1 file changed, 74 deletions(-)
delete mode 100644 packages/kilo-ui/src/stories/dock-prompt.stories.tsx
diff --git a/packages/kilo-ui/src/stories/dock-prompt.stories.tsx b/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
deleted file mode 100644
index a84b036e3ca..00000000000
--- a/packages/kilo-ui/src/stories/dock-prompt.stories.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-/** @jsxImportSource solid-js */
-import type { Meta, StoryObj } from "storybook-solidjs-vite"
-import { DockPrompt } from "@opencode-ai/ui/dock-prompt"
-import { Button } from "@opencode-ai/ui/button"
-
-const meta: Meta = {
- title: "Components/DockPrompt",
- parameters: { layout: "padded" },
-}
-export default meta
-type Story = StoryObj
-
-export const Question: Story = {
- name: "Question kind",
- render: () => (
-
-
Which testing framework should I use?}
- footer={
-
-
- Dismiss
-
-
- Submit
-
-
- }
- >
-
-
Choose one of the following options:
-
- Vitest
- Jest
- Playwright
- Bun test
-
-
-
-
- ),
-}
-
-export const Permission: Story = {
- name: "Permission kind",
- render: () => (
-
-
Permission required — write}
- footer={
-
-
- Deny
-
-
- Always Allow
-
-
- Allow Once
-
-
- }
- >
-
- src/main.tsx
-
- src/utils.ts
-
-
-
- ),
-}
From 8ec15e63a55fd4ae22deb7f1cb8232496f6c4234 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 10:20:22 +0100
Subject: [PATCH 07/22] fix SessionList import to use default import
---
packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
index 87423caba6d..fd31e252593 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
@@ -22,7 +22,7 @@ import { LanguageContext } from "../context/language"
import { dict as uiEn } from "@kilocode/kilo-ui/i18n/en"
import { dict as appEn } from "../i18n/en"
import { dict as kiloEn } from "@kilocode/kilo-i18n/en"
-import { SessionList } from "../components/history/SessionList"
+import SessionList from "../components/history/SessionList"
const dict: Record = { ...appEn, ...uiEn, ...kiloEn }
function t(key: string) {
From 33bc5e95188a999547dd297991a45df327b2895b Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 10:30:00 +0100
Subject: [PATCH 08/22] add tsconfig.test.json so Playwright can use top-level
await in spec
---
packages/kilo-vscode/playwright.config.ts | 4 ++--
packages/kilo-vscode/tsconfig.test.json | 11 +++++++++++
2 files changed, 13 insertions(+), 2 deletions(-)
create mode 100644 packages/kilo-vscode/tsconfig.test.json
diff --git a/packages/kilo-vscode/playwright.config.ts b/packages/kilo-vscode/playwright.config.ts
index 30680574fc8..993cc008cc3 100644
--- a/packages/kilo-vscode/playwright.config.ts
+++ b/packages/kilo-vscode/playwright.config.ts
@@ -3,6 +3,7 @@ import { defineConfig, devices } from "@playwright/test"
export default defineConfig({
testDir: "./tests",
testMatch: "**/*.spec.ts",
+ tsconfig: "./tsconfig.test.json",
fullyParallel: true,
forbidOnly: !!process.env["CI"],
retries: process.env["CI"] ? 1 : 0,
@@ -24,8 +25,7 @@ export default defineConfig({
},
],
webServer: {
- command:
- "bunx storybook build -o ./storybook-static && bunx http-server ./storybook-static -p 6007 --silent",
+ command: "bunx storybook build -o ./storybook-static && bunx http-server ./storybook-static -p 6007 --silent",
url: "http://localhost:6007",
reuseExistingServer: !process.env["CI"],
timeout: 300_000,
diff --git a/packages/kilo-vscode/tsconfig.test.json b/packages/kilo-vscode/tsconfig.test.json
new file mode 100644
index 00000000000..bc928dac9a1
--- /dev/null
+++ b/packages/kilo-vscode/tsconfig.test.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "target": "ESNext",
+ "lib": ["ESNext"],
+ "strict": true,
+ "skipLibCheck": true
+ },
+ "include": ["tests/**/*"]
+}
From 3ab5e38137f5604cb83edb1f27922f23ca60df16 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 10:35:05 +0100
Subject: [PATCH 09/22] rename spec to .mts so Node treats it as ESM (enables
top-level await)
---
packages/kilo-vscode/playwright.config.ts | 2 +-
.../{visual-regression.spec.ts => visual-regression.spec.mts} | 0
2 files changed, 1 insertion(+), 1 deletion(-)
rename packages/kilo-vscode/tests/{visual-regression.spec.ts => visual-regression.spec.mts} (100%)
diff --git a/packages/kilo-vscode/playwright.config.ts b/packages/kilo-vscode/playwright.config.ts
index 993cc008cc3..bd40b8cec4e 100644
--- a/packages/kilo-vscode/playwright.config.ts
+++ b/packages/kilo-vscode/playwright.config.ts
@@ -2,7 +2,7 @@ import { defineConfig, devices } from "@playwright/test"
export default defineConfig({
testDir: "./tests",
- testMatch: "**/*.spec.ts",
+ testMatch: "**/*.spec.{ts,mts}",
tsconfig: "./tsconfig.test.json",
fullyParallel: true,
forbidOnly: !!process.env["CI"],
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.mts
similarity index 100%
rename from packages/kilo-vscode/tests/visual-regression.spec.ts
rename to packages/kilo-vscode/tests/visual-regression.spec.mts
From a44ad92d81a81b97f2f5834ccf97dabcb872c9a7 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 10:40:28 +0100
Subject: [PATCH 10/22] fix workflow git add path for .mts snapshot dir
---
.github/workflows/visual-regression.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
index 4d23526c065..e9dbfaf75fc 100644
--- a/.github/workflows/visual-regression.yml
+++ b/.github/workflows/visual-regression.yml
@@ -172,7 +172,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
+ git add packages/kilo-vscode/tests/visual-regression.spec.mts-snapshots/
if git diff --cached --quiet; then
echo "No new baselines — nothing to commit."
echo "changed=false" >> "$GITHUB_OUTPUT"
From dd4ed945d4b3c0d13a76efc11f85099291dbcd20 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 11:03:44 +0100
Subject: [PATCH 11/22] remove unwanted stories
---
.../src/stories/dock-surface.stories.tsx | 73 -------------------
.../src/stories/agent-manager.stories.tsx | 29 +-------
.../webview-ui/src/stories/chat.stories.tsx | 29 --------
.../src/stories/history.stories.tsx | 11 ---
.../webview-ui/src/stories/shared.stories.tsx | 24 +-----
5 files changed, 2 insertions(+), 164 deletions(-)
delete mode 100644 packages/kilo-ui/src/stories/dock-surface.stories.tsx
diff --git a/packages/kilo-ui/src/stories/dock-surface.stories.tsx b/packages/kilo-ui/src/stories/dock-surface.stories.tsx
deleted file mode 100644
index 3e9296a96da..00000000000
--- a/packages/kilo-ui/src/stories/dock-surface.stories.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-/** @jsxImportSource solid-js */
-import type { Meta, StoryObj } from "storybook-solidjs-vite"
-import { DockShell, DockShellForm, DockTray } from "@opencode-ai/ui/dock-surface"
-
-const meta: Meta = {
- title: "Components/DockSurface",
- parameters: { layout: "padded" },
-}
-export default meta
-type Story = StoryObj
-
-export const Shell: Story = {
- name: "DockShell",
- render: () => (
-
-
- DockShell — main container body for the dock surface.
-
-
- ),
-}
-
-export const Tray: Story = {
- name: "DockTray",
- render: () => (
-
-
- DockTray — footer tray area (attach=none).
-
-
- ),
-}
-
-export const TrayAttachTop: Story = {
- name: "DockTray attach=top",
- render: () => (
-
-
- DockTray — header tray area (attach=top).
-
-
- ),
-}
-
-export const ShellWithTray: Story = {
- name: "DockShell + DockTray",
- render: () => (
-
-
- Body content goes here inside DockShell.
-
-
- Footer actions slot
-
-
- ),
-}
-
-export const ShellForm: Story = {
- name: "DockShellForm",
- render: () => (
-
-
- DockShellForm — shell variant using a form element.
-
-
- ),
-}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
index bbc9232d96d..5c211cf379e 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -1,16 +1,11 @@
/** @jsxImportSource solid-js */
/**
* Stories for Agent Manager components:
- * AgentManagerApp, FileTree, DiffPanel, FullScreenDiffView
- *
- * FileTree, DiffPanel, and FullScreenDiffView accept data-only props and only
- * require a LanguageContext, so they are storied with StoryProviders.
- * AgentManagerApp sets up its own full provider chain and is rendered directly.
+ * FileTree, DiffPanel, FullScreenDiffView
*/
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
-import { AgentManagerApp } from "../../agent-manager/AgentManagerApp"
import { FileTree } from "../../agent-manager/FileTree"
import { DiffPanel } from "../../agent-manager/DiffPanel"
import { FullScreenDiffView } from "../../agent-manager/FullScreenDiffView"
@@ -88,17 +83,6 @@ export const FileTreeEmpty: Story = {
// DiffPanel
// ---------------------------------------------------------------------------
-export const DiffPanelLoading: Story = {
- name: "DiffPanel — loading",
- render: () => (
-
-
- {}} onClose={() => {}} />
-
-
- ),
-}
-
export const DiffPanelWithDiffs: Story = {
name: "DiffPanel — with diffs (unified)",
render: () => (
@@ -164,14 +148,3 @@ export const FullScreenDiffWithChanges: Story = {
// ---------------------------------------------------------------------------
// AgentManagerApp
// ---------------------------------------------------------------------------
-
-export const AgentManagerRoot: Story = {
- name: "AgentManagerApp — initial connecting state",
- render: () => (
- // AgentManagerApp sets up its own VSCodeProvider + ThemeProvider + layout.
- // In Storybook it renders its empty/connecting state (no extension host).
-
- ),
-}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
index 7c66c1d50cb..a9f15e705a7 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -80,17 +80,6 @@ export const ChatViewIdle: Story = {
),
}
-export const ChatViewReadonly: Story = {
- name: "ChatView — readonly",
- render: () => (
-
-
-
-
-
- ),
-}
-
export const ChatViewWithQuestion: Story = {
name: "ChatView — with QuestionDock",
render: () => (
@@ -117,24 +106,6 @@ export const MessageListEmpty: Story = {
),
}
-export const MessageListLoading: Story = {
- name: "MessageList — loading",
- render: () => {
- const loadingSession = {
- loading: () => true,
- }
- // Render empty state — the real "loading" state requires overriding session.loading()
- // which we approximate here: the component will show its empty-state logo instead.
- return (
-
-
-
-
-
- )
- },
-}
-
// ---------------------------------------------------------------------------
// QuestionDock stories
// ---------------------------------------------------------------------------
diff --git a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
index fd31e252593..c70c030df69 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
@@ -143,17 +143,6 @@ const meta: Meta = {
export default meta
type Story = StoryObj
-export const Empty: Story = {
- name: "Empty list",
- render: () => (
-
-
-
-
-
- ),
-}
-
export const WithItems: Story = {
name: "With sessions",
render: () => (
diff --git a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
index 1c17d51ab50..790e7e857fd 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
@@ -6,7 +6,7 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
import { ModelSelectorBase } from "../components/shared/ModelSelector"
-import { ModeSwitcherBase, ModeSwitcher } from "../components/shared/ModeSwitcher"
+import { ModeSwitcherBase } from "../components/shared/ModeSwitcher"
const agents = [
{ name: "code", description: "Write, edit and review code", mode: "primary" as const },
@@ -62,19 +62,6 @@ export const ModelSelectorAllowClear: Story = {
// ModeSwitcher
// ---------------------------------------------------------------------------
-export const ModeSwitcherSingle: Story = {
- name: "ModeSwitcherBase — single agent (hidden)",
- render: () => (
-
- {}}
- />
-
- ),
-}
-
export const ModeSwitcherMultiple: Story = {
name: "ModeSwitcherBase — multiple agents",
render: () => (
@@ -92,12 +79,3 @@ export const ModeSwitcherAskSelected: Story = {
),
}
-
-export const ModeSwitcherFromSession: Story = {
- name: "ModeSwitcher — wired to session context",
- render: () => (
-
-
-
- ),
-}
From 2dd576c753d2ab3447ff60946e719d7e492d4e8d Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 11:17:19 +0100
Subject: [PATCH 12/22] remove unwanted stories; fix history story width
cropping
---
.../src/stories/agent-manager.stories.tsx | 19 -------
.../webview-ui/src/stories/chat.stories.tsx | 26 ----------
.../src/stories/history.stories.tsx | 4 +-
.../webview-ui/src/stories/shared.stories.tsx | 49 +------------------
4 files changed, 3 insertions(+), 95 deletions(-)
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
index 5c211cf379e..6eff6493b14 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -107,25 +107,6 @@ export const DiffPanelWithDiffs: Story = {
// FullScreenDiffView
// ---------------------------------------------------------------------------
-export const FullScreenDiffLoading: Story = {
- name: "FullScreenDiffView — loading",
- render: () => (
-
-
- {}}
- comments={[]}
- onCommentsChange={() => {}}
- onClose={() => {}}
- />
-
-
- ),
-}
-
export const FullScreenDiffWithChanges: Story = {
name: "FullScreenDiffView — with changes",
render: () => (
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
index a9f15e705a7..2440e06f61f 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -80,32 +80,6 @@ export const ChatViewIdle: Story = {
),
}
-export const ChatViewWithQuestion: Story = {
- name: "ChatView — with QuestionDock",
- render: () => (
-
-
-
-
-
- ),
-}
-
-// ---------------------------------------------------------------------------
-// MessageList stories
-// ---------------------------------------------------------------------------
-
-export const MessageListEmpty: Story = {
- name: "MessageList — empty",
- render: () => (
-
-
-
-
-
- ),
-}
-
// ---------------------------------------------------------------------------
// QuestionDock stories
// ---------------------------------------------------------------------------
diff --git a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
index c70c030df69..b3c945e87e3 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx
@@ -138,7 +138,7 @@ const WithSessions: ParentComponent<{ sessions?: typeof mockSessions }> = (props
const meta: Meta = {
title: "History/SessionList",
- parameters: { layout: "padded" },
+ parameters: { layout: "fullscreen" },
}
export default meta
type Story = StoryObj
@@ -147,7 +147,7 @@ export const WithItems: Story = {
name: "With sessions",
render: () => (
-
+
diff --git a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
index 790e7e857fd..6f1941e6573 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx
@@ -1,19 +1,11 @@
/** @jsxImportSource solid-js */
/**
- * Stories for shared controls: ModelSelector and ModeSwitcher.
+ * Stories for shared controls: ModelSelector.
*/
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
import { ModelSelectorBase } from "../components/shared/ModelSelector"
-import { ModeSwitcherBase } from "../components/shared/ModeSwitcher"
-
-const agents = [
- { name: "code", description: "Write, edit and review code", mode: "primary" as const },
- { name: "ask", description: "Answer questions without making changes", mode: "primary" as const },
- { name: "architect", description: "Plan and design before implementation", mode: "primary" as const },
- { name: "debug", description: "Diagnose and fix issues", mode: "primary" as const },
-]
const meta: Meta = {
title: "Shared",
@@ -40,42 +32,3 @@ export const ModelSelectorNoProviders: Story = {
),
}
-
-export const ModelSelectorAllowClear: Story = {
- name: "ModelSelector — allow clear",
- render: () => (
-
-
- {}}
- placement="bottom-start"
- allowClear
- clearLabel="Use default model"
- />
-
-
- ),
-}
-
-// ---------------------------------------------------------------------------
-// ModeSwitcher
-// ---------------------------------------------------------------------------
-
-export const ModeSwitcherMultiple: Story = {
- name: "ModeSwitcherBase — multiple agents",
- render: () => (
-
- {}} />
-
- ),
-}
-
-export const ModeSwitcherAskSelected: Story = {
- name: "ModeSwitcherBase — ask mode selected",
- render: () => (
-
- {}} />
-
- ),
-}
From 738a83fd3fb27b6e5a7ce8e2ad0ddcd80fd1f14f Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 11:41:12 +0100
Subject: [PATCH 13/22] revert .mts hack: use tests/package.json type:module
instead
---
.github/workflows/visual-regression.yml | 2 +-
packages/kilo-vscode/playwright.config.ts | 3 +-
packages/kilo-vscode/tests/package.json | 1 +
.../tests/visual-regression.spec.ts | 71 +++++++++++++++++++
packages/kilo-vscode/tsconfig.test.json | 11 ---
5 files changed, 74 insertions(+), 14 deletions(-)
create mode 100644 packages/kilo-vscode/tests/package.json
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts
delete mode 100644 packages/kilo-vscode/tsconfig.test.json
diff --git a/.github/workflows/visual-regression.yml b/.github/workflows/visual-regression.yml
index e9dbfaf75fc..4d23526c065 100644
--- a/.github/workflows/visual-regression.yml
+++ b/.github/workflows/visual-regression.yml
@@ -172,7 +172,7 @@ jobs:
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- git add packages/kilo-vscode/tests/visual-regression.spec.mts-snapshots/
+ git add packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/
if git diff --cached --quiet; then
echo "No new baselines — nothing to commit."
echo "changed=false" >> "$GITHUB_OUTPUT"
diff --git a/packages/kilo-vscode/playwright.config.ts b/packages/kilo-vscode/playwright.config.ts
index bd40b8cec4e..8d2c81b8341 100644
--- a/packages/kilo-vscode/playwright.config.ts
+++ b/packages/kilo-vscode/playwright.config.ts
@@ -2,8 +2,7 @@ import { defineConfig, devices } from "@playwright/test"
export default defineConfig({
testDir: "./tests",
- testMatch: "**/*.spec.{ts,mts}",
- tsconfig: "./tsconfig.test.json",
+ testMatch: "**/*.spec.ts",
fullyParallel: true,
forbidOnly: !!process.env["CI"],
retries: process.env["CI"] ? 1 : 0,
diff --git a/packages/kilo-vscode/tests/package.json b/packages/kilo-vscode/tests/package.json
new file mode 100644
index 00000000000..5ffd9800b97
--- /dev/null
+++ b/packages/kilo-vscode/tests/package.json
@@ -0,0 +1 @@
+{ "type": "module" }
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.ts
new file mode 100644
index 00000000000..45bde6e0e69
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts
@@ -0,0 +1,71 @@
+import { test, expect, type Page } from "@playwright/test"
+import { platform } from "node:os"
+
+const IS_DARWIN = platform() === "darwin"
+
+// Screenshot baselines are captured on Linux CI — font rendering and anti-aliasing
+// differ on macOS, which causes false-positive diffs. Skip the entire suite there.
+if (IS_DARWIN) {
+ console.warn("Visual regression tests must be run on CI, skipping on local macOS.")
+ test.skip()
+}
+
+type Story = {
+ id: string
+ title: string
+ name: string
+}
+
+type StoriesIndex = {
+ stories?: Record
+ entries?: Record
+}
+
+const STORYBOOK_URL = "http://localhost:6007"
+
+// Fetched once per worker process — cheap HTTP call to the already-running Storybook
+async function fetchStories(): Promise {
+ const res = await fetch(`${STORYBOOK_URL}/index.json`).catch(() => fetch(`${STORYBOOK_URL}/stories.json`))
+ if (!res.ok) throw new Error(`Storybook index fetch failed: ${res.status} ${res.statusText}`)
+ const data = (await res.json()) as StoriesIndex
+ const map = data.entries ?? data.stories ?? {}
+ return Object.values(map).filter((s) => s.id && !s.id.endsWith("--docs"))
+}
+
+async function disableAnimations(page: Page) {
+ await page.addStyleTag({
+ content: `
+ *, *::before, *::after {
+ animation-duration: 0s !important;
+ animation-delay: 0s !important;
+ transition-duration: 0s !important;
+ transition-delay: 0s !important;
+ }
+ `,
+ })
+}
+
+// Stories to skip from visual regression (add IDs here if needed)
+const SKIP = new Set([])
+
+// Generate one test() per story so Playwright's scheduler can distribute
+// them freely across workers — no manual sharding needed.
+// Skip fetching stories on macOS since test.skip() above already marks the file skipped.
+const stories = IS_DARWIN ? [] : (await fetchStories()).filter((s) => !SKIP.has(s.id))
+
+for (const story of stories) {
+ test(`${story.title} / ${story.name}`, async ({ page }) => {
+ await page.goto(
+ `/iframe.html?id=${story.id}&viewMode=story&globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern`,
+ { waitUntil: "load" },
+ )
+ await disableAnimations(page)
+ await page.waitForSelector("#storybook-root *", { state: "attached" })
+
+ // Screenshot just the story content, not the full viewport canvas.
+ // Use [component, variant] path so snapshots are grouped per component dir.
+ const [component, variant] = story.id.split("--")
+ const root = page.locator("#storybook-root")
+ await expect(root).toHaveScreenshot([component!, `${variant}.png`])
+ })
+}
diff --git a/packages/kilo-vscode/tsconfig.test.json b/packages/kilo-vscode/tsconfig.test.json
deleted file mode 100644
index bc928dac9a1..00000000000
--- a/packages/kilo-vscode/tsconfig.test.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "compilerOptions": {
- "module": "ESNext",
- "moduleResolution": "bundler",
- "target": "ESNext",
- "lib": ["ESNext"],
- "strict": true,
- "skipLibCheck": true
- },
- "include": ["tests/**/*"]
-}
From 271d0ebe1d440ab14546b4121775203b185f5aba Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 11:46:30 +0100
Subject: [PATCH 14/22] ignore playwright-report/
---
packages/kilo-vscode/.gitignore | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/packages/kilo-vscode/.gitignore b/packages/kilo-vscode/.gitignore
index 99ae09f5426..656e3f294eb 100644
--- a/packages/kilo-vscode/.gitignore
+++ b/packages/kilo-vscode/.gitignore
@@ -2,4 +2,5 @@ bin/
out/
.vscode-test/
storybook-static/
-test-results/
\ No newline at end of file
+test-results/
+playwright-report/
\ No newline at end of file
From 99fe35007de983ee648b850d8c0f10d5fa622c78 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 13:34:59 +0100
Subject: [PATCH 15/22] fix prompt-input stories: remove redundant provider
wrappers now in StoryProviders
---
.../src/stories/prompt-input.stories.tsx | 30 ++++---------------
1 file changed, 6 insertions(+), 24 deletions(-)
diff --git a/packages/kilo-vscode/webview-ui/src/stories/prompt-input.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/prompt-input.stories.tsx
index f7550ad69c2..40f76ce3ded 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/prompt-input.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/prompt-input.stories.tsx
@@ -15,18 +15,8 @@ import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { type ParentComponent } from "solid-js"
import { StoryProviders, mockSessionValue } from "./StoryProviders"
import { SessionContext } from "../context/session"
-import { VSCodeProvider } from "../context/vscode"
-import { ServerProvider } from "../context/server"
-import { ConfigProvider } from "../context/config"
-import { ProviderProvider } from "../context/provider"
import { PromptInput } from "../components/chat/PromptInput"
-// ---------------------------------------------------------------------------
-// Providers — wraps StoryProviders with the extra providers PromptInput needs
-// (VSCode/Server/Config/ProviderProvider are not in StoryProviders because
-// composite stories only render display components that don't need them)
-// ---------------------------------------------------------------------------
-
const agents = [
{ name: "code", description: "Write, edit and review code", mode: "primary" as const },
{ name: "ask", description: "Answer questions without making changes", mode: "primary" as const },
@@ -44,20 +34,12 @@ const PromptProviders: ParentComponent<{ variants?: boolean }> = (props) => {
}
return (
-
-
-
-
-
- {/* overflow:hidden prevents margin-collapse so top/bottom borders are captured in screenshots */}
-
- {props.children}
-
-
-
-
-
-
+
+ {/* overflow:hidden prevents margin-collapse so top/bottom borders are captured in screenshots */}
+
+ {props.children}
+
+
)
}
From a5bff3c44fbf83687bb659ab578cf3b17f70cde0 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 13:40:19 +0100
Subject: [PATCH 16/22] fix bot review issues: narrow viewport, unused import,
missing descriptions, dead comment
---
.../kilo-vscode/tests/visual-regression.spec.ts | 8 +++++---
.../src/stories/agent-manager.stories.tsx | 4 ----
.../webview-ui/src/stories/chat.stories.tsx | 15 ++++++++++++---
3 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.ts
index 45bde6e0e69..efe40e4e2dc 100644
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts
@@ -55,6 +55,10 @@ const stories = IS_DARWIN ? [] : (await fetchStories()).filter((s) => !SKIP.has(
for (const story of stories) {
test(`${story.title} / ${story.name}`, async ({ page }) => {
+ // Narrow stories (IDs ending in "-200") use a 200px viewport
+ const narrow = story.id.endsWith("-200")
+ await page.setViewportSize({ width: narrow ? 200 : 420, height: 720 })
+
await page.goto(
`/iframe.html?id=${story.id}&viewMode=story&globals=colorScheme:dark;theme:kilo-vscode;vscodeTheme:dark-modern`,
{ waitUntil: "load" },
@@ -62,10 +66,8 @@ for (const story of stories) {
await disableAnimations(page)
await page.waitForSelector("#storybook-root *", { state: "attached" })
- // Screenshot just the story content, not the full viewport canvas.
- // Use [component, variant] path so snapshots are grouped per component dir.
const [component, variant] = story.id.split("--")
const root = page.locator("#storybook-root")
- await expect(root).toHaveScreenshot([component!, `${variant}.png`])
+ await expect(root).toHaveScreenshot([component!, `${variant!}.png`])
})
}
diff --git a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
index 6eff6493b14..6d2bff7da1d 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx
@@ -125,7 +125,3 @@ export const FullScreenDiffWithChanges: Story = {
),
}
-
-// ---------------------------------------------------------------------------
-// AgentManagerApp
-// ---------------------------------------------------------------------------
diff --git a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
index 2440e06f61f..c9ac76a851a 100644
--- a/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
+++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx
@@ -10,7 +10,6 @@
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
import { ChatView } from "../components/chat/ChatView"
-import { MessageList } from "../components/chat/MessageList"
import { QuestionDock } from "../components/chat/QuestionDock"
import type { QuestionRequest } from "../types/messages"
@@ -44,11 +43,21 @@ const multiQuestion: QuestionRequest = {
questions: [
{
question: "Which testing framework?",
- options: [{ label: "Vitest" }, { label: "Jest" }, { label: "Bun test" }],
+ header: "Step 1 of 2",
+ options: [
+ { label: "Vitest", description: "Fast, Vite-native" },
+ { label: "Jest", description: "Widely adopted" },
+ { label: "Bun test", description: "Built-in, zero config" },
+ ],
},
{
question: "Should I include coverage reporting?",
- options: [{ label: "Yes, Istanbul" }, { label: "Yes, V8" }, { label: "No" }],
+ header: "Step 2 of 2",
+ options: [
+ { label: "Yes, Istanbul", description: "Instrumentation-based" },
+ { label: "Yes, V8", description: "Native V8 coverage" },
+ { label: "No", description: "Skip coverage" },
+ ],
},
],
tool: { messageID: "asst-msg-001", callID: "call-question-002" },
From 6a55ee84cf3019f4c25a4a9c9bbd0bf1ccb9f9b8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Wed, 4 Mar 2026 12:46:09 +0000
Subject: [PATCH 17/22] chore: update kilo-vscode visual regression baselines
---
.../agentmanager/diff-panel-with-diffs-chromium-linux.png | 3 +++
.../agentmanager/file-tree-empty-chromium-linux.png | 3 +++
.../agentmanager/file-tree-with-changes-chromium-linux.png | 3 +++
.../full-screen-diff-with-changes-chromium-linux.png | 3 +++
.../chat/chat-view-idle-chromium-linux.png | 3 +++
.../chat/question-dock-multi-chromium-linux.png | 3 +++
.../chat/question-dock-single-chromium-linux.png | 3 +++
.../history-sessionlist/with-items-chromium-linux.png | 3 +++
.../settings/providers-configure-chromium-linux.png | 3 +++
.../settings/settings-panel-chromium-linux.png | 3 +++
.../shared/model-selector-no-providers-chromium-linux.png | 3 +++
11 files changed, 33 insertions(+)
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-empty-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-with-changes-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/full-screen-diff-with-changes-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/providers-configure-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
new file mode 100644
index 00000000000..343447558f1
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/diff-panel-with-diffs-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:6d8550b95fec011a61707b544af9a18af85dc51f91a3e93d99a4d0e91bbd86b0
+size 20568
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-empty-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-empty-chromium-linux.png
new file mode 100644
index 00000000000..de9e378e1f5
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-empty-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:158c955adc62f761e53d7b40d4ae5caf509ceb1f24cb05f499d3ed633b762366
+size 4447
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-with-changes-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-with-changes-chromium-linux.png
new file mode 100644
index 00000000000..0670e2c5da0
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/file-tree-with-changes-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a442e795a4bbf42cffde54230853b31e8b62338932d21cda82714726c2626619
+size 14296
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/full-screen-diff-with-changes-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/full-screen-diff-with-changes-chromium-linux.png
new file mode 100644
index 00000000000..834baace8b3
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/agentmanager/full-screen-diff-with-changes-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c412052053c3591f14ad30ceb35f34bcbc41b979fc27a07445f43b1f89b55695
+size 23389
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
new file mode 100644
index 00000000000..f89f3b97e60
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f5a07b6b6a5b32a4a46cb4572222184d0de69e1c110b0418aa44d254296d25d7
+size 15769
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
new file mode 100644
index 00000000000..d5670533140
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-multi-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f0c06ab1220743bf5ef3b0c16f40de6cf7915aa224635592aeaa584d6586a160
+size 19479
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
new file mode 100644
index 00000000000..e05f875ab51
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/question-dock-single-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:e6ae4436d14f3b4c2fb3f0ecff3c5f6b456c3b23b35103fa5dab46a24a3750b8
+size 25233
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png
new file mode 100644
index 00000000000..04fc4802d8d
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/history-sessionlist/with-items-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:79c47f310dacd31434abff158e167a7b837ac4497d1c38f31582b61f854d08fa
+size 17996
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/providers-configure-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/providers-configure-chromium-linux.png
new file mode 100644
index 00000000000..75bfd3e57c9
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/providers-configure-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:136362f99c47a4b3c850f659d9c9c69935908053591aa1fd54c241cf2ef69b6f
+size 26901
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png
new file mode 100644
index 00000000000..5c3466e5ee3
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/settings/settings-panel-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da39896ddff095f6eebb3de8215c581fd384633ea35fcea9c9e5531c6eeb36dc
+size 52821
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png
new file mode 100644
index 00000000000..c5d449f7b53
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/shared/model-selector-no-providers-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da2d65474a3e1e71a1a3c3c7ee1fb2213dfe9897dbe5aa600e8d5f92826c6d88
+size 1074
From 4993804f4927d8bf217432121e6371097fd1d2bf Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 13:47:27 +0100
Subject: [PATCH 18/22] ci: trigger visual regression
From 0324da8bb974db3bff63bba73677d273e8915a40 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 13:49:19 +0100
Subject: [PATCH 19/22] fix logo not rendering in Storybook: serve icons as
static dir and set ICONS_BASE_URI
---
packages/kilo-vscode/.storybook/main.ts | 1 +
packages/kilo-vscode/.storybook/preview.tsx | 3 +++
2 files changed, 4 insertions(+)
diff --git a/packages/kilo-vscode/.storybook/main.ts b/packages/kilo-vscode/.storybook/main.ts
index 98be6d87566..d1ef464f284 100644
--- a/packages/kilo-vscode/.storybook/main.ts
+++ b/packages/kilo-vscode/.storybook/main.ts
@@ -6,6 +6,7 @@ const config: StorybookConfig = {
framework: "storybook-solidjs-vite",
stories: ["../webview-ui/src/stories/**/*.stories.@(ts|tsx)"],
addons: ["@storybook/addon-docs"],
+ staticDirs: [{ from: "../assets/icons", to: "/icons" }],
refs: {},
viteFinal: async (config) => {
return mergeConfig(config, {
diff --git a/packages/kilo-vscode/.storybook/preview.tsx b/packages/kilo-vscode/.storybook/preview.tsx
index f69d799def4..a662254aebf 100644
--- a/packages/kilo-vscode/.storybook/preview.tsx
+++ b/packages/kilo-vscode/.storybook/preview.tsx
@@ -7,6 +7,9 @@ import "../../kilo-ui/.storybook/fonts.css"
import "@kilocode/kilo-ui/styles"
import "../webview-ui/src/styles/chat.css"
+// Make the Kilo logo available in Storybook (normally injected by the extension host)
+;(window as { ICONS_BASE_URI?: string }).ICONS_BASE_URI = "/icons"
+
const themeDecorator: DecoratorFunction = (Story, context) => {
const themeId = (context.globals["theme"] as string) ?? "kilo-vscode"
const vscodeThemeId = (context.globals["vscodeTheme"] as string) ?? "dark-modern"
From e1f2eec41269ddc9d1d59fcab122ae00720d5e79 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 15:13:33 +0100
Subject: [PATCH 20/22] delete chat-view-idle baseline to force regeneration
with logo fix
---
.../chat/chat-view-idle-chromium-linux.png | 3 ---
1 file changed, 3 deletions(-)
delete mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
deleted file mode 100644
index f89f3b97e60..00000000000
--- a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:f5a07b6b6a5b32a4a46cb4572222184d0de69e1c110b0418aa44d254296d25d7
-size 15769
From 08c0efda36fba3253de521dc217fb9d00714d73f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
Date: Wed, 4 Mar 2026 14:14:55 +0000
Subject: [PATCH 21/22] chore: update kilo-vscode visual regression baselines
---
.../chat/chat-view-idle-chromium-linux.png | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
new file mode 100644
index 00000000000..f93a836f20b
--- /dev/null
+++ b/packages/kilo-vscode/tests/visual-regression.spec.ts-snapshots/chat/chat-view-idle-chromium-linux.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d385bd579ac5587b1f70416196f77b6465d6a69fedf7270b889eedb29d0ac73d
+size 14864
From 1ee419cd158374555e00a60fca268c5b1526a515 Mon Sep 17 00:00:00 2001
From: Mark IJbema
Date: Wed, 4 Mar 2026 15:19:26 +0100
Subject: [PATCH 22/22] ci: trigger visual regression