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
This commit is contained in:
kiloconnect[bot]
2026-03-04 13:27:58 +01:00
committed by Mark IJbema
parent 90aed7928b
commit cc3a6fd922
8 changed files with 802 additions and 25 deletions
@@ -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: () => (
<DockPrompt
kind="question"
header={<span style={{ "font-weight": "600" }}>Which testing framework should I use?</span>}
footer={
<div style={{ display: "flex", gap: "8px" }}>
<Button variant="ghost" size="small">Dismiss</Button>
<Button variant="primary" size="small">Submit</Button>
</div>
}
>
<div style={{ padding: "8px 0" }}>
<p style={{ margin: 0, "font-size": "13px" }}>Choose one of the following options:</p>
<ul style={{ margin: "8px 0 0", padding: "0 0 0 16px", "font-size": "13px" }}>
<li>Vitest</li>
<li>Jest</li>
<li>Playwright</li>
<li>Bun test</li>
</ul>
</div>
</DockPrompt>
),
}
export const Permission: Story = {
name: "Permission kind",
render: () => (
<DockPrompt
kind="permission"
header={<span style={{ "font-weight": "600" }}>Permission required write</span>}
footer={
<div style={{ display: "flex", gap: "8px" }}>
<Button variant="ghost" size="small">Deny</Button>
<Button variant="secondary" size="small">Always Allow</Button>
<Button variant="primary" size="small">Allow Once</Button>
</div>
}
>
<div style={{ padding: "8px 0", "font-size": "13px" }}>
<code>src/main.tsx</code>
<br />
<code>src/utils.ts</code>
</div>
</DockPrompt>
),
}
@@ -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 style={{ padding: "12px", border: "1px solid var(--border-base)", "border-radius": "8px" }}>
<p style={{ margin: 0, "font-size": "13px" }}>DockShell main container body for the dock surface.</p>
</DockShell>
),
}
export const Tray: Story = {
name: "DockTray",
render: () => (
<DockTray style={{ padding: "8px 12px", background: "var(--background-surface)", "border-radius": "0 0 8px 8px" }}>
<p style={{ margin: 0, "font-size": "13px" }}>DockTray footer tray area (attach=none).</p>
</DockTray>
),
}
export const TrayAttachTop: Story = {
name: "DockTray attach=top",
render: () => (
<DockTray attach="top" style={{ padding: "8px 12px", background: "var(--background-surface)", "border-radius": "8px 8px 0 0" }}>
<p style={{ margin: 0, "font-size": "13px" }}>DockTray header tray area (attach=top).</p>
</DockTray>
),
}
export const ShellWithTray: Story = {
name: "DockShell + DockTray",
render: () => (
<div style={{ border: "1px solid var(--border-base)", "border-radius": "8px", overflow: "hidden" }}>
<DockShell style={{ padding: "12px" }}>
<p style={{ margin: 0, "font-size": "13px" }}>Body content goes here inside DockShell.</p>
</DockShell>
<DockTray style={{ padding: "8px 12px", display: "flex", gap: "8px", "justify-content": "flex-end" }}>
<span style={{ "font-size": "12px", color: "var(--text-dimmed)" }}>Footer actions slot</span>
</DockTray>
</div>
),
}
export const ShellForm: Story = {
name: "DockShellForm",
render: () => (
<DockShellForm style={{ padding: "12px", border: "1px solid var(--border-base)", "border-radius": "8px" }}>
<p style={{ margin: 0, "font-size": "13px" }}>DockShellForm shell variant using a form element.</p>
</DockShellForm>
),
}
@@ -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,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 <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: "280px", 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: "280px", height: "400px" }}>
<FileTree diffs={[]} activeFile={null} onFileSelect={() => {}} />
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// DiffPanel
// ---------------------------------------------------------------------------
export const DiffPanelLoading: Story = {
name: "DiffPanel — loading",
render: () => (
<StoryProviders>
<div style={{ width: "600px", height: "500px", display: "flex", "flex-direction": "column" }}>
<DiffPanel
diffs={[]}
loading
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
/>
</div>
</StoryProviders>
),
}
export const DiffPanelWithDiffs: Story = {
name: "DiffPanel — with diffs (unified)",
render: () => (
<StoryProviders>
<div style={{ width: "600px", height: "500px", display: "flex", "flex-direction": "column" }}>
<DiffPanel
diffs={mockDiffs}
loading={false}
diffStyle="unified"
onDiffStyleChange={() => {}}
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
onExpand={() => {}}
/>
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// FullScreenDiffView
// ---------------------------------------------------------------------------
export const FullScreenDiffLoading: Story = {
name: "FullScreenDiffView — loading",
render: () => (
<StoryProviders>
<div style={{ width: "1024px", height: "700px", display: "flex" }}>
<FullScreenDiffView
diffs={[]}
loading
diffStyle="unified"
onDiffStyleChange={() => {}}
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
/>
</div>
</StoryProviders>
),
}
export const FullScreenDiffWithChanges: Story = {
name: "FullScreenDiffView — with changes",
render: () => (
<StoryProviders>
<div style={{ width: "1024px", height: "700px", display: "flex" }}>
<FullScreenDiffView
diffs={mockDiffs}
loading={false}
diffStyle="unified"
onDiffStyleChange={() => {}}
comments={[]}
onCommentsChange={() => {}}
onClose={() => {}}
/>
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// 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).
<div style={{ width: "900px", height: "600px" }}>
<AgentManagerApp />
</div>
),
}
@@ -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: () => (
<StoryProviders sessionID={SESSION_ID} status="idle">
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
<ChatView />
</div>
</StoryProviders>
),
}
export const ChatViewReadonly: Story = {
name: "ChatView — readonly",
render: () => (
<StoryProviders sessionID={SESSION_ID} status="idle">
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
<ChatView readonly />
</div>
</StoryProviders>
),
}
export const ChatViewWithQuestion: Story = {
name: "ChatView — with QuestionDock",
render: () => (
<StoryProviders sessionID={SESSION_ID} questions={[singleQuestion]}>
<div style={{ width: "400px", height: "600px", display: "flex", "flex-direction": "column" }}>
<ChatView />
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// MessageList stories
// ---------------------------------------------------------------------------
export const MessageListEmpty: Story = {
name: "MessageList — empty",
render: () => (
<StoryProviders sessionID={SESSION_ID}>
<div style={{ width: "400px", height: "500px" }}>
<MessageList />
</div>
</StoryProviders>
),
}
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 (
<StoryProviders sessionID={SESSION_ID}>
<div style={{ width: "400px", height: "500px" }}>
<MessageList />
</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,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<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: "padded" },
}
export default meta
type Story = StoryObj
export const Empty: Story = {
name: "Empty list",
render: () => (
<WithSessions sessions={[]}>
<div style={{ width: "320px", height: "500px" }}>
<SessionList onSelectSession={noop} />
</div>
</WithSessions>
),
}
export const WithItems: Story = {
name: "With sessions",
render: () => (
<WithSessions sessions={mockSessions as any}>
<div style={{ width: "320px", height: "500px" }}>
<SessionList onSelectSession={noop} />
</div>
</WithSessions>
),
}
@@ -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: "560px", 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: "560px", "max-height": "700px", overflow: "auto" }}>
<ProvidersTab />
</div>
</StoryProviders>
),
}
@@ -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: () => (
<StoryProviders>
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
<ModelSelectorBase
value={{ providerID: "kilo", modelID: "kilo/auto" }}
onSelect={() => {}}
placement="bottom-start"
/>
</div>
</StoryProviders>
),
}
export const ModelSelectorAllowClear: Story = {
name: "ModelSelector — allow clear",
render: () => (
<StoryProviders>
<div style={{ display: "flex", "align-items": "center", gap: "8px" }}>
<ModelSelectorBase
value={null}
onSelect={() => {}}
placement="bottom-start"
allowClear
clearLabel="Use default model"
/>
</div>
</StoryProviders>
),
}
// ---------------------------------------------------------------------------
// ModeSwitcher
// ---------------------------------------------------------------------------
export const ModeSwitcherSingle: Story = {
name: "ModeSwitcherBase — single agent (hidden)",
render: () => (
<StoryProviders>
<ModeSwitcherBase
agents={[{ name: "code", description: "Code mode", mode: "primary" as const }]}
value="code"
onSelect={() => {}}
/>
</StoryProviders>
),
}
export const ModeSwitcherMultiple: Story = {
name: "ModeSwitcherBase — multiple agents",
render: () => (
<StoryProviders>
<ModeSwitcherBase agents={agents} value="code" onSelect={() => {}} />
</StoryProviders>
),
}
export const ModeSwitcherAskSelected: Story = {
name: "ModeSwitcherBase — ask mode selected",
render: () => (
<StoryProviders>
<ModeSwitcherBase agents={agents} value="ask" onSelect={() => {}} />
</StoryProviders>
),
}
export const ModeSwitcherFromSession: Story = {
name: "ModeSwitcher — wired to session context",
render: () => (
<StoryProviders>
<ModeSwitcher />
</StoryProviders>
),
}