---
name: frontend-flow
description: Ensures frontend code quality by enforcing Design System (9 ESLint rules), Storybook coverage, and 5-step verification pipeline. Use when creating/modifying React components in frontend/src/, especially shared/ where quality gates are mandatory. Triggers on "create component", "verify frontend", "storybook coverage", "перевір фронтенд", "frontend flow". Goal - all verification passes before task completion, knowledge captured in vault.
---
# Frontend Flow
Quality-first frontend development workflow for Pulse Radar.
## Overview
```
┌─────────────────────────────────────────────────────────────┐
│ QUALITY-FOCUSED FRONTEND WORKFLOW │
│ │
│ 1. SESSION 2. DISCOVERY 3. IMPLEMENT 4. STORYBOOK │
│ ──────────── ──────────── ──────────── ──────────── │
│ Journal Storybook MCP Tokens Stories │
│ Beads Vault Search Patterns Screenshots │
│ Context AGENTS.md Design System Interaction │
│ │
│ 5. VERIFICATION (CRITICAL) 6. KNOWLEDGE │
│ ──────────────────────────── ──────────── │
│ TypeScript → ESLint → Tests Journal done │
│ Story check → Accessibility Capture │
│ ALL MUST PASS Beads update │
└─────────────────────────────────────────────────────────────┘
```
**Principle:** Quality over speed. Verification must pass before task completion.
## Phase 1: Session & Context
**Trigger:** Starting frontend work
**Actions:**
1. Start journal session: `/obsidian:journal session "Task title"`
2. Get issue context: `bd show {issue}`
3. Read previous handoff if exists
**Goal:** Establish context and enable progress tracking.
## Phase 2: Discovery (CRITICAL!)
**Trigger:** BEFORE creating or modifying any component
**Required checks:**
```bash
# 1. Check existing components in Storybook
storybook_list_components
# 2. Get props API for similar components
storybook_get_component_props componentId="..."
# 3. Search knowledge vault
/obsidian:search "relevant pattern"
# 4. Read Design System rules
frontend/AGENTS.md
```
**Checkpoint:** "Existing component found? → USE IT instead of creating new!"
**Anti-pattern:** Creating new component without Storybook check = quality violation.
→ See @references/discovery.md for detailed checklist.
## Phase 3: Implementation (Design System)
**Trigger:** During code writing
**Required rules:**
| Rule | Use | Don't Use |
|------|-----|-----------|
| Colors | `@shared/tokens/*` | `bg-red-500`, `text-green-600` |
| Patterns | `@shared/patterns/*` | Manual composition |
| Icons | `lucide-react` | heroicons, Radix icons |
| Touch | `h-11 w-11` (44px) | `h-9 w-9` (36px) |
| Spacing | `gap-2`, `gap-4`, `p-4` | `gap-3`, `p-5` (off-grid) |
| Icon buttons | `aria-label="..."` | No label |
| Status | Icon + Text | Color only |
**Related skills:** `/frontend`, `/design-tokens`
## Phase 3.5: Component Portability (NEW!)
**Trigger:** When creating/modifying components in `shared/`
**Principle:** Components in shared/ must be PORTABLE — work without API, store, router.
```
┌─────────────────────────────────────────────────────────────┐
│ PORTABLE COMPONENT = Pure Function │
│ │
│ Props → Component → UI │
│ │
│ ✅ No useQuery/useMutation inside │
│ ✅ No apiClient/fetch calls inside │
│ ✅ No useNavigate/useParams/useLocation │
│ ✅ No useStore (Zustand) directly │
│ ✅ All data via props │
│ ✅ Works in Storybook WITHOUT providers │
└─────────────────────────────────────────────────────────────┘
```
**Anti-pattern (FORBIDDEN):**
```tsx
// ❌ NON-PORTABLE — violates CDD principles
import { useQuery } from '@tanstack/react-query'
import { apiClient } from '@/shared/lib/api'
export function ActivityHeatmap() {
const { data } = useQuery({ queryFn: () => apiClient.get('/activity') })
return
}
```
**Correct pattern (Presenter + Hook):**
```tsx
// ✅ PORTABLE PRESENTER — shared/components/
interface ActivityHeatmapProps {
data: ActivityDataPoint[]
isLoading?: boolean
className?: string
}
export function ActivityHeatmap({ data, isLoading, className }: ActivityHeatmapProps) {
if (isLoading) return
return
}
// Data hook — features/activity/hooks/ or page level
export function useActivityData(timeRange: TimeRange) {
return useQuery({
queryKey: ['activity', timeRange],
queryFn: () => activityService.getActivity(timeRange)
})
}
// Page usage — composition
function DashboardPage() {
const { data, isLoading } = useActivityData('week')
return
}
```
**Portability Checklist:**
- [ ] No useQuery/useMutation in shared/components
- [ ] No apiClient/fetch imports in shared/components
- [ ] No useNavigate/useParams in shared/components
- [ ] All data passed via typed props interface
- [ ] Optional className prop for customization
- [ ] Works in Storybook without QueryClient/Router mocks
**Verification command:**
```bash
# Should return 0 violations
grep -rE "useQuery|apiClient" src/shared/components --include="*.tsx" | grep -v test
```
## Phase 3.6: Component Integration Decision (NEW!)
**Trigger:** When needing a new UI element
**Decision Tree:**
```
NEED NEW UI ELEMENT?
│
▼
┌─────────────────────────────┐
│ 1. Check shared/ui/ │ → EXISTS → USE IT
└─────────────────────────────┘
│ NOT FOUND
▼
┌─────────────────────────────┐
│ 2. Check shared/components/ │ → EXISTS → USE IT
└─────────────────────────────┘
│ NOT FOUND
▼
┌─────────────────────────────┐
│ 3. Check shared/patterns/ │ → EXISTS → USE IT
└─────────────────────────────┘
│ NOT FOUND
▼
┌─────────────────────────────┐
│ 4. Check shadcn registry │
│ ui.shadcn.com/docs │
└─────────────────────────────┘
│
FOUND ┴ NOT FOUND
│ │
▼ ▼
EVALUATE BUILD in shared/components
│
Fits 80%? ┬ YES → npx shadcn add → shared/ui
│
└ NO → WRAP in shared/components
```
**Where to put new component:**
| Type | Location | Example |
|------|----------|---------|
| shadcn primitive | `shared/ui/` | Button, Dialog, Select |
| shadcn + customize | `shared/ui/` (modify) | Badge with new variant |
| Composition | `shared/patterns/` | CardWithStatus, FormField |
| Business reusable | `shared/components/` | SearchBar, TopicSelector |
| Feature-specific | `features/X/components/` | AtomCard, ProviderList |
| Page-specific | `pages/X/components/` | DashboardStats |
**Adding shadcn component example:**
```bash
# 1. Check if exists
grep -r "DatePicker\|Calendar" src/shared/
# 2. Add from shadcn
npx shadcn@latest add calendar
# 3. Create composition wrapper if needed
# src/shared/components/DatePicker/index.tsx
```
**Checkpoint:** "Component location decided? Portability requirements clear?"
## Phase 4: Storybook (Component Library)
**Trigger:** After creating/modifying component
**Mandatory coverage:**
| Location | Story Required? | Min Stories |
|----------|-----------------|-------------|
| `shared/ui/` | Always | 4-6 |
| `shared/patterns/` | Always | 5-8 |
| `shared/components/` | Always | 4-6 |
| `features/*/` | If >50 LOC or reused | 2-4 |
**Actions:**
1. Create `.stories.tsx` with `tags: ['autodocs']`
2. Cover all variants, states, sizes
3. Add interaction tests for clickable elements
4. Capture screenshot: `storybook_capture_screenshot storyId="..."`
**Checkpoint:** "Story created with full coverage?"
**Related skill:** `/storybook`
## Phase 5: Verification Pipeline (MOST IMPORTANT!)
**Trigger:** After code written, BEFORE considering task complete
**Required pipeline (ALL steps!):**
```bash
cd frontend
# 1. TypeScript — type safety
npx tsc --noEmit
# 2. ESLint — Design System compliance (9 rules)
npm run lint
# 3. Unit tests — logic correctness
npm run test:run
# 4. Story coverage — documentation
npm run story:check
# 5. Accessibility (for shared/ components)
npm run test:a11y
```
**ESLint Design System Rules (all 9 — ERROR level):**
| # | Rule | Ensures |
|---|------|---------|
| 1 | `no-raw-tailwind-colors` | Semantic tokens only |
| 2 | `no-odd-spacing` | 4px grid system |
| 3 | `no-heroicons` | lucide-react only |
| 4 | `no-raw-zindex` | Named z-index tokens |
| 5 | `no-direct-fonts` | Font tokens only |
| 6 | `no-raw-page-wrapper` | PageWrapper required |
| 7 | `no-direct-api-imports` | Service classes |
| 8 | `no-hardcoded-api-paths` | API_ENDPOINTS config |
| 9 | `stories-require-autodocs` | Autodocs tag |
**If any step fails:** Fix → Re-run pipeline → Don't proceed until ALL pass.
→ See @references/verification.md for troubleshooting.
## Phase 6: Knowledge Capture
**Trigger:** After successful verification
**Actions:**
1. Close journal session: `/obsidian:journal done`
2. Add findings as wikilinks: `[[знання/паттерни/pattern-name]]`
3. Capture learnings: `/obsidian:capture auto`
4. Update Beads: `bd comments add {issue} "Progress: ..."`
5. Generate handoff for next session
**Learning categories:**
- `знання/паттерни/` — reusable approaches
- `знання/рішення/` — architectural decisions
- `знання/помилки/` — mistakes to avoid
→ See @references/documentation.md for templates.
## Quality Gates
| Gate | Criterion | Blocking? |
|------|-----------|-----------|
| Discovery | Storybook checked BEFORE coding | Yes |
| Design System | Tokens instead of raw colors | Yes (ESLint blocks) |
| Storybook | Story for shared/ components | Yes |
| TypeScript | `tsc --noEmit` passes | Yes |
| ESLint | 9 Design System rules pass | Yes |
| Tests | Unit tests pass | Yes |
| Story Coverage | `story:check` passes | Yes |
| Accessibility | `test:a11y` for shared/ | Recommended |
| Knowledge | Learnings captured in vault | Recommended |
**Principle:** If verification fails → task NOT complete.
## Quick Reference
**Commands:**
| Action | Command |
|--------|---------|
| Start Storybook | `just storybook` |
| TypeScript check | `npx tsc --noEmit` |
| ESLint | `npm run lint` |
| ESLint fix | `npm run lint:fix` |
| Unit tests | `npm run test:run` |
| Story coverage | `npm run story:check` |
| A11y tests | `npm run test:a11y` |
**Related skills:**
| Skill | When to use |
|-------|-------------|
| `/frontend` | Architecture, state management |
| `/storybook` | Story templates, interaction tests |
| `/design-tokens` | Token API reference |
| `/testing` | Vitest, Playwright patterns |
## Example
```
User: "Create TopicCard component"
Claude follows frontend-flow:
1. [Session] /obsidian:journal session "TopicCard component"
2. [Discovery] BEFORE coding!
→ storybook_list_components → check existing cards
→ Found CardWithStatus — base structure exists!
→ /obsidian:search "card pattern" → found prior decisions
3. [Implementation]
→ Using @shared/tokens for colors
→ Using @shared/patterns/CardWithStatus as base
→ All icon buttons with aria-label
→ 44px touch targets
4. [Storybook]
→ TopicCard.stories.tsx with tags: ['autodocs']
→ 5 stories: Default, Empty, Loading, Error, WithBadge
→ storybook_capture_screenshot
5. [Verification] ALL must pass!
→ npx tsc --noEmit ✅
→ npm run lint ✅ (9 ESLint rules pass)
→ npm run test:run ✅
→ npm run story:check ✅
→ npm run test:a11y ✅
6. [Knowledge]
→ /obsidian:journal done
→ Findings: [[знання/паттерни/topic-card-composition]]
→ /obsidian:capture auto
```
**Key:** Verification runs COMPLETELY before task is considered done.