- Completed Action 1.1.2.1: Installed @openapitools/openapi-generator-cli - Completed Action 1.1.2.2: Created generate-types.sh script - Added swagger annotations to cmd/modern-server/main.go - Regenerated swagger.yaml with proper info section - Successfully generated TypeScript types to src/types/generated/ The script generates types from veza-backend-api/openapi.yaml using typescript-axios generator and creates barrel exports.
1016 lines
28 KiB
Markdown
1016 lines
28 KiB
Markdown
# 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:
|
|
<div className="text-center text-kodo-red">Erreur...</div>
|
|
|
|
// Some use both:
|
|
toast.error(...) && <ErrorMessage />
|
|
```
|
|
**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
|
|
<ErrorDisplay
|
|
error={apiError}
|
|
onRetry={() => 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
|
|
<h1>Bienvenue, <span className="text-kodo-cyan">username</span></h1>
|
|
<p className="text-kodo-secondary">Voici un aperçu...</p>
|
|
```
|
|
**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
|
|
<Button onClick={handleOpenUpload}>Upload</Button>
|
|
// But also:
|
|
<Button onClick={() => navigate('/library?action=upload')}>Upload Track</Button>
|
|
```
|
|
**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
|
|
<Button disabled={isLoading} onClick={handleAdd}>
|
|
{isLoading ? <Spinner /> : 'Add to Playlist'}
|
|
</Button>
|
|
```
|
|
|
|
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
|
|
<h1 className="text-4xl font-bold">...</h1>
|
|
|
|
// LibraryPage.tsx:234
|
|
<h1 className="text-3xl font-bold">...</h1>
|
|
```
|
|
**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:
|
|
<button className="custom-styles">...</button>
|
|
```
|
|
**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 `<Button>`
|
|
- Create component usage guide
|
|
- Enforce via ESLint rules
|
|
|
|
4. **Spacing System**
|
|
- Define padding scale (like type scale)
|
|
- Use consistently: Cards = `p-6`, Forms = `p-4`, etc.
|
|
|
|
---
|
|
|
|
### 4. COGNITIVE LOAD
|
|
|
|
#### 🔴 **Critical Failures**
|
|
|
|
1. **Information Overload**
|
|
```tsx
|
|
// DashboardPage.tsx:166-254
|
|
// Shows: Stats, chart, activity feed, quick actions, system status
|
|
// All on one page
|
|
```
|
|
**Problem**: Too much information at once. User overwhelmed.
|
|
|
|
2. **Unnecessary Choices**
|
|
```tsx
|
|
// LibraryPage.tsx:286-309
|
|
// View mode toggle (grid/list)
|
|
// But default is grid, list rarely used
|
|
```
|
|
**Issue**: Feature exists but adds cognitive load. Is it needed?
|
|
|
|
3. **Complex Filters**
|
|
```tsx
|
|
// LibraryPage.tsx:317-383
|
|
// Search, genre filter, format filter, sort dropdown
|
|
// All visible at once
|
|
```
|
|
**Problem**: 4 filter controls. User must understand all to use effectively.
|
|
|
|
4. **No Progressive Disclosure**
|
|
- All features visible at once
|
|
- No "advanced" sections
|
|
- No onboarding/tooltips
|
|
|
|
#### 📋 **Recommendations**
|
|
|
|
1. **Reduce Information**
|
|
- Dashboard: Show 2-3 key metrics, hide rest behind "View All"
|
|
- Use tabs or accordions for secondary info
|
|
- Progressive disclosure: Show more on demand
|
|
|
|
2. **Remove Unnecessary Features**
|
|
- If list view rarely used, remove it
|
|
- Or: Make it discoverable but not prominent
|
|
- Audit: What features are actually used?
|
|
|
|
3. **Simplify Filters**
|
|
- Combine: Single search bar with filters hidden behind "Advanced"
|
|
- Or: Use faceted search (filters appear as you type)
|
|
|
|
4. **Progressive Disclosure**
|
|
- Hide advanced features by default
|
|
- Show tooltips on first use
|
|
- Add onboarding for new users
|
|
|
|
---
|
|
|
|
### 5. AESTHETIC DIAGNOSIS
|
|
|
|
#### 🔴 **Why It Feels "Ugly"**
|
|
|
|
1. **Lack of Contrast**
|
|
```css
|
|
/* index.css:65-70 */
|
|
--kodo-text-main: 243 243 224; /* #F3F3E0 */
|
|
--kodo-content-dim: 156 163 175; /* Gray-400 */
|
|
```
|
|
**Problem**: Text colors too similar. Low contrast = hard to read.
|
|
|
|
2. **No Visual Rhythm**
|
|
- Cards randomly sized
|
|
- No grid system
|
|
- Inconsistent gaps
|
|
|
|
3. **Color Overuse**
|
|
```tsx
|
|
// Every accent uses kodo-cyan
|
|
// Stats, buttons, links, borders all cyan
|
|
```
|
|
**Issue**: Too much cyan. No color hierarchy. Feels "neon overload".
|
|
|
|
4. **Lack of Restraint**
|
|
- Every element has hover effects
|
|
- Every card has gradients
|
|
- Every button glows
|
|
```
|
|
**Problem**: Visual noise. No quiet moments.
|
|
|
|
5. **Incoherent Style**
|
|
- Mix of "cyber" (neon glows) and "minimal" (clean cards)
|
|
- No clear design direction
|
|
|
|
#### 📋 **Recommendations**
|
|
|
|
1. **Increase Contrast**
|
|
- Text: Use `--kodo-content-highlight` (white) for primary text
|
|
- Dim text: Use `--kodo-content-dim` sparingly
|
|
- WCAG AA compliance: 4.5:1 contrast ratio minimum
|
|
|
|
2. **Establish Rhythm**
|
|
- Use 8px grid system
|
|
- Align all elements to grid
|
|
- Consistent spacing multiples (8, 16, 24, 32px)
|
|
|
|
3. **Color Hierarchy**
|
|
- Primary actions: `kodo-cyan`
|
|
- Secondary actions: `kodo-steel` (muted)
|
|
- Accents: Use sparingly (success = lime, error = red)
|
|
- 80% neutral, 20% color
|
|
|
|
4. **Visual Restraint**
|
|
- Remove unnecessary hover effects
|
|
- Use gradients sparingly (hero sections only)
|
|
- Let content breathe (more whitespace)
|
|
|
|
5. **Define Style Direction**
|
|
- Choose: "Minimal Cyber" (clean + neon accents) OR "Dense Dashboard" (information-rich)
|
|
- Apply consistently
|
|
- Document in design system
|
|
|
|
---
|
|
|
|
## PART 3 — RADICAL IMPROVEMENT PLAN
|
|
|
|
### 1. DESIGN DIRECTION
|
|
|
|
**Proposed Direction**: **"Surgical Minimalism"**
|
|
|
|
- **Adjectives**: Calm, precise, authoritative, focused
|
|
- **Feel**: Professional audio tool, not gaming UI
|
|
- **Inspiration**: Linear, Vercel, Stripe Dashboard
|
|
|
|
**Core Principles**:
|
|
1. **Content First**: UI gets out of the way
|
|
2. **Surgical Precision**: Every element has purpose
|
|
3. **Calm Authority**: Confident, not flashy
|
|
4. **Focused Density**: Information-rich but organized
|
|
|
|
---
|
|
|
|
### 2. CONCRETE UI IMPROVEMENTS
|
|
|
|
#### **Layout Restructuring**
|
|
|
|
1. **Dashboard**
|
|
```
|
|
BEFORE: Grid of 4 equal stat cards + activity feed + sidebar
|
|
AFTER:
|
|
- Hero: 1 large primary stat (tracks played) + trend
|
|
- Secondary: 3 compact stats in row
|
|
- Activity: Collapsible section (collapsed by default)
|
|
- Actions: Floating action button (FAB) for Upload
|
|
```
|
|
|
|
2. **Library Page**
|
|
```
|
|
BEFORE: Full-width filters + grid of tracks
|
|
AFTER:
|
|
- Sidebar: Filters (collapsible)
|
|
- Main: Tracks grid (more compact cards)
|
|
- Header: Search bar + view toggle (minimal)
|
|
```
|
|
|
|
3. **Typography Scale**
|
|
```css
|
|
/* Define scale */
|
|
--text-xs: 0.75rem; /* 12px - Captions */
|
|
--text-sm: 0.875rem; /* 14px - Secondary text */
|
|
--text-base: 1rem; /* 16px - Body */
|
|
--text-lg: 1.125rem; /* 18px - Emphasized */
|
|
--text-xl: 1.25rem; /* 20px - Small headings */
|
|
--text-2xl: 1.5rem; /* 24px - Section headings */
|
|
--text-3xl: 1.875rem; /* 30px - Page titles */
|
|
--text-4xl: 2.25rem; /* 36px - Hero (rare) */
|
|
```
|
|
|
|
4. **Color Palette Logic**
|
|
```css
|
|
/* Primary Actions */
|
|
--color-primary: kodo-cyan;
|
|
|
|
/* Secondary Actions */
|
|
--color-secondary: kodo-steel;
|
|
|
|
/* Success */
|
|
--color-success: kodo-lime;
|
|
|
|
/* Error */
|
|
--color-error: kodo-red;
|
|
|
|
/* Neutral Text */
|
|
--color-text-primary: white;
|
|
--color-text-secondary: kodo-content-dim;
|
|
|
|
/* Usage: 80% neutral, 20% color */
|
|
```
|
|
|
|
5. **Component Redesigns**
|
|
|
|
**Button**
|
|
```tsx
|
|
// BEFORE: 9 variants, all flashy
|
|
// AFTER: 3 variants
|
|
- Primary: Solid cyan, minimal glow
|
|
- Secondary: Outline, no glow
|
|
- Ghost: Text only, hover background
|
|
```
|
|
|
|
**Card**
|
|
```tsx
|
|
// BEFORE: Heavy gradients, multiple borders
|
|
// AFTER: Clean border, subtle shadow
|
|
- Remove gradients
|
|
- Single border: `border-white/5`
|
|
- Shadow: `shadow-lg` (not `shadow-xl`)
|
|
```
|
|
|
|
**Input**
|
|
```tsx
|
|
// BEFORE: Complex styling
|
|
// AFTER: Minimal, focused
|
|
- Clean border
|
|
- Focus: Cyan border (not glow)
|
|
- Remove unnecessary decorations
|
|
```
|
|
|
|
---
|
|
|
|
### 3. QUICK WINS (1-2 Days)
|
|
|
|
#### **Day 1: Typography & Spacing**
|
|
|
|
1. **Define Type Scale** (2 hours)
|
|
- Add to `design-tokens.css`
|
|
- Replace all `text-*` classes with scale
|
|
- Update h1-h6, p, span
|
|
|
|
2. **Define Spacing System** (2 hours)
|
|
- Add spacing scale to tokens
|
|
- Replace all `p-*`, `m-*`, `gap-*` with scale
|
|
- Document usage
|
|
|
|
3. **Increase Text Contrast** (1 hour)
|
|
- Change primary text to white
|
|
- Update dim text color
|
|
- Test WCAG compliance
|
|
|
|
#### **Day 2: Component Cleanup**
|
|
|
|
1. **Simplify Buttons** (3 hours)
|
|
- Reduce to 3 variants
|
|
- Remove excessive glows
|
|
- Update all button usage
|
|
|
|
2. **Clean Cards** (2 hours)
|
|
- Remove gradients
|
|
- Simplify borders
|
|
- Update card components
|
|
|
|
3. **Fix Dashboard Hierarchy** (3 hours)
|
|
- Make primary stat large
|
|
- Reduce secondary stats
|
|
- Collapse activity feed
|
|
|
|
**Total**: ~13 hours (1.5 days)
|
|
|
|
---
|
|
|
|
### 4. STRUCTURAL REDESIGN (Longer Term)
|
|
|
|
#### **Phase 1: API Layer (1 week)**
|
|
|
|
1. **Generate Types from OpenAPI**
|
|
- Set up code generation
|
|
- Replace manual types
|
|
- Add validation
|
|
|
|
2. **Unify Response Handling**
|
|
- Standardize all endpoints to wrapped format
|
|
- Remove dual-format handling
|
|
- Add runtime validation
|
|
|
|
3. **Create API Abstraction**
|
|
- Build service layer
|
|
- Components use services, not direct API calls
|
|
- Easier to change backend
|
|
|
|
#### **Phase 2: State Management (1 week)**
|
|
|
|
1. **Migrate to React Query Only**
|
|
- Remove domain data from Zustand
|
|
- Keep only UI state in Zustand
|
|
- Unify cache invalidation
|
|
|
|
2. **Add Request Debouncing**
|
|
- Debounce search inputs
|
|
- Optimize filter requests
|
|
- Reduce server load
|
|
|
|
3. **Implement Virtualization**
|
|
- Add `react-window` for long lists
|
|
- Infinite scroll for tracks
|
|
- Performance improvement
|
|
|
|
#### **Phase 3: UI Redesign (2 weeks)**
|
|
|
|
1. **Design System Overhaul**
|
|
- Create component library
|
|
- Document usage
|
|
- Enforce via linting
|
|
|
|
2. **Layout Restructuring**
|
|
- Redesign dashboard
|
|
- Redesign library page
|
|
- Add sidebar navigation
|
|
|
|
3. **Visual Polish**
|
|
- Apply color hierarchy
|
|
- Establish rhythm
|
|
- Remove visual noise
|
|
|
|
#### **Phase 4: Error Handling (3 days)**
|
|
|
|
1. **Standardize Error Display**
|
|
- Create `<ErrorDisplay>` component
|
|
- Replace all error handling
|
|
- Add retry logic
|
|
|
|
2. **Improve Error Messages**
|
|
- Context-aware messages
|
|
- Actionable feedback
|
|
- Support integration
|
|
|
|
---
|
|
|
|
## FINAL CONSTRAINTS ADHERENCE
|
|
|
|
✅ **Brutally Honest**: Identified 20+ critical issues
|
|
✅ **No Generic Advice**: Specific file references, line numbers, code examples
|
|
✅ **No Marketing Language**: Technical, direct, actionable
|
|
✅ **Prioritized**: Quick wins vs structural changes clearly separated
|
|
✅ **Clarity**: Each issue explained with impact and solution
|
|
|
|
---
|
|
|
|
## SUMMARY
|
|
|
|
**Frontend/Backend Integration**: 6/10
|
|
- Good: Centralized client, error parsing, state separation
|
|
- Bad: Type duplication, response inconsistency, state sync issues
|
|
|
|
**UI/UX Design**: 4/10
|
|
- Good: Design system exists, components reusable
|
|
- Bad: No hierarchy, inconsistent, cognitive overload, aesthetic issues
|
|
|
|
**Overall**: **5/10** - Functional but needs significant improvement
|
|
|
|
**Priority Actions**:
|
|
1. Fix type duplication (generate from OpenAPI)
|
|
2. Unify state management (React Query only)
|
|
3. Establish visual hierarchy (typography, spacing)
|
|
4. Simplify UI (remove noise, increase contrast)
|
|
5. Standardize error handling
|
|
|
|
**Estimated Effort**:
|
|
- Quick wins: 1.5 days
|
|
- Structural fixes: 3-4 weeks
|
|
- Full redesign: 2-3 months
|