Skip to content

Commit 164f60b

Browse files
authored
feat: Implement storybook (#648)
We have a lot of UI which is not easy to test because most of our interface is tied to things like agent/git state. With storybook we can easily inspect those.
1 parent 8211a91 commit 164f60b

File tree

8 files changed

+874
-13
lines changed

8 files changed

+874
-13
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,6 @@ AGENTS.md
4646
playwright-results/
4747
playwright-report/
4848
test-results/
49+
50+
*storybook.log
51+
storybook-static

apps/twig/.storybook/main.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import path, { dirname } from "node:path";
2+
import { fileURLToPath } from "node:url";
3+
import type { StorybookConfig } from "@storybook/react-vite";
4+
import react from "@vitejs/plugin-react";
5+
import { mergeConfig } from "vite";
6+
7+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
8+
9+
function getAbsolutePath(value: string) {
10+
return dirname(fileURLToPath(import.meta.resolve(`${value}/package.json`)));
11+
}
12+
13+
const config: StorybookConfig = {
14+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
15+
addons: [
16+
getAbsolutePath("@storybook/addon-a11y"),
17+
getAbsolutePath("@storybook/addon-docs"),
18+
],
19+
framework: getAbsolutePath("@storybook/react-vite"),
20+
async viteFinal(config) {
21+
return mergeConfig(config, {
22+
plugins: [react()],
23+
resolve: {
24+
alias: {
25+
"@": path.resolve(__dirname, "../src"),
26+
"@main": path.resolve(__dirname, "../src/main"),
27+
"@renderer": path.resolve(__dirname, "../src/renderer"),
28+
"@shared": path.resolve(__dirname, "../src/shared"),
29+
"@api": path.resolve(__dirname, "../src/api"),
30+
"@features": path.resolve(__dirname, "../src/renderer/features"),
31+
"@components": path.resolve(__dirname, "../src/renderer/components"),
32+
"@stores": path.resolve(__dirname, "../src/renderer/stores"),
33+
"@hooks": path.resolve(__dirname, "../src/renderer/hooks"),
34+
"@utils": path.resolve(__dirname, "../src/renderer/utils"),
35+
},
36+
},
37+
});
38+
},
39+
};
40+
41+
export default config;

apps/twig/.storybook/preview.tsx

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Theme } from "@radix-ui/themes";
2+
import "@radix-ui/themes/styles.css";
3+
import type { Preview } from "@storybook/react-vite";
4+
import "../src/renderer/styles/globals.css";
5+
6+
const preview: Preview = {
7+
parameters: {
8+
controls: {
9+
matchers: {
10+
color: /(background|color)$/i,
11+
date: /Date$/i,
12+
},
13+
},
14+
backgrounds: {
15+
default: "dark",
16+
values: [
17+
{ name: "dark", value: "#111113" },
18+
{ name: "light", value: "#ffffff" },
19+
],
20+
},
21+
},
22+
decorators: [
23+
(Story, context) => {
24+
const isDark = context.globals.theme !== "light";
25+
return (
26+
<Theme
27+
appearance={isDark ? "dark" : "light"}
28+
accentColor={isDark ? "orange" : "yellow"}
29+
grayColor="slate"
30+
panelBackground="solid"
31+
radius="none"
32+
scaling="105%"
33+
>
34+
<Story />
35+
</Theme>
36+
);
37+
},
38+
],
39+
globalTypes: {
40+
theme: {
41+
description: "Theme",
42+
defaultValue: "dark",
43+
toolbar: {
44+
title: "Theme",
45+
icon: "circlehollow",
46+
items: ["dark", "light"],
47+
dynamicTitle: true,
48+
},
49+
},
50+
},
51+
};
52+
53+
export default preview;

apps/twig/package.json

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
"test": "vitest run",
2626
"test:e2e": "playwright test --config=tests/e2e/playwright.config.ts",
2727
"test:e2e:headed": "playwright test --config=tests/e2e/playwright.config.ts --headed",
28-
"postinstall": "cd ../.. && npx @electron/rebuild -f -m node_modules/node-pty || true && bash apps/twig/scripts/patch-electron-name.sh"
28+
"postinstall": "cd ../.. && npx @electron/rebuild -f -m node_modules/node-pty || true && bash apps/twig/scripts/patch-electron-name.sh",
29+
"storybook": "storybook dev -p 6006",
30+
"build-storybook": "storybook build"
2931
},
3032
"keywords": [
3133
"posthog",
@@ -69,7 +71,11 @@
6971
"vite": "^6.0.7",
7072
"vite-tsconfig-paths": "^5.1.4",
7173
"vitest": "^4.0.10",
72-
"yaml": "^2.8.1"
74+
"yaml": "^2.8.1",
75+
"storybook": "10.2.0",
76+
"@storybook/react-vite": "10.2.0",
77+
"@storybook/addon-a11y": "10.2.0",
78+
"@storybook/addon-docs": "10.2.0"
7379
},
7480
"dependencies": {
7581
"@codemirror/lang-angular": "^0.1.4",
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import type { Meta, StoryObj } from "@storybook/react-vite";
2+
import {
3+
InlinePermissionSelector,
4+
type PermissionOption,
5+
} from "./InlinePermissionSelector";
6+
7+
const meta: Meta<typeof InlinePermissionSelector> = {
8+
title: "Sessions/InlinePermissionSelector",
9+
component: InlinePermissionSelector,
10+
parameters: {
11+
layout: "padded",
12+
},
13+
argTypes: {
14+
onSelect: { action: "selected" },
15+
onCancel: { action: "cancelled" },
16+
},
17+
};
18+
19+
export default meta;
20+
type Story = StoryObj<typeof InlinePermissionSelector>;
21+
22+
const defaultOptions: PermissionOption[] = [
23+
{
24+
optionId: "allow_always",
25+
name: "Accept All",
26+
description: "Allow this action for all similar requests",
27+
kind: "allow_always",
28+
},
29+
{
30+
optionId: "allow_once",
31+
name: "Accept",
32+
description: "Allow this action once",
33+
kind: "allow_once",
34+
},
35+
{
36+
optionId: "reject_once",
37+
name: "Reject",
38+
description: "Reject this action",
39+
kind: "reject_once",
40+
},
41+
];
42+
43+
export const Default: Story = {
44+
args: {
45+
title: "Allow running: npm install lodash",
46+
options: defaultOptions,
47+
},
48+
};
49+
50+
export const BashCommand: Story = {
51+
args: {
52+
title: "Allow running: rm -rf node_modules",
53+
options: defaultOptions,
54+
},
55+
};
56+
57+
export const FileEdit: Story = {
58+
args: {
59+
title: "Allow editing: src/components/Button.tsx",
60+
options: [
61+
{
62+
optionId: "allow_always",
63+
name: "Accept All",
64+
description: "Allow edits to this file",
65+
kind: "allow_always",
66+
},
67+
{
68+
optionId: "allow_once",
69+
name: "Accept",
70+
description: "Allow this edit once",
71+
kind: "allow_once",
72+
},
73+
{
74+
optionId: "reject_once",
75+
name: "Reject",
76+
description: "Reject this edit",
77+
kind: "reject_once",
78+
},
79+
],
80+
},
81+
};
82+
83+
export const Disabled: Story = {
84+
args: {
85+
title: "Allow running: npm install lodash",
86+
options: defaultOptions,
87+
disabled: true,
88+
},
89+
};
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import type { Plan } from "@features/sessions/types";
2+
import type { Meta, StoryObj } from "@storybook/react-vite";
3+
import { PlanStatusBar } from "./PlanStatusBar";
4+
5+
const meta: Meta<typeof PlanStatusBar> = {
6+
title: "Sessions/PlanStatusBar",
7+
component: PlanStatusBar,
8+
parameters: {
9+
layout: "fullscreen",
10+
},
11+
};
12+
13+
export default meta;
14+
type Story = StoryObj<typeof PlanStatusBar>;
15+
16+
const createPlan = (
17+
entries: Array<{
18+
content: string;
19+
status: "pending" | "in_progress" | "completed" | "failed";
20+
}>,
21+
): Plan =>
22+
({
23+
sessionUpdate: "plan",
24+
entries: entries.map((e) => ({
25+
content: e.content,
26+
activeForm: e.content,
27+
status: e.status,
28+
})),
29+
}) as unknown as Plan;
30+
31+
export const InProgress: Story = {
32+
args: {
33+
plan: createPlan([
34+
{ content: "Create database schema", status: "completed" },
35+
{ content: "Implement authentication endpoints", status: "completed" },
36+
{ content: "Add middleware for protected routes", status: "in_progress" },
37+
{ content: "Write unit tests", status: "pending" },
38+
{ content: "Write integration tests", status: "pending" },
39+
]),
40+
},
41+
};
42+
43+
export const JustStarted: Story = {
44+
args: {
45+
plan: createPlan([
46+
{ content: "Analyze codebase structure", status: "in_progress" },
47+
{ content: "Identify files to modify", status: "pending" },
48+
{ content: "Implement changes", status: "pending" },
49+
{ content: "Run tests", status: "pending" },
50+
]),
51+
},
52+
};
53+
54+
export const NearlyComplete: Story = {
55+
args: {
56+
plan: createPlan([
57+
{ content: "Create database schema", status: "completed" },
58+
{ content: "Implement authentication endpoints", status: "completed" },
59+
{ content: "Add middleware for protected routes", status: "completed" },
60+
{ content: "Write unit tests", status: "completed" },
61+
{ content: "Write integration tests", status: "in_progress" },
62+
]),
63+
},
64+
};
65+
66+
export const WithFailure: Story = {
67+
args: {
68+
plan: createPlan([
69+
{ content: "Create database schema", status: "completed" },
70+
{ content: "Implement authentication endpoints", status: "failed" },
71+
{ content: "Add middleware for protected routes", status: "pending" },
72+
{ content: "Write unit tests", status: "pending" },
73+
]),
74+
},
75+
};
76+
77+
export const AllComplete: Story = {
78+
args: {
79+
plan: createPlan([
80+
{ content: "Create database schema", status: "completed" },
81+
{ content: "Implement authentication endpoints", status: "completed" },
82+
{ content: "Add middleware for protected routes", status: "completed" },
83+
]),
84+
},
85+
parameters: {
86+
docs: {
87+
description: {
88+
story: "When all tasks are complete, the status bar is hidden.",
89+
},
90+
},
91+
},
92+
};
93+
94+
export const NoPlan: Story = {
95+
args: {
96+
plan: null,
97+
},
98+
parameters: {
99+
docs: {
100+
description: {
101+
story: "When there is no plan, the status bar is hidden.",
102+
},
103+
},
104+
},
105+
};

mprocs.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ procs:
1111
cwd: .
1212
color: magenta
1313

14+
storybook:
15+
cmd: ["pnpm", "--filter", "twig", "run", "storybook"]
16+
cwd: .
17+
color: cyan
18+
autostart: false
19+
1420
mobile-ios:
1521
cmd: ["pnpm", "--filter", "@posthog/mobile", "run", "ios"]
1622
cwd: .

0 commit comments

Comments
 (0)