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 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" diff --git a/packages/kilo-vscode/playwright.config.ts b/packages/kilo-vscode/playwright.config.ts index 30680574fc8..8d2c81b8341 100644 --- a/packages/kilo-vscode/playwright.config.ts +++ b/packages/kilo-vscode/playwright.config.ts @@ -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, 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.mts b/packages/kilo-vscode/tests/visual-regression.spec.mts new file mode 100644 index 00000000000..7285adb2c80 --- /dev/null +++ b/packages/kilo-vscode/tests/visual-regression.spec.mts @@ -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 + 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 }) => { + // 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`]) + }) +} diff --git a/packages/kilo-vscode/tests/visual-regression.spec.ts b/packages/kilo-vscode/tests/visual-regression.spec.ts index 6cf917ba004..efe40e4e2dc 100644 --- a/packages/kilo-vscode/tests/visual-regression.spec.ts +++ b/packages/kilo-vscode/tests/visual-regression.spec.ts @@ -48,35 +48,26 @@ 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 + 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`]) - } - }) -}) +} 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..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 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 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..6d2bff7da1d --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/stories/agent-manager.stories.tsx @@ -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
\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 DiffPanelWithDiffs: Story = { + name: "DiffPanel — with diffs (unified)", + render: () => ( + +
+ {}} + comments={[]} + onCommentsChange={() => {}} + onClose={() => {}} + onExpand={() => {}} + /> +
+
+ ), +} + +// --------------------------------------------------------------------------- +// FullScreenDiffView +// --------------------------------------------------------------------------- + +export const FullScreenDiffWithChanges: Story = { + name: "FullScreenDiffView — with changes", + render: () => ( + +
+ {}} + comments={[]} + onCommentsChange={() => {}} + onClose={() => {}} + /> +
+
+ ), +} 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..c9ac76a851a --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/stories/chat.stories.tsx @@ -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: () => ( + +
+ +
+
+ ), +} + +// --------------------------------------------------------------------------- +// 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..b3c945e87e3 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/stories/history.stories.tsx @@ -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 = { ...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: "fullscreen" }, +} +export default meta +type Story = StoryObj + +export const WithItems: Story = { + name: "With sessions", + render: () => ( + +
+ +
+
+ ), +} 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} +
+
) } 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..8de75b4c2c5 --- /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..6f1941e6573 --- /dev/null +++ b/packages/kilo-vscode/webview-ui/src/stories/shared.stories.tsx @@ -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: () => ( + +
+ {}} + placement="bottom-start" + /> +
+
+ ), +}