# Veza Architecture & Design: Critical Analysis **Date**: 2025-01-27 **Author**: Senior Full-Stack Architect & Product Designer **Scope**: Frontend ↔ Backend Integration, UI/UX Design **Exclusions**: Rust Chat & Audio Streaming Modules (as requested) --- ## PART 1 β€” FRONTEND / BACKEND INTEGRATION ANALYSIS ### 1. CONTRACT INTEGRITY #### βœ… **Strengths** 1. **Unified Response Envelope** - Backend consistently uses `{ success, data, error }` wrapper (`veza-backend-api/internal/response/response.go`) - Frontend client unwraps automatically (`apps/web/src/services/api/client.ts:312-316`) - **However**: Some endpoints return direct format (e.g., `{ tracks: [...], pagination: {...} }`), creating inconsistency 2. **Type Definitions** - TypeScript types exist (`apps/web/src/types/api.ts`) - Backend uses snake_case consistently - **Problem**: Types are duplicated between `types/api.ts`, `types/dto.ts`, and feature-specific type files #### πŸ”΄ **Critical Issues** 1. **Type Duplication & Drift** ```typescript // apps/web/src/types/api.ts:136-178 export interface Track { id: string; creator_id: string; artist: string; // String, not relation // ... 40+ fields } // apps/web/src/features/tracks/types/track.ts (likely different) // Backend: veza-backend-api/internal/models/track.go (Go struct) ``` **Impact**: Three sources of truth. Changes require manual sync. High risk of runtime errors. 2. **Implicit Contracts** - No OpenAPI/Swagger spec enforced at build time - Frontend assumes backend structure without runtime validation - Example: `LibraryPage.tsx:111-114` filters tracks client-side, but backend may already filter 3. **Response Format Inconsistency** ```typescript // Some endpoints return wrapped: { success: true, data: { tracks: [...], pagination: {...} } } // Others return direct: { tracks: [...], pagination: {...} } ``` **Location**: `apps/web/src/services/api/client.ts:312-316` handles both, but silently **Risk**: Silent failures when backend changes format #### πŸ“‹ **Recommendations** 1. **Generate Types from OpenAPI** - Use `openapi-generator` or `swagger-codegen` to generate TypeScript types from backend spec - Enforce at CI/CD: fail build if types don't match 2. **Runtime Schema Validation** - Use Zod schemas (`apps/web/src/schemas/apiSchemas.ts` exists but incomplete) - Validate ALL responses before unwrapping - Log violations in production 3. **Version API Contracts** - Add `/api/v1/` versioning (already exists) - Document breaking changes explicitly - Use semantic versioning for API changes --- ### 2. DATA FLOW CLARITY #### βœ… **Strengths** 1. **Centralized API Client** - Single `apiClient` (`apps/web/src/services/api/client.ts`) - Request deduplication (`requestDeduplication.ts`) - Response caching (`responseCache.ts`) - Offline queue (`offlineQueue.ts`) 2. **State Management** - Zustand stores for domain logic (`features/*/store/`) - React Query for server state (`@tanstack/react-query`) - Clear separation: Zustand = client state, React Query = server state #### πŸ”΄ **Critical Issues** 1. **Over-Fetching** ```typescript // DashboardPage.tsx:26 fetchItems({ limit: 5 }); // Fetches library items // But also: // useDashboard() hook likely fetches stats separately // Recent activity fetched separately ``` **Problem**: Multiple roundtrips for dashboard data that could be one endpoint 2. **Client-Side Filtering** ```typescript // LibraryPage.tsx:111-114 const filteredTracks: Track[] = useMemo(() => { if (!tracksData?.tracks) return []; return tracksData.tracks; // No filtering! But search/filter UI exists }, [tracksData?.tracks]); ``` **Issue**: Search/filter UI (`LibraryPage.tsx:322-327`) sends params to backend, but also filters client-side. Unclear which is authoritative. 3. **Stale State Risk** ```typescript // authStore.ts:142-218 refreshUser: async () => { // Complex deduplication logic // But: What if user updates profile in another tab? } ``` **Problem**: Broadcast sync exists (`broadcastSync.ts`) but only syncs Zustand state, not React Query cache 4. **Race Conditions** ```typescript // LibraryPage.tsx:116-120 useEffect(() => { if (searchTerm.trim() && page !== 1) { setPage(1); // Resets page on search } }, [searchTerm]); ``` **Issue**: If user types fast, multiple requests fire. Deduplication helps, but pagination state can desync. #### πŸ“‹ **Recommendations** 1. **Aggregate Endpoints** - Create `/api/v1/dashboard` returning: `{ stats, recent_activity, library_preview }` - Reduces 3+ requests to 1 2. **Server-Side Filtering Only** - Remove client-side filtering from `LibraryPage.tsx` - Backend handles all search/filter/sort - Frontend displays what backend returns 3. **Unified Cache Invalidation** - Sync React Query cache across tabs - Use BroadcastChannel for React Query invalidation - Current: Only Zustand syncs (`broadcastSync.ts`) 4. **Request Debouncing** - Add debounce to search input (`LibraryPage.tsx:322-327`) - Current: Fires on every keystroke --- ### 3. ERROR PROPAGATION #### βœ… **Strengths** 1. **Centralized Error Parsing** - `parseApiError()` (`apps/web/src/utils/apiErrorHandler.ts:47`) - Handles multiple backend error formats - Normalizes to `ApiError` interface 2. **User-Friendly Messages** - `formatUserFriendlyError()` (`apps/web/src/utils/errorMessages.ts:120`) - Context-aware messages - French localization #### πŸ”΄ **Critical Issues** 1. **Error Display Inconsistency** ```typescript // Some components use toast: toast.error('Impossible d\'ajouter la piste'); // Others use inline errors:
Erreur...
// Some use both: toast.error(...) && ``` **Problem**: No standard. Users miss errors or see duplicates. 2. **Silent Failures** ```typescript // LibraryPage.tsx:141-144 catch (error) { logger.error('Failed to add track to playlist:', { error }); toast.error('Impossible d\'ajouter la piste Γ  la playlist'); // No retry, no details, no recovery path } ``` **Issue**: Error logged but user gets generic message. No actionable feedback. 3. **Network Error Handling** ```typescript // apiErrorHandler.ts:108-122 if (axiosError.request && !axiosError.response) { // Network error return { code: 0, message: 'Network error: Unable to connect to server' }; } ``` **Problem**: Generic message. Doesn't distinguish: - Timeout vs connection refused - Offline vs server down - Partial failure vs complete failure 4. **Error Recovery Missing** - No retry UI for failed mutations - No "retry" button in error states - Offline queue exists but no UI to manage it #### πŸ“‹ **Recommendations** 1. **Standard Error Component** ```typescript retry()} showDetails={isDev} context="playlist" /> ``` 2. **Error Categories** - Network errors β†’ Show offline indicator + retry - Validation errors β†’ Highlight fields - Auth errors β†’ Redirect to login - Server errors β†’ Show request ID + support link 3. **Error Boundaries** - Wrap routes in error boundaries (`ErrorBoundary.tsx` exists but underused) - Catch unhandled errors gracefully 4. **Retry Logic** - Add "Retry" button to all error states - Use exponential backoff (already in `apiClient`, but no UI) --- ### 4. STATE OWNERSHIP #### βœ… **Strengths** 1. **Clear Separation** - Zustand: Client state (UI preferences, selections) - React Query: Server state (tracks, users, playlists) - Local state: Component-specific (modals, forms) 2. **Optimistic Updates** - `optimisticStoreUpdates.ts` exists - Used for some mutations #### πŸ”΄ **Critical Issues** 1. **State Duplication** ```typescript // authStore.ts stores user user: User | null; // But React Query also caches user: useQuery(['user', id], () => getUser(id)) ``` **Problem**: Two sources of truth. Can desync. 2. **Cache Invalidation Ambiguity** ```typescript // LibraryPage.tsx:153 queryClient.invalidateQueries({ queryKey: ['tracks'] }); // But tracks are also in Zustand library store // Which is source of truth? ``` **Issue**: Invalidate React Query but Zustand may still have stale data. 3. **Broadcast Sync Limitations** ```typescript // broadcastSync.ts:72-279 // Only syncs Zustand stores // React Query cache not synced ``` **Problem**: Open app in two tabs, update in one, other tab shows stale data. 4. **Race Conditions** ```typescript // authStore.ts:142-218 refreshUser: async () => { if (currentState._refreshUserPromise) { return currentState._refreshUserPromise; // Deduplication } // But: What if called from multiple components simultaneously? } ``` **Issue**: Complex deduplication logic suggests architectural problem. #### πŸ“‹ **Recommendations** 1. **Single Source of Truth** - Use React Query as primary cache - Zustand only for UI state (theme, sidebar open/closed) - Remove domain data from Zustand 2. **Unified Sync** - Extend `broadcastSync` to invalidate React Query - Or: Use React Query's built-in sync (if available) 3. **Simplify Auth State** - Store only `isAuthenticated` boolean in Zustand - User data from React Query: `useQuery(['user', 'me'])` - Remove `user` from Zustand 4. **Optimistic Updates Standardization** - Use React Query's `onMutate` for all mutations - Remove custom optimistic logic from stores --- ### 5. SECURITY & ROBUSTNESS #### βœ… **Strengths** 1. **JWT Authentication** - Tokens in Authorization header - Refresh token in httpOnly cookie - Token versioning for revocation 2. **CSRF Protection** - CSRF service exists (`apps/web/src/services/csrf.ts`) - Backend middleware (`veza-backend-api/internal/middleware/csrf.go`) 3. **Request Validation** - Zod schemas for requests (`apps/web/src/schemas/apiRequestSchemas.ts`) - Backend validation (`go-playground/validator`) #### πŸ”΄ **Critical Issues** 1. **Token Storage** ```typescript // tokenStorage.ts uses localStorage localStorage.setItem('access_token', token); ``` **Problem**: XSS vulnerability. Access token in localStorage can be stolen. **Current**: Refresh token in httpOnly cookie (good), but access token in localStorage (bad) 2. **Client-Side Validation Only** ```typescript // Forms validate client-side // But backend may have different rules ``` **Issue**: User sees "valid" but backend rejects. No pre-validation. 3. **Error Information Leakage** ```typescript // apiErrorHandler.ts:315-320 if (includeRequestId && error.request_id) { const isDev = import.meta.env.DEV; if (isDev) { message = `${message} [Request ID: ${error.request_id}]`; } } ``` **Problem**: Request IDs in dev only. Production errors lack correlation IDs for support. 4. **No Rate Limit Handling** - Backend has rate limiting - Frontend doesn't show rate limit status - No "slow down" UI feedback #### πŸ“‹ **Recommendations** 1. **Secure Token Storage** - Move access token to httpOnly cookie (like refresh token) - Or: Use short-lived tokens (5 min) + frequent refresh - Remove localStorage for tokens 2. **Pre-Validation** - Call `/api/v1/validate` endpoint before submit - Show backend validation errors in real-time 3. **Error Correlation** - Always show request ID to user (in support mode) - Add "Report Issue" button with request ID 4. **Rate Limit UI** - Show rate limit status in header - Disable buttons when rate limited - Show countdown timer --- ### 6. SCALABILITY & EVOLUTION #### πŸ”΄ **Critical Issues** 1. **Tight Coupling** ```typescript // Components import specific API functions import { getTracks } from '@/features/tracks/api/trackApi'; // But also use React Query useQuery(['tracks'], () => getTracks(...)) ``` **Problem**: Components know about API structure. Hard to change backend. 2. **No API Versioning Strategy** - `/api/v1/` exists but no migration path - No deprecation warnings - Breaking changes would break frontend silently 3. **Bundle Size** - Large component library (shadcn/ui) - Multiple state management solutions (Zustand + React Query) - No code splitting by route 4. **Performance Bottlenecks** ```typescript // LibraryPage.tsx:98-106 useQuery({ queryKey: ['tracks', 'library', queryParams, searchTerm], queryFn: () => getTracks(page, limit, queryParams), }); ``` **Issue**: Fetches 50 tracks at once. No virtualization. Large lists will lag. #### πŸ“‹ **Recommendations** 1. **API Abstraction Layer** ```typescript // services/api/tracks.ts export const tracksApi = { list: (params) => apiClient.get('/tracks', { params }), get: (id) => apiClient.get(`/tracks/${id}`), }; // Components use abstraction, not direct API calls ``` 2. **Versioning Strategy** - Add `X-API-Version` header - Backend returns `X-API-Deprecated: true` for old versions - Frontend shows migration warning 3. **Code Splitting** - Lazy load routes (`React.lazy`) - Split vendor bundles - Use dynamic imports for heavy components 4. **Virtualization** - Use `react-window` or `@tanstack/react-virtual` for long lists - Load tracks in chunks (infinite scroll) --- ## PART 2 β€” UI / UX CRITICAL REVIEW ### 1. VISUAL HIERARCHY #### πŸ”΄ **Critical Failures** 1. **No Clear Focal Point** ```tsx // DashboardPage.tsx:96-129

Bienvenue, username

Voici un aperΓ§u...

``` **Problem**: Welcome message competes with stats cards. Eye doesn't know where to look first. 2. **Stats Overload** ```tsx // DashboardPage.tsx:132-164 // 4 stat cards in grid // All same size, same visual weight ``` **Issue**: No hierarchy. All stats feel equally important. User scans but doesn't focus. 3. **Dead Zones** ```tsx // LibraryPage.tsx:317-383 // Filters card takes full width // But filters are sparse ``` **Problem**: Large empty space in filter card. Wasted screen real estate. 4. **Inconsistent Spacing** ```tsx // Some components use: space-y-6 // Others use: space-y-4 // Others use: gap-4 ``` **Issue**: No spacing system. Feels chaotic. #### πŸ“‹ **Recommendations** 1. **Establish Focal Point** - Make primary action (Upload Track) largest, most prominent - Use size contrast: Primary CTA = 2x secondary actions - Reduce welcome message size/weight 2. **Stats Hierarchy** - Show 1-2 primary stats large - Secondary stats smaller, grouped - Use visual weight (size, color) to indicate importance 3. **Eliminate Dead Zones** - Compact filters into sidebar or collapsible section - Use space for content, not empty cards 4. **Spacing System** - Define: `--spacing-xs: 0.25rem` through `--spacing-xxl: 4rem` - Use consistently across all components - Document in design system --- ### 2. INTERACTION CLARITY #### πŸ”΄ **Critical Failures** 1. **Ambiguous Actions** ```tsx // LibraryPage.tsx:266-285 // But also: ``` **Problem**: Two upload buttons, different behaviors. User confused which to use. 2. **Hidden Affordances** ```tsx // LibraryPage.tsx:413-470 // Track cards are clickable // But no visual indication until hover ``` **Issue**: Cards look static. User doesn't know they're interactive. 3. **No Feedback** ```tsx // LibraryPage.tsx:137-145 const handleAddToPlaylist = async (...) => { await addTrackToPlaylistMutation.mutateAsync(...); toast.success('Piste ajoutΓ©e'); } ``` **Problem**: Button doesn't show loading state. User clicks multiple times. 4. **Unclear States** ```tsx // LibraryPage.tsx:78-79 const [isBulkMode, setIsBulkMode] = useState(false); // But UI doesn't clearly show bulk mode is active ``` **Issue**: Bulk mode toggles but visual change is subtle. User doesn't realize mode changed. #### πŸ“‹ **Recommendations** 1. **Single Action Path** - One "Upload" button, one behavior - Remove duplicates - Use consistent labels 2. **Visual Affordances** - Add hover states to all interactive elements - Use cursor: pointer for clickable items - Add subtle shadows/borders to indicate interactivity 3. **Loading States** ```tsx ``` 4. **Mode Indicators** - Show banner when bulk mode active: "Bulk selection mode" - Highlight selected items clearly - Show count: "3 items selected" --- ### 3. CONSISTENCY #### πŸ”΄ **Critical Failures** 1. **Typography Chaos** ```tsx // DashboardPage.tsx:101

...

// LibraryPage.tsx:234

...

``` **Problem**: Same semantic level (h1), different sizes. No type scale. 2. **Color Inconsistency** ```tsx // Some use: text-kodo-cyan // Others use: text-kodo-cyan-dim // Others use: text-cyan-500 (Tailwind default) ``` **Issue**: Mix of design system colors and Tailwind defaults. No color logic. 3. **Component Drift** ```tsx // button.tsx has 9 variants // But components create custom buttons: ``` **Problem**: Design system components exist but not used consistently. 4. **Spacing Inconsistency** - Cards use `p-6` in some places, `p-4` in others - No padding system #### πŸ“‹ **Recommendations** 1. **Type Scale** ```css --text-xs: 0.75rem; --text-sm: 0.875rem; --text-base: 1rem; --text-lg: 1.125rem; --text-xl: 1.25rem; --text-2xl: 1.5rem; --text-3xl: 1.875rem; --text-4xl: 2.25rem; ``` - Use consistently for h1-h6, p, etc. 2. **Color System** - Document: When to use `kodo-cyan` vs `kodo-cyan-dim` - Remove Tailwind defaults, use design system only - Create color usage guide 3. **Component Library** - Audit all custom buttons β†’ replace with `