veza/apps/web/docs/ARCHITECTURE.md

4.1 KiB

Veza Frontend Architecture Guide

Status: Living Document
Version: 2.0 (Post-Audit 2026)

This document outlines the architectural principles, patterns, and rules that govern the Veza frontend. It supersedes previous ad-hoc audit reports.


1. Core Philosophy: "Visual First"

"If it can't be rendered in Storybook, it is architecturally broken."

We follow a Storybook-Driven Development (SDD) approach.

  • Isolation: Every component must be renderable in isolation. Dependencies (Providers, Router, Store) must be explicit.
  • Verification: Storybook is our primary verification tool for UI logic and layout.

2. State Management Strategy

We distinguish three types of state. Mixing them is strictly forbidden.

2.1. Server State (Data) -> React Query

Data that belongs to the backend (Users, Tracks, Playlists).

  • Tool: @tanstack/react-query
  • Rule: Never copy server data into a global store (Zustand) unless strictly necessary for client-side manipulation (e.g., a complex audio editor buffer).
  • Caching: Managed automatically by query keys.

2.2. Client Global State (App) -> Zustand

Data that is truly global to the client session (Auth Token, Shopping Cart, Audio Player Status).

  • Tool: zustand
  • Rule: Use atomic selectors to prevent render-thrashing.
  • Structure:
    • authStore: User session.
    • cartStore: E-commerce state (Items, Total).
    • playerStore: Audio playback state.

Legacy Note: React Context is BANNED for high-frequency state updates. It is reserved for dependency injection (Theme, i18n).

2.3. UI Local State -> useState / useReducer

Ephemeral state specific to a component (Modal Open/Close, Form Inputs).

  • Rule: If it doesn't need to persist when navigating away, it stays local.

3. Component Engineering

We adhere to the Smart vs Dumb (Container vs Presentational) separation to ensure testability.

3.1. Dumb Components (UI)

  • Role: Render props into HTML. Emit events via callbacks.
  • Dependencies: ZERO. No explicit side-effects, no API calls, no Context consumers (except Theme).
  • Testing: Storybook.
// ✅ Correct Dumb Component
export const ProductCard = ({ title, price, onAddToCart }: Props) => (
  <div onClick={onAddToCart}>{title} - {price}</div>
);

3.2. Smart Components (Containers)

  • Role: Wire data to UI.
  • Dependencies: Allowed (useQuery, useCartStore, useParams).
  • Testing: Integration Tests (MSW + Storybook play functions).
// ✅ Correct Smart Component
export const ProductCardContainer = ({ id }) => {
  const { data } = useProduct(id);
  const addToCart = useCartStore(s => s.addItem);
  return <ProductCard title={data.title} onAddToCart={() => addToCart(data)} />;
};

4. Design System & Styling

We use Tailwind CSS with a rigorous Design System (Kodo).

  • Tokens Only: Do not use arbitrary values (e.g., w-[350px]). Use design tokens (w-sidebar).
  • Dark Mode: All UI/Layout components must implement dark: variants.
  • Icons: lucide-react. Icons must inherit color via currentColor.

5. Storybook Usage

Storybook is not optional. It is the definition of "Done".

5.1. Decorators

Use granular decorators from src/stories/decorators.tsx instead of global wrapping in preview.tsx.

  • withToast: Injects ToastProvider.
  • withRouter: Injects MemoryRouter.
  • withStoreState: Mocks Zustand state.

5.2. Interaction Testing

Critical user flows (e.g., Add to Cart) must have a .play function in their story to verify interaction without manual testing.


6. Testing Pyramid

  1. Unit (Vitest): Utilities, Store Reducers, Hooks.
  2. Integration (Storybook + Vitest): Component wiring, Props interface.
  3. E2E (Playwright): Critical Paths (Login, Checkout, Signup).

7. Anti-Patterns (Dos & Don'ts)

Don't Do
useContext(CartContext) useCartStore(selector)
w-[17px] w-4 or w-5 (stick to grid)
Props Drilling (> 3 levels) Composition (Slots) or Context (if static)
API calls in useEffect useQuery
any type Generated types from OpenAPI