Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/decisions/ADR-0030-storybook-baseline-vite-8-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# ADR-0030: Storybook Baseline with Vite 8 Compatibility

- **Status**: Accepted
- **Date**: 2026-04-09
- **Deciders**: Taskdeck maintainers

## Context

Taskdeck uses Vite 8 in the frontend workspace. The Storybook baseline issue requested Storybook 8.x, but Storybook 8 does not support the Vite 8 toolchain used by the app.

The team still needs a stable, reviewable component catalogue for the 17 `Td*` primitives, plus a place to validate visual variants without touching the production app.

## Decision

Use Storybook 10.3.x for the frontend Storybook baseline.

Reasons:

- Storybook 10.3.x supports `vite@^8.0.0`.
- The CSF3 story format remains the same, so the story authoring model stays familiar.
- The preview should import the app stylesheet so Storybook reflects the production component look and spacing as closely as practical.
- Story files live in `src/stories/` to keep the component directory focused on runtime code.

## Alternatives Considered

- Storybook 8.x: rejected because it is not compatible with Vite 8.
- Deferring Storybook entirely: rejected because the component library needs a reviewable baseline now.
- Co-locating stories with components: workable, but rejected for this baseline because a centralized story directory keeps the setup simpler.

## Consequences

- Storybook can be built and maintained without downgrading Vite.
- Visual review of `Td*` primitives is available through `npm run storybook` and `npm run storybook:build`.
- Story files are excluded from the app typecheck, so production compilation stays focused on runtime code.
- Future contributors have a recorded rationale for the Storybook version choice.

## References

- PR #807
- Issue #251
- `frontend/taskdeck-web/.storybook/main.ts`
- `frontend/taskdeck-web/.storybook/preview.ts`
- `frontend/taskdeck-web/src/stories/`
1 change: 1 addition & 0 deletions docs/decisions/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,4 @@
| [0027](ADR-0027-cloud-target-topology-autoscaling.md) | Cloud Target Topology and Autoscaling Reference Architecture | Accepted | 2026-04-09 |
| [0028](ADR-0028-staged-deployment-bluegreen-canary.md) | Staged Deployment — Blue/Green with Canary Verification | Accepted | 2026-04-09 |
| [0029](ADR-0029-oidc-mfa-pluggable-identity.md) | OIDC/SSO Integration with Optional TOTP MFA | Accepted | 2026-04-09 |
| [0030](ADR-0030-storybook-baseline-vite-8-compatibility.md) | Storybook Baseline with Vite 8 Compatibility | Accepted | 2026-04-09 |
1 change: 1 addition & 0 deletions frontend/taskdeck-web/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ lerna-debug.log*
node_modules
dist
dist-ssr
storybook-static
*.local

# Editor directories and files
Expand Down
52 changes: 52 additions & 0 deletions frontend/taskdeck-web/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import type { StorybookConfig } from '@storybook/vue3-vite'

function isPwaPlugin(plugin: unknown): boolean {
if (plugin && typeof plugin === 'object' && 'name' in plugin) {
const name = String((plugin as { name?: unknown }).name ?? '').toLowerCase()
return name.includes('pwa') || name.includes('workbox')
}
return false
}

function stripPwaPlugins(plugins: unknown[]): unknown[] {
const filtered: unknown[] = []

for (const plugin of plugins) {
if (Array.isArray(plugin)) {
// Recursively filter nested arrays but preserve the array structure
const filteredNested = stripPwaPlugins(plugin)
if (filteredNested.length > 0) {
filtered.push(filteredNested)
}
continue
}
if (!isPwaPlugin(plugin)) {
filtered.push(plugin)
}
}

return filtered
}

const config: StorybookConfig = {
stories: ['../src/**/*.stories.@(ts|tsx)'],
framework: {
name: '@storybook/vue3-vite',
options: {
docgen: 'vue-component-meta',
},
},
async viteFinal(viteConfig) {
// Strip PWA plugin — it is app-specific and breaks the Storybook build
// because the Storybook output includes large JS bundles that exceed
// the workbox precache size limit.
Comment on lines +40 to +42
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The viteFinal configuration directly mutates the config.plugins array. It is safer to treat the configuration object as immutable and return a new object to avoid potential side effects in the Vite build pipeline.

Suggested change
// Strip PWA plugin — it is app-specific and breaks the Storybook build
// because the Storybook output includes large JS bundles that exceed
// the workbox precache size limit.
return {
...config,
plugins: config.plugins ? stripPwaPlugins(config.plugins as unknown[]) : config.plugins,
}

return {
...viteConfig,
plugins: viteConfig.plugins
? (stripPwaPlugins(viteConfig.plugins as unknown[]) as typeof viteConfig.plugins)
: viteConfig.plugins,
}
},
}

export default config
27 changes: 27 additions & 0 deletions frontend/taskdeck-web/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Preview } from '@storybook/vue3-vite'
import '../src/style.css'

const preview: Preview = {
parameters: {
controls: { expanded: true },
backgrounds: {
default: 'obsidian',
values: [
{ name: 'obsidian', value: '#131313' },
{ name: 'light', value: '#f5f3f1' },
],
},
},
decorators: [
(story) => ({
components: { story },
template: `
<div style="padding: 1rem;">
<story />
</div>
`,
}),
],
}

export default preview
Loading
Loading