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={ +
+ + +
+ } + > +
+

Choose one of the following options:

+ +
+
+ ), +} + +export const Permission: Story = { + name: "Permission kind", + render: () => ( + Permission required — write} + footer={ +
+ + + +
+ } + > +
+ 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={ -
- - +
+ Which testing framework should I use?} + footer={ +
+ + +
+ } + > +
+

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={ -
- - - +
+ Permission required — write} + footer={ +
+ + + +
+ } + > +
+ 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={ -
- - -
- } - > -
-

Choose one of the following options:

-
    -
  • Vitest
  • -
  • Jest
  • -
  • Playwright
  • -
  • Bun test
  • -
-
-
-
- ), -} - -export const Permission: Story = { - name: "Permission kind", - render: () => ( -
- Permission required — write} - footer={ -
- - - -
- } - > -
- 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