/storybook-stories
Write Storybook stories for PostHog UI components. Covers the provider stack stories run inside, the key gotcha that tRPC/useHostTRPC queries never resolve in Storybook (so data-fetching components render empty), and the pure-presentational split that makes a component
Claude Code
$ npx -y skills add posthog/posthog --skill storybook-stories --agent claude-code
Codex
$ npx skills add posthog/posthog --skill storybook-stories -a codex -y
Cursor
$ npx skills add posthog/posthog --skill storybook-stories -a cursor -y
Gemini CLI
$ npx skills add posthog/posthog --skill storybook-stories -a gemini-cli -y
GitHub Copilot
$ npx skills add posthog/posthog --skill storybook-stories -a github-copilot -y
OpenCode
$ npx skills add posthog/posthog --skill storybook-stories -a opencode -y
Windsurf
$ npx skills add posthog/posthog --skill storybook-stories -a windsurf -y
SKILL.md
storybook-stories.SKILL.mdname: storybook-stories
description: Write Storybook stories for PostHog UI components. Covers the provider stack stories run inside, the key gotcha that tRPC/useHostTRPC queries never resolve in Storybook (so data-fetching components render empty), and the pure-presentational split that makes a component storyable. Use when adding or fixing a *.stories.tsx file under packages/ui.
Storybook stories in PostHog
Stories live next to components as `*.stories.tsx` and are collected by `apps/code/.storybook/main.ts` (its glob includes `packages/ui/src/**/*.stories.tsx`). Run/build:
pnpm --filter code storybook # dev server on :6006
pnpm --filter code build-storybook # static build (also a good CI/typecheck gate)
Every story is already wrapped (don't re-wrap)
`apps/code/.storybook/preview.tsx` applies two global decorators, so a story should **not** add its own providers or `<Theme>`:
- `withAppProviders` — a QueryClient, the host tRPC context, a DI
`ServiceProvider`, and a minimal TanStack Router. So `useHostTRPC()`, `useService()`, `useRouterState()`, etc. render instead of throwing "must be used within a Provider".
- A `<Theme>` (Radix) bound to the dark/light toolbar global. This root provider is
the one sanctioned Radix usage — it supplies the CSS tokens. Radix *components* are banned: build stories and components from `@posthog/quill` plus `div` + Tailwind (see [UI Components](../../../AGENTS.md#ui-components)).
Add a per-story decorator only to constrain layout (e.g. wrap in a `maxWidth` div so a full-width component sizes realistically).
The gotcha: data never arrives in Storybook
This is the thing that wastes time. In `withAppProviders` the tRPC `ipcLink` is a **no-op** (`apps/code/.storybook/mocks/electron-trpc.ts`), so:
- Any query issued through `useHostTRPC()` (and hooks built on it, like
`useClaudeCliSessions`) **stays pending forever** — `query.data` is `undefined`, permanently.
- `useService(TOKEN)` returns an **inert proxy stub** for anything not
explicitly bound (`service.foo().bar` never throws, but calls are no-ops). Only a few tokens resolve for real: `HOST_TRPC_CLIENT` (a no-op client with a handful of stubbed methods), `IMPERATIVE_QUERY_CLIENT`, `DIFF_WORKER_FACTORY`.
So a component that fetches its own data renders its **empty/loading** branch in Storybook — frequently `null`. Storying it directly shows nothing.
The fix: split a pure presentational component
Separate the data/wiring from the rendering, and story the pure part — which also satisfies the repo rule "components render; hooks wrap exactly one query" (`AGENTS.md`). Keep both in the same file:
// Pure — takes data + handlers as props. This is what the story targets.
export function WidgetList({ items, onPick }: WidgetListProps) { … }
// Container — does the tRPC/useService wiring, renders <WidgetList/>.
export function Widget({ repoPath }: WidgetProps) {
const { data } = useSomeQuery(repoPath);
return <WidgetList items={data?.items ?? []} onPick={…} />;
}Then each story is just `args` for `WidgetList` — one per visual state (empty, single, over-limit, in-flight/disabled, fallback text, …). Real example: `packages/ui/src/features/task-detail/components/ContinueCliSessions.tsx` + `.stories.tsx`.
Filtering/branching that lives in the container (not the pure view) isn't exercised by these visual stories — cover it with a small unit test if it's worth pinning.
Conventions
- `title` groups in the sidebar, e.g. `"Task Detail/ContinueCliSessions"`.
- Build fixtures with a small factory (`session(overrides)`) rather than
repeating object literals across stories.
- `Date.now()`/`new Date()` are fine in stories, but fixed ISO strings keep
relative-time output stable enough for visual review.
- Typecheck covers stories (they're `.tsx` under the package); a
`build-storybook` additionally catches Storybook-specific breakage.
Read more
name: storybook-stories description: Write Storybook stories for PostHog UI components. Covers the provider stack stories run inside, the key gotcha that tRPC/useHostTRPC queries never resolve in Storybook (so data-fetching components render empty), and the pure-presentational split that makes a component storyable. Use when adding or fixing a *.stories.tsx file under packages/ui.
Storybook stories in PostHog
Stories live next to components as `*.stories.tsx` and are collected by `apps/code/.storybook/main.ts` (its glob includes `packages/ui/src/**/*.stories.tsx`). Run/build:
pnpm --filter code storybook # dev server on :6006 pnpm --filter code build-storybook # static build (also a good CI/typecheck gate)
Every story is already wrapped (don't re-wrap)
`apps/code/.storybook/preview.tsx` applies two global decorators, so a story should **not** add its own providers or `<Theme>`:
- `withAppProviders` — a QueryClient, the host tRPC context, a DI
`ServiceProvider`, and a minimal TanStack Router. So `useHostTRPC()`, `useService()`, `useRouterState()`, etc. render instead of throwing "must be used within a Provider".
- A `<Theme>` (Radix) bound to the dark/light toolbar global. This root provider is
the one sanctioned Radix usage — it supplies the CSS tokens. Radix *components* are banned: build stories and components from `@posthog/quill` plus `div` + Tailwind (see [UI Components](../../../AGENTS.md#ui-components)).
Add a per-story decorator only to constrain layout (e.g. wrap in a `maxWidth` div so a full-width component sizes realistically).
The gotcha: data never arrives in Storybook
This is the thing that wastes time. In `withAppProviders` the tRPC `ipcLink` is a **no-op** (`apps/code/.storybook/mocks/electron-trpc.ts`), so:
- Any query issued through `useHostTRPC()` (and hooks built on it, like
`useClaudeCliSessions`) **stays pending forever** — `query.data` is `undefined`, permanently.
- `useService(TOKEN)` returns an **inert proxy stub** for anything not
explicitly bound (`service.foo().bar` never throws, but calls are no-ops). Only a few tokens resolve for real: `HOST_TRPC_CLIENT` (a no-op client with a handful of stubbed methods), `IMPERATIVE_QUERY_CLIENT`, `DIFF_WORKER_FACTORY`.
So a component that fetches its own data renders its **empty/loading** branch in Storybook — frequently `null`. Storying it directly shows nothing.
The fix: split a pure presentational component
Separate the data/wiring from the rendering, and story the pure part — which also satisfies the repo rule "components render; hooks wrap exactly one query" (`AGENTS.md`). Keep both in the same file:
// Pure — takes data + handlers as props. This is what the story targets.
export function WidgetList({ items, onPick }: WidgetListProps) { … }
// Container — does the tRPC/useService wiring, renders <WidgetList/>.
export function Widget({ repoPath }: WidgetProps) {
const { data } = useSomeQuery(repoPath);
return <WidgetList items={data?.items ?? []} onPick={…} />;
}Then each story is just `args` for `WidgetList` — one per visual state (empty, single, over-limit, in-flight/disabled, fallback text, …). Real example: `packages/ui/src/features/task-detail/components/ContinueCliSessions.tsx` + `.stories.tsx`.
Filtering/branching that lives in the container (not the pure view) isn't exercised by these visual stories — cover it with a small unit test if it's worth pinning.
Conventions
- `title` groups in the sidebar, e.g. `"Task Detail/ContinueCliSessions"`.
- Build fixtures with a small factory (`session(overrides)`) rather than
repeating object literals across stories.
- `Date.now()`/`new Date()` are fine in stories, but fixed ISO strings keep
relative-time output stable enough for visual review.
- Typecheck covers stories (they're `.tsx` under the package); a
`build-storybook` additionally catches Storybook-specific breakage.
:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.
More skills from other plugins.
paddleocr-doc-parsing
Use this skill to extract structured Markdown/JSON from PDFs and document images—tables with cell-level precision, formulas as…
13c-metabolic-flux
Estimates intracellular metabolic fluxes from steady-state carbon-13 isotope-tracing measurements using validated atom maps,…
Questions about storybook-stories.
- Which coding agents can use storybook-stories?
- storybook-stories installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

