Write Storybook Stories for ComfyUI_frontend
Workflow
- !!!!IMPORTANT Confirm the worktree is on a
feat/* or fix/* branch. Base PRs on the local main, not a fork branch.
- Read the component source first. Understand props, emits, slots, exposed methods, and any supporting types or composables.
- Read nearby stories before writing anything.
- Search stories:
rg --files src apps | rg '\.stories\.ts$'
- Inspect title patterns:
rg -n "title:\\s*'" src apps --glob '*.stories.ts'
- If a Figma link is provided, list the states you need to cover before writing stories.
- Co-locate the story file with the component:
ComponentName.stories.ts.
- Add each variation on separate stories, except hover state. this should be automatically applied by the implementation and not require a separate story.
- Run Storybook and validation checks before handing off.
Match Local Conventions
- Copy the closest neighboring story instead of forcing one universal template.
- Most repo stories use
@storybook/vue3-vite.
- Add
tags: ['autodocs'] unless the surrounding stories in that area intentionally omit it.
- Use
ComponentPropsAndSlots<typeof Component> when it helps with prop and slot typing.
- Keep
render functions stateful when needed. Use ref(), computed(), and toRefs(args) instead of mutating Storybook args directly.
- Use
args.default or other slot-shaped args when the component content is provided through slots.
- Use
ComponentExposed only when a component's exposed API breaks the normal Storybook typing.
- Add decorators for realistic width or background context when the component needs it.
Title Patterns
Do not invent titles from scratch when a close sibling story already exists. Match the nearest domain pattern.
| Component area |
Typical title pattern |
src/components/ui/button/Button.vue |
Components/Button/Button |
src/components/ui/input/Input.vue |
Components/Input |
src/components/ui/search-input/SearchInput.vue |
Components/Input/SearchInput |
src/components/common/SearchBox.vue |
Components/Input/SearchBox |
src/renderer/extensions/vueNodes/widgets/components/* |
Widgets/<WidgetName> |
src/platform/assets/components/* |
Platform/Assets/<ComponentName> |
If multiple patterns seem plausible, follow the closest sibling story in the same folder tree.
Common Story Shapes
Stateful input or v-model
export const Default: Story = {
render: (args) => ({
components: { MyComponent },
setup() {
const { disabled, size } = toRefs(args)
const value = ref('Hello world')
return { value, disabled, size }
},
template:
'<MyComponent v-model="value" :disabled="disabled" :size="size" />'
})
}
Slot-driven content
const meta: Meta<ComponentPropsAndSlots<typeof Button>> = {
argTypes: {
default: { control: 'text' }
},
args: {
default: 'Button'
}
}
export const SingleButton: Story = {
render: (args) => ({
components: { Button },
setup() {
return { args }
},
template: '<Button v-bind="args">{{ args.default }}</Button>'
})
}
Variants or edge cases grid
export const AllVariants: Story = {
render: () => ({
components: { MyComponent },
template: `
<div class="grid gap-4 sm:grid-cols-2">
<MyComponent />
<MyComponent disabled />
<MyComponent loading />
<MyComponent invalid />
</div>
`
})
}
Figma Mapping
- Extract the named states from the design first.
- Prefer explicit prop-driven stories such as
Disabled, Loading, Invalid, WithPlaceholder, AllSizes, or EdgeCases.
- Add an aggregate story such as
AllVariants, AllSizes, or EdgeCases when side-by-side comparison is useful.
- Use pseudo-state parameters only if the addon is already configured in this repo.
- If a Figma state cannot be represented exactly, capture the closest prop-driven version and explain the gap in the story docs.
Component-Specific Notes
- Widget components often need a minimal
SimplifiedWidget object. Build it in setup() and use computed() when args change widget.options.
- Input and search components often need a width-constrained wrapper so they render at realistic sizes.
- Asset and platform cards often need background decorators such as
bg-base-background and fixed-width containers.
- Desktop installer stories may need custom
backgrounds parameters and may intentionally keep the older Storybook import style used by neighboring files.
- Use semantic tokens such as
bg-base-background and bg-node-component-surface instead of dark: variants or hardcoded theme assumptions.
Checklist
Avoid
- Do not guess props, emits, slots, or exposed methods.
- Do not force one generic title convention across the repo.
- Do not mutate Storybook args directly for
v-model components.
- Do not introduce
dark: Tailwind variants in story wrappers.
- Do not create barrel files.
- Do not assume every story needs
layout: 'centered' or a Default export; follow the nearest existing pattern.
1---2name: writing-storybook-stories3description: Write or update Storybook stories for Vue components in ComfyUI_frontend. Use when adding, modifying, reviewing, or debugging `.stories.ts` files, Storybook docs, component demos, or visual catalog entries.4---56# Write Storybook Stories for ComfyUI_frontend78## Workflow9101. !!!!IMPORTANT Confirm the worktree is on a `feat/*` or `fix/*` branch. Base PRs on the local `main`, not a fork branch.112. Read the component source first. Understand props, emits, slots, exposed methods, and any supporting types or composables.123. Read nearby stories before writing anything.13 - Search stories: `rg --files src apps | rg '\.stories\.ts$'`14 - Inspect title patterns: `rg -n "title:\\s*'" src apps --glob '*.stories.ts'`154. If a Figma link is provided, list the states you need to cover before writing stories.165. Co-locate the story file with the component: `ComponentName.stories.ts`.176. Add each variation on separate stories, except hover state. this should be automatically applied by the implementation and not require a separate story.187. Run Storybook and validation checks before handing off.1920## Match Local Conventions2122- Copy the closest neighboring story instead of forcing one universal template.23- Most repo stories use `@storybook/vue3-vite`.24- Add `tags: ['autodocs']` unless the surrounding stories in that area intentionally omit it.25- Use `ComponentPropsAndSlots<typeof Component>` when it helps with prop and slot typing.26- Keep `render` functions stateful when needed. Use `ref()`, `computed()`, and `toRefs(args)` instead of mutating Storybook args directly.27- Use `args.default` or other slot-shaped args when the component content is provided through slots.28- Use `ComponentExposed` only when a component's exposed API breaks the normal Storybook typing.29- Add decorators for realistic width or background context when the component needs it.3031## Title Patterns3233Do not invent titles from scratch when a close sibling story already exists. Match the nearest domain pattern.3435| Component area | Typical title pattern |36| ------------------------------------------------------- | --------------------------------- |37| `src/components/ui/button/Button.vue` | `Components/Button/Button` |38| `src/components/ui/input/Input.vue` | `Components/Input` |39| `src/components/ui/search-input/SearchInput.vue` | `Components/Input/SearchInput` |40| `src/components/common/SearchBox.vue` | `Components/Input/SearchBox` |41| `src/renderer/extensions/vueNodes/widgets/components/*` | `Widgets/<WidgetName>` |42| `src/platform/assets/components/*` | `Platform/Assets/<ComponentName>` |4344If multiple patterns seem plausible, follow the closest sibling story in the same folder tree.4546## Common Story Shapes4748### Stateful input or `v-model`4950```typescript51export const Default: Story = {52 render: (args) => ({53 components: { MyComponent },54 setup() {55 const { disabled, size } = toRefs(args)56 const value = ref('Hello world')57 return { value, disabled, size }58 },59 template:60 '<MyComponent v-model="value" :disabled="disabled" :size="size" />'61 })62}63```6465### Slot-driven content6667```typescript68const meta: Meta<ComponentPropsAndSlots<typeof Button>> = {69 argTypes: {70 default: { control: 'text' }71 },72 args: {73 default: 'Button'74 }75}7677export const SingleButton: Story = {78 render: (args) => ({79 components: { Button },80 setup() {81 return { args }82 },83 template: '<Button v-bind="args">{{ args.default }}</Button>'84 })85}86```8788### Variants or edge cases grid8990```typescript91export const AllVariants: Story = {92 render: () => ({93 components: { MyComponent },94 template: `95 <div class="grid gap-4 sm:grid-cols-2">96 <MyComponent />97 <MyComponent disabled />98 <MyComponent loading />99 <MyComponent invalid />100 </div>101 `102 })103}104```105106## Figma Mapping107108- Extract the named states from the design first.109- Prefer explicit prop-driven stories such as `Disabled`, `Loading`, `Invalid`, `WithPlaceholder`, `AllSizes`, or `EdgeCases`.110- Add an aggregate story such as `AllVariants`, `AllSizes`, or `EdgeCases` when side-by-side comparison is useful.111- Use pseudo-state parameters only if the addon is already configured in this repo.112- If a Figma state cannot be represented exactly, capture the closest prop-driven version and explain the gap in the story docs.113114## Component-Specific Notes115116- Widget components often need a minimal `SimplifiedWidget` object. Build it in `setup()` and use `computed()` when `args` change `widget.options`.117- Input and search components often need a width-constrained wrapper so they render at realistic sizes.118- Asset and platform cards often need background decorators such as `bg-base-background` and fixed-width containers.119- Desktop installer stories may need custom `backgrounds` parameters and may intentionally keep the older Storybook import style used by neighboring files.120- Use semantic tokens such as `bg-base-background` and `bg-node-component-surface` instead of `dark:` variants or hardcoded theme assumptions.121122## Checklist123124- [ ] Read the component source and any supporting types or composables125- [ ] Match the nearest local title pattern and story style126- [ ] Include a baseline story; name it `Default` only when that matches nearby conventions127- [ ] Add focused stories for meaningful states128- [ ] Add `tags: ['autodocs']`129- [ ] Keep the story co-located with the component130- [ ] Run `pnpm storybook`131- [ ] Run `pnpm typecheck`132- [ ] Run `pnpm lint`133134## Avoid135136- Do not guess props, emits, slots, or exposed methods.137- Do not force one generic title convention across the repo.138- Do not mutate Storybook args directly for `v-model` components.139- Do not introduce `dark:` Tailwind variants in story wrappers.140- Do not create barrel files.141- Do not assume every story needs `layout: 'centered'` or a `Default` export; follow the nearest existing pattern.