veza/docs/PLAN_V0_801_IMPLEMENTATION.md

295 lines
8.9 KiB
Markdown
Raw Normal View History

# 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" (14px20px)
- 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
<a href="#main-content" className="sr-only focus:not-sr-only ...">Skip to content</a>
```
**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<BeforeInstallPromptEvent | null>(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<WakeLockSentinel | null>(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
```