Merge pull request #6588 from Kilo-Org/screenshot-test-coverage-high-priority

test: add Storybook stories and visual regression baselines for high-priority components
This commit is contained in:
Mark IJbema
2026-03-04 16:20:45 +01:00
committed by GitHub
25 changed files with 651 additions and 82 deletions
+2 -1
View File
@@ -2,4 +2,5 @@ bin/
out/
.vscode-test/
storybook-static/
test-results/
test-results/
playwright-report/
+1
View File
@@ -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, {
@@ -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<SolidRenderer> = (Story, context) => {
const themeId = (context.globals["theme"] as string) ?? "kilo-vscode"
const vscodeThemeId = (context.globals["vscodeTheme"] as string) ?? "dark-modern"
+1 -2
View File
@@ -24,8 +24,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,
+1
View File
@@ -0,0 +1 @@
{ "type": "module" }
@@ -0,0 +1,74 @@
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<string, Story>
entries?: Record<string, Story>
}
const STORYBOOK_URL = "http://localhost:6007"
// Fetched once per worker process — cheap HTTP call to the already-running Storybook
async function fetchStories(): Promise<Story[]> {
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<string>([])
// 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 }) => {
// 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`])
})
}
@@ -48,35 +48,26 @@ async function disableAnimations(page: Page) {
// Stories to skip from visual regression (add IDs here if needed)
const SKIP = new Set<string>([])
// 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
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`])
}
})
})
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6d8550b95fec011a61707b544af9a18af85dc51f91a3e93d99a4d0e91bbd86b0
size 20568
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:158c955adc62f761e53d7b40d4ae5caf509ceb1f24cb05f499d3ed633b762366
size 4447
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a442e795a4bbf42cffde54230853b31e8b62338932d21cda82714726c2626619
size 14296
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c412052053c3591f14ad30ceb35f34bcbc41b979fc27a07445f43b1f89b55695
size 23389
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d385bd579ac5587b1f70416196f77b6465d6a69fedf7270b889eedb29d0ac73d
size 14864
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f0c06ab1220743bf5ef3b0c16f40de6cf7915aa224635592aeaa584d6586a160
size 19479
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6ae4436d14f3b4c2fb3f0ecff3c5f6b456c3b23b35103fa5dab46a24a3750b8
size 25233
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:79c47f310dacd31434abff158e167a7b837ac4497d1c38f31582b61f854d08fa
size 17996
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:136362f99c47a4b3c850f659d9c9c69935908053591aa1fd54c241cf2ef69b6f
size 26901
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da39896ddff095f6eebb3de8215c581fd384633ea35fcea9c9e5531c6eeb36dc
size 52821
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:da2d65474a3e1e71a1a3c3c7ee1fb2213dfe9897dbe5aa600e8d5f92826c6d88
size 1074
@@ -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<StoryProvidersProps> = (props) => {
const [locale] = createSignal<"en">("en")
return (
<DialogProvider>
<LanguageContext.Provider
value={{
locale,
setLocale: noop,
userOverride: () => "" as any,
t,
}}
>
<I18nProvider value={{ locale: () => "en", t }}>
<SessionContext.Provider value={session as any}>
<DataProvider data={data()} directory="/project/">
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<MarkedProvider>
{props.noPadding ? props.children : <div style={{ padding: "12px" }}>{props.children}</div>}
</MarkedProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</DataProvider>
</SessionContext.Provider>
</I18nProvider>
</LanguageContext.Provider>
</DialogProvider>
<VSCodeProvider>
<ServerProvider>
<ConfigProvider>
<ProviderProvider>
<DialogProvider>
<LanguageContext.Provider
value={{
locale,
setLocale: noop,
userOverride: () => "" as any,
t,
}}
>
<I18nProvider value={{ locale: () => "en", t }}>
<SessionContext.Provider value={session as any}>
<DataProvider data={data()} directory="/project/">
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<MarkedProvider>
{props.noPadding ? props.children : <div style={{ padding: "12px" }}>{props.children}</div>}
</MarkedProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</DataProvider>
</SessionContext.Provider>
</I18nProvider>
</LanguageContext.Provider>
</DialogProvider>
</ProviderProvider>
</ConfigProvider>
</ServerProvider>
</VSCodeProvider>
)
}
@@ -0,0 +1,127 @@
/** @jsxImportSource solid-js */
/**
* Stories for Agent Manager components:
* FileTree, DiffPanel, FullScreenDiffView
*/
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
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 <div class="chat-view" />\n}\n`,
after: `import { Component, createSignal } from "solid-js"\n\nexport const ChatView: Component = () => {\n const [open, setOpen] = createSignal(false)\n return <div class="chat-view" />\n}\n`,
},
{
file: "src/components/chat/MessageList.tsx",
status: "modified",
additions: 3,
deletions: 1,
before: `export const MessageList = () => <div class="message-list" />\n`,
after: `export const MessageList = () => (\n <div class="message-list" role="log" aria-live="polite" />\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: () => (
<StoryProviders>
<div style={{ width: "420px", height: "400px", overflow: "auto" }}>
<FileTree diffs={mockDiffs} activeFile="src/components/chat/ChatView.tsx" onFileSelect={() => {}} showSummary />
</div>
</StoryProviders>
),
}
export const FileTreeEmpty: Story = {
name: "FileTree — no changes",
render: () => (
<StoryProviders>
<div style={{ width: "420px", height: "400px" }}>
<FileTree diffs={[]} activeFile={null} onFileSelect={() => {}} />
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// DiffPanel
// ---------------------------------------------------------------------------
export const DiffPanelWithDiffs: Story = {
name: "DiffPanel — with diffs (unified)",
render: () => (
<StoryProviders>
<div style={{ width: "420px", height: "500px", display: "flex", "flex-direction": "column" }}>
<DiffPanel
diffs={mockDiffs}
loading={false}
diffStyle="unified"
onDiffStyleChange={() => {}}
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
onExpand={() => {}}
/>
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// FullScreenDiffView
// ---------------------------------------------------------------------------
export const FullScreenDiffWithChanges: Story = {
name: "FullScreenDiffView — with changes",
render: () => (
<StoryProviders>
<div style={{ width: "420px", height: "700px", display: "flex" }}>
<FullScreenDiffView
diffs={mockDiffs}
loading={false}
diffStyle="unified"
onDiffStyleChange={() => {}}
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
/>
</div>
</StoryProviders>
),
}
@@ -0,0 +1,116 @@
/** @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 { 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?",
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?",
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" },
}
// ---------------------------------------------------------------------------
// 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: () => (
<StoryProviders sessionID={SESSION_ID} status="idle">
<div style={{ width: "420px", height: "600px", display: "flex", "flex-direction": "column" }}>
<ChatView />
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// QuestionDock stories
// ---------------------------------------------------------------------------
export const QuestionDockSingle: Story = {
name: "QuestionDock — single question",
render: () => (
<StoryProviders sessionID={SESSION_ID} questions={[singleQuestion]}>
<div style={{ width: "420px" }}>
<QuestionDock request={singleQuestion} />
</div>
</StoryProviders>
),
}
export const QuestionDockMulti: Story = {
name: "QuestionDock — multi-question wizard",
render: () => (
<StoryProviders sessionID={SESSION_ID} questions={[multiQuestion]}>
<div style={{ width: "420px" }}>
<QuestionDock request={multiQuestion} />
</div>
</StoryProviders>
),
}
@@ -0,0 +1,155 @@
/** @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<string, string> = { ...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<string>(),
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 (
<VSCodeProvider>
<ServerProvider>
<ConfigProvider>
<ProviderProvider>
<DialogProvider>
<LanguageContext.Provider value={{ locale, setLocale: noop, userOverride: () => "" as any, t }}>
<I18nProvider value={{ locale: () => "en", t }}>
<SessionContext.Provider value={session as any}>
<DataProvider
data={{
session: sessions,
session_status: {},
session_diff: {},
message: {},
part: {},
permission: {},
question: {},
provider: { all: [], connected: false, default: {} },
}}
directory="/project/"
>
<DiffComponentProvider component={Diff}>
<CodeComponentProvider component={Code}>
<MarkedProvider>
<div style={{ padding: "12px" }}>{props.children}</div>
</MarkedProvider>
</CodeComponentProvider>
</DiffComponentProvider>
</DataProvider>
</SessionContext.Provider>
</I18nProvider>
</LanguageContext.Provider>
</DialogProvider>
</ProviderProvider>
</ConfigProvider>
</ServerProvider>
</VSCodeProvider>
)
}
const meta: Meta = {
title: "History/SessionList",
parameters: { layout: "fullscreen" },
}
export default meta
type Story = StoryObj
export const WithItems: Story = {
name: "With sessions",
render: () => (
<WithSessions sessions={mockSessions as any}>
<div style={{ height: "500px" }}>
<SessionList onSelectSession={noop} />
</div>
</WithSessions>
),
}
@@ -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 (
<VSCodeProvider>
<ServerProvider>
<ConfigProvider>
<ProviderProvider>
<StoryProviders noPadding>
{/* overflow:hidden prevents margin-collapse so top/bottom borders are captured in screenshots */}
<div style={{ overflow: "hidden" }}>
<SessionContext.Provider value={session as any}>{props.children}</SessionContext.Provider>
</div>
</StoryProviders>
</ProviderProvider>
</ConfigProvider>
</ServerProvider>
</VSCodeProvider>
<StoryProviders noPadding>
{/* overflow:hidden prevents margin-collapse so top/bottom borders are captured in screenshots */}
<div style={{ overflow: "hidden" }}>
<SessionContext.Provider value={session as any}>{props.children}</SessionContext.Provider>
</div>
</StoryProviders>
)
}
@@ -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: () => (
<StoryProviders>
<div style={{ width: "420px", height: "700px", display: "flex", "flex-direction": "column" }}>
<Settings onBack={() => {}} />
</div>
</StoryProviders>
),
}
export const ProvidersConfigure: Story = {
name: "ProvidersTab — no providers configured",
render: () => (
<StoryProviders>
<div style={{ width: "420px", "max-height": "700px", overflow: "auto" }}>
<ProvidersTab />
</div>
</StoryProviders>
),
}
@@ -0,0 +1,34 @@
/** @jsxImportSource solid-js */
/**
* Stories for shared controls: ModelSelector.
*/
import type { Meta, StoryObj } from "storybook-solidjs-vite"
import { StoryProviders } from "./StoryProviders"
import { ModelSelectorBase } from "../components/shared/ModelSelector"
const meta: Meta = {
title: "Shared",
parameters: { layout: "padded" },
}
export default meta
type Story = StoryObj
// ---------------------------------------------------------------------------
// ModelSelector
// ---------------------------------------------------------------------------
export const ModelSelectorNoProviders: Story = {
name: "ModelSelector — no providers",
render: () => (
<StoryProviders>
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
<ModelSelectorBase
value={{ providerID: "kilo", modelID: "kilo/auto" }}
onSelect={() => {}}
placement="bottom-start"
/>
</div>
</StoryProviders>
),
}