| Field | Value |
|---|
| ID | FRD-047 |
| Owner | David Holmes |
| Status | Shipped |
| Last Updated | 2026-05-26 |
| Target Release | v2.1.0 |
| Type | Infra |
| Complexity | M |
Document Summary
Eight UI components in src/components/ui/ lack Storybook stories and documentation. This FRD defines the work to add stories for each component using the createComponentDocs pattern, mark internal-only helpers with @internal JSDoc, and ensure all components are visually covered in Storybook for regression testing and discoverability.
Introduction
Overview
The design system has eight components without Storybook stories: scorecard.tsx, gauge-ring.tsx, manifest-viewer.tsx, filter-pill-bar.tsx, overlay-caret.tsx, search-result-row.tsx, slide-out-panel-manager.tsx, and pod-terminal-live-session.tsx. Without stories, these components are invisible to consumers browsing Storybook, untested by visual regression, and undocumented for usage patterns.
Goals
- Add a Storybook story file for each of the eight components.
- Use the
createComponentDocs utility for consistent documentation structure.
- Mark internal-only components with
@internal JSDoc so consumers know the stability contract.
- Ensure every story renders correctly in
pnpm build-storybook.
Non-Goals
- Redesigning or refactoring any of these components.
- Deciding public vs. internal export status (covered by FRD-048, barrel export audit).
- Adding interaction tests beyond visual snapshots.
Scope
In Scope
| Item | Description |
|---|
scorecard.stories.tsx | Story for the scorecard metric display component (74 lines). |
gauge-ring.stories.tsx | Story for the circular gauge visualization (150 lines). |
manifest-viewer.stories.tsx | Story for the YAML/JSON manifest viewer (9 lines — may be a re-export or stub). |
filter-pill-bar.stories.tsx | Story for the filter pill bar component (115 lines). |
overlay-caret.stories.tsx | Story for the overlay positioning caret (108 lines). |
search-result-row.stories.tsx | Story for the search result list item (236 lines). |
slide-out-panel-manager.stories.tsx | Story for the slide-out panel container (234 lines). |
pod-terminal-live-session.stories.tsx | Story for the live terminal session component (182 lines). |
@internal JSDoc | Add @internal tag to components confirmed as internal helpers. |
Out of Scope
| Item | Reason |
|---|
| Barrel export decisions | Covered by FRD-048. |
| Component refactoring | Stories document current behavior, not desired behavior. |
| Interaction or accessibility tests | Can be added as follow-up; this FRD covers visual stories only. |
Users and Pain Points
| User | Pain Point |
|---|
| Component consumer | Cannot discover these components in Storybook; must read source code to learn about them. |
| Visual regression CI | These components are not covered by visual snapshots, so regressions go undetected. |
| New team member | No documentation for usage patterns, props, or expected behavior of these components. |
| Design reviewer | Cannot review visual appearance of these components in Storybook without running the app. |
Definitions
| Term | Definition |
|---|
createComponentDocs | A utility function used across the design system to generate consistent Storybook documentation pages with prop tables, usage examples, and design notes. |
@internal JSDoc | A JSDoc tag indicating that a component is not part of the public API and may change without notice. |
| Visual regression | Automated screenshot comparison that detects unintended visual changes between builds. |
Current State
The eight components exist in src/components/ui/ and are used internally by other components or application surfaces. None have corresponding .stories.tsx files. Component sizes range from 9 lines (manifest-viewer.tsx, likely a re-export) to 236 lines (search-result-row.tsx). None have @internal JSDoc tags despite several being internal implementation details.
Proposed Solution
For each of the eight components:
- Create a story file adjacent to the component (e.g.,
scorecard.stories.tsx next to scorecard.tsx).
- Use
createComponentDocs to generate the docs page with:
- Component description.
- Prop table (auto-generated from TypeScript types).
- A default story showing typical usage.
- At least one variant story showing a key alternative state (e.g., empty state, loading state, error state).
- Story
title follows the Storybook sidebar governance (ADR-016). Place under the existing Components/UI/ path.
- For components confirmed as internal helpers (those not in the barrel export), add
/** @internal */ JSDoc above the component export.
Story placement per ADR-016
All stories go under Components/UI/[ComponentName] since these are UI-layer components. No new Storybook root section is created.
Requirements
| ID | Priority | Requirement |
|---|
| STORY-01 | P0 | Each of the 8 components has a .stories.tsx file. |
| STORY-02 | P0 | Each story uses createComponentDocs for the docs page. |
| STORY-03 | P0 | Each story includes a default story and at least one variant. |
| STORY-04 | P0 | All stories render without errors in pnpm build-storybook. |
| STORY-05 | P1 | Internal-only components have @internal JSDoc. |
| STORY-06 | P1 | Story titles follow ADR-016 sidebar governance. |
Functional Requirements
- Scorecard: Default story shows a scorecard with label, value, and trend indicator. Variant shows scorecard with no trend (neutral state).
- Gauge Ring: Default story shows a gauge at 75% fill. Variants for 0%, 50%, and 100%. Variant for custom color.
- Manifest Viewer: Default story shows a sample YAML manifest. Variant shows JSON format.
- Filter Pill Bar: Default story shows 3-4 active filter pills. Variant shows empty state (no filters). Variant shows overflow (many pills).
- Overlay Caret: Default story shows the caret positioned above a target. Variants for each position (top, bottom, left, right).
- Search Result Row: Default story shows a result with title, snippet, and metadata. Variant shows highlighted search terms. Variant shows result without snippet.
- Slide Out Panel Manager: Default story shows the panel in open state with sample content. Variant shows closed/collapsed state. Variant shows panel with header actions.
- Pod Terminal Live Session: Default story shows a terminal with sample output lines. Variant shows connection-pending state. Variant shows disconnected state.
Non-Functional Requirements
| Category | Requirement |
|---|
| Consistency | All stories use the same documentation pattern (createComponentDocs) as existing component stories. |
| Build time | Stories should not significantly increase Storybook build time (estimated less than 5 seconds additional). |
| Snapshot stability | Stories should produce deterministic screenshots (no animations in default state, mocked data). |
API/Interface Requirements
No API changes. Stories document the existing component APIs.
Accessibility Requirements
- Each story should render the component in an accessible state (proper ARIA attributes, keyboard focus visible).
- The
createComponentDocs output includes an accessibility section noting any ARIA requirements.
- Stories for interactive components (filter-pill-bar, slide-out-panel-manager) should demonstrate keyboard interaction in the docs page.
Content and Documentation Requirements
- Each
createComponentDocs call includes:
- A one-sentence component description.
- A “When to use” note.
- A “When not to use” note (if applicable).
- Props table (auto-generated).
- For
@internal components, the description includes a note that the component is internal and may change.
Dependencies
| Dependency | Type | Notes |
|---|
createComponentDocs | Internal | Existing utility for story documentation. |
| ADR-016 | Governance | Sidebar placement rules. |
| ADR-017 | Governance | Docs truthfulness rules. |
| The 8 component source files | Internal | src/components/ui/. |
Risks and Tradeoffs
| Risk | Likelihood | Impact | Mitigation |
|---|
manifest-viewer.tsx is only 9 lines and may not have meaningful props to document. | High | Low | If it is a simple re-export, the story just re-exports the upstream story or shows the composition. |
pod-terminal-live-session.tsx depends on heavy runtime deps (xterm). | Medium | Medium | Mock the terminal output in the story; do not connect to a real terminal. The lazy-loading FRD (FRD-049) addresses the bundle cost. |
| Story mocking may diverge from real component behavior. | Low | Low | Use the simplest mocks possible. Document any mocking in story source comments. |
Open Questions
- Should
manifest-viewer.tsx get its own story or be folded into a parent component’s story? Depends on whether it has standalone props.
- Should stories for
pod-terminal-live-session.tsx be gated behind a lazy-load boundary (coordinated with FRD-049)?
- Should
@internal components be placed under a separate Storybook path like Components/Internal/? ADR-016 does not currently define this. Leaning toward keeping under Components/UI/ with an [Internal] suffix in the story title.
Acceptance Criteria
LLM Handoff Instructions
When implementing this FRD:
- For each component, read the source file to understand props, states, and usage context.
- Create the story file adjacent to the component. Follow the pattern of an existing story in
src/components/ui/ (e.g., look for a well-documented component with createComponentDocs).
- Set the story
title to Components/UI/[ComponentName]. If the component is internal, append [Internal] to the title.
- Import
createComponentDocs from the shared story utility. Pass the component, description, and any configuration.
- Create a
Default story with representative props. Create at least one additional variant.
- For
pod-terminal-live-session.stories.tsx, mock terminal output rather than connecting to a real session. Use static sample lines.
- For
manifest-viewer.tsx, check if it re-exports another component. If so, the story can demonstrate the composition.
- Add
/** @internal */ JSDoc to component exports that are not in src/index.ts barrel exports.
- Run
pnpm build-storybook to confirm all stories render.
- Run
pnpm typecheck and pnpm vitest run --project unit.
Decision Log
| Date | Decision | Rationale |
|---|
| 2026-05-26 | Use createComponentDocs for all stories. | Consistent with existing design system documentation pattern. |
| 2026-05-26 | Place all stories under Components/UI/. | Follows ADR-016; no new root sections. |
| 2026-05-26 | Mark internal components with @internal JSDoc. | Communicates stability contract without removing or hiding components. |
Document History
| Version | Date | Author | Changes |
|---|
| 0.1 | 2026-05-26 | David Holmes | Initial draft. |