# Plan d'implémentation v0.801 — UX/UI Polish, Accessibilité & PWA ## État des lieux | Feature | État | Détail | |---------|------|--------| | Thème clair/sombre/auto | ✅ | ThemeProvider, toggle Settings | | Contraste élevé | ❌ | Non implémenté | | Compact/confortable | ❌ | Layout unique | | Accent color | ❌ | Primary fixe | | Navigation clavier | ⚠️ | Partiel | | ARIA labels | ⚠️ | Manquants sur boutons icônes | | Focus visible | ⚠️ | Outline basique | | Font size ajustable | ❌ | Tailles fixes | | prefers-reduced-motion | ❌ | Animations non conditionnées | | PWA installable | ⚠️ | SW basique, manifest incomplet | | Offline | ❌ | Pas de caching | | Background playback | ❌ | Audio s'arrête en background | --- ## Fichiers existants clés - Design tokens : [`index.css`](apps/web/src/index.css) (variables CSS, Tailwind layers) - ThemeProvider : existant dans `apps/web/src/` - Settings : [`SettingsAppearance.tsx`](apps/web/src/components/settings/SettingsAppearance.tsx) - Player hooks : [`apps/web/src/features/player/hooks/`](apps/web/src/features/player/hooks/) - UI components : [`apps/web/src/components/ui/`](apps/web/src/components/ui/) - Manifest : [`apps/web/public/manifest.json`](apps/web/public/manifest.json) - Storybook config : [`apps/web/.storybook/`](apps/web/.storybook/) --- ## Step 1 : Migration + User preferences backend (UX1-04, UX1-05) **Fichier** : `veza-backend-api/migrations/118_users_preferences.sql` (nouveau) ```sql ALTER TABLE users ADD COLUMN IF NOT EXISTS preferences JSONB DEFAULT '{}'; ``` **Fichier** : `veza-backend-api/internal/models/user.go` — ajouter : ```go Preferences datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"preferences,omitempty"` ``` **Fichier** : `veza-backend-api/internal/handlers/user_handler.go` — ajouter `PUT /users/me/preferences` : ```go func (h *UserHandler) UpdatePreferences(c *gin.Context) { userID, ok := GetUserIDUUID(c) if !ok { return } var prefs map[string]interface{} if err := c.ShouldBindJSON(&prefs); err != nil { RespondWithAppError(c, apperrors.NewValidationError("invalid preferences")) return } // update user.preferences in DB // ... RespondSuccess(c, http.StatusOK, gin.H{"preferences": prefs}) } ``` **Commit** : `feat(users): add preferences JSONB column and PUT endpoint` --- ## Step 2 : Design tokens — contraste élevé + compact (UX1-01, UX1-02) **Fichier** : `apps/web/src/index.css` — ajouter après les thèmes existants : ```css /* High contrast mode */ [data-contrast="high"] { --color-foreground: #000000; --color-background: #ffffff; --color-muted: #4a4a4a; --color-muted-foreground: #2a2a2a; --color-border: #000000; } .dark[data-contrast="high"] { --color-foreground: #ffffff; --color-background: #000000; --color-muted: #cccccc; --color-muted-foreground: #e0e0e0; --color-border: #ffffff; } /* Density modes */ [data-density="compact"] { font-size: 13px; } [data-density="comfortable"] { font-size: 15px; } /* Reduced motion */ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } } ``` **Commit** : `feat(ui): add high contrast, compact density, reduced motion CSS tokens` --- ## Step 3 : Settings UI — appearance controls (UX1-01 to UX1-04) **Fichier** : `apps/web/src/components/settings/SettingsAppearance.tsx` — ajouter : - Toggle "High Contrast" (data-contrast="high" sur document.documentElement) - Radio "Compact / Default / Comfortable" (data-density) - Color picker pour accent hue (CSS variable --color-primary) - Slider "Font Size" (14px–20px) - Persistence dans localStorage + sync PUT /users/me/preferences **Commit** : `feat(settings): add high contrast toggle, density, accent color, font size` --- ## Step 4 : Accessibilité — ARIA, clavier, focus (UX2-01 to UX2-07) **Fichier** : `apps/web/src/components/ui/` — pour chaque composant : - `Button.tsx` : aria-label sur boutons icônes (pas de texte visible) - `Dialog.tsx` / `Modal.tsx` : aria-modal, focus trap, Escape ferme - `Input.tsx` : aria-describedby pour messages erreur - `Tabs.tsx` : role="tablist", role="tab", aria-selected - `Menu.tsx` / `Dropdown.tsx` : role="menu", Arrow key navigation - `Table.tsx` : role="table", scope="col" sur headers - `Pagination.tsx` : aria-label="Pagination", aria-current="page" **Fichier** : `apps/web/src/index.css` — focus visible amélioré : ```css :focus-visible { outline: 2px solid var(--color-primary); outline-offset: 2px; } ``` **Fichier** : `apps/web/src/components/layout/` — skip-to-content : ```html Skip to content ``` **Fichier** : `apps/web/src/hooks/useReducedMotion.ts` (nouveau) : ```typescript export function useReducedMotion(): boolean { const [reduced, setReduced] = useState( () => window.matchMedia('(prefers-reduced-motion: reduce)').matches ); useEffect(() => { const mq = window.matchMedia('(prefers-reduced-motion: reduce)'); const handler = (e: MediaQueryListEvent) => setReduced(e.matches); mq.addEventListener('change', handler); return () => mq.removeEventListener('change', handler); }, []); return reduced; } ``` **Commit** : `feat(a11y): ARIA labels, keyboard nav, focus visible, skip-to-content, reduced motion` --- ## Step 5 : axe-core Storybook (UX2-08) ```bash cd apps/web && npm install --save-dev @storybook/addon-a11y ``` **Fichier** : `apps/web/.storybook/main.ts` — ajouter `'@storybook/addon-a11y'` dans addons **Commit** : `feat(storybook): add addon-a11y for automated accessibility testing` --- ## Step 6 : PWA — manifest, service worker, install prompt (UX3-01 to UX3-03) **Fichier** : `apps/web/public/manifest.json` — compléter : ```json { "name": "Veza — Audio Collaborative Platform", "short_name": "Veza", "description": "Collaborative audio platform for musicians", "start_url": "/", "display": "standalone", "theme_color": "#0f172a", "background_color": "#0f172a", "icons": [ { "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png" }, { "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png" }, { "src": "/icons/icon-maskable.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" } ], "shortcuts": [ { "name": "Dashboard", "url": "/dashboard" }, { "name": "Marketplace", "url": "/marketplace" }, { "name": "Go Live", "url": "/live/go-live" } ] } ``` **Fichier** : `apps/web/src/hooks/usePWAInstall.ts` (nouveau) ```typescript export function usePWAInstall() { const [prompt, setPrompt] = useState(null); const [isInstalled, setIsInstalled] = useState(false); useEffect(() => { const handler = (e: Event) => { e.preventDefault(); setPrompt(e as BeforeInstallPromptEvent); }; window.addEventListener('beforeinstallprompt', handler); window.addEventListener('appinstalled', () => setIsInstalled(true)); return () => window.removeEventListener('beforeinstallprompt', handler); }, []); const install = async () => { if (prompt) { await prompt.prompt(); } }; return { canInstall: !!prompt && !isInstalled, install, isInstalled }; } ``` **Commit** : `feat(pwa): complete manifest, install prompt hook, offline caching` --- ## Step 7 : Background playback mobile (UX3-04) **Fichier** : `apps/web/src/features/player/hooks/useWakeLock.ts` (nouveau) ```typescript export function useWakeLock(isPlaying: boolean) { const lockRef = useRef(null); useEffect(() => { if (!('wakeLock' in navigator)) return; if (isPlaying) { navigator.wakeLock.request('screen').then(l => { lockRef.current = l; }).catch(() => {}); } else { lockRef.current?.release(); lockRef.current = null; } return () => { lockRef.current?.release(); }; }, [isPlaying]); } ``` **Commit** : `feat(player): add WakeLock for background playback on mobile` --- ## Step 8 : Documentation + release **Fichier** : `CHANGELOG.md` — section v0.801 **Fichier** : `docs/PROJECT_STATE.md` — Dernier tag → v0.801, section v0.801 **Fichier** : `docs/FEATURE_STATUS.md` — section "Livré en v0.801" **Commit** : `docs: update CHANGELOG, PROJECT_STATE, FEATURE_STATUS for v0.801` --- ## Step 9 : Rétrospective + archivage + tag **Fichier** : `docs/RETROSPECTIVE_V0801.md` (nouveau) **Fichier** : `docs/V0_802_RELEASE_SCOPE.md` (placeholder) **Fichier** : `docs/SCOPE_CONTROL.md` — mise à jour **Fichier** : `.cursorrules` — scope v0.802 ```bash mv docs/V0_801_RELEASE_SCOPE.md docs/archive/ git add . && git commit -m "chore(docs): archive V0_801_RELEASE_SCOPE" git tag v0.801 ``` --- ## Validation finale ```bash cd veza-backend-api && go build ./... && go test ./... -v cd apps/web && npm run build # Lighthouse audit: PWA score, a11y score git tag v0.801 ```