First-attempt commit3a5c6e184only captured the .gitignore change; the pre-commit hook silently dropped the 343 staged moves/deletes during lint-staged's "no matching task" path. This commit re-applies the intended J1 content on top ofbec75f143(which was pushed in parallel). Uses --no-verify because: - J1 only touches .md/.json/.log/.png/binaries — zero code that would benefit from lint-staged, typecheck, or vitest - The hook demonstrated it corrupts pure-rename commits in this repo - Explicitly authorized by user for this one commit Changes (343 total: 169 deletions + 174 renames): Binaries purged (~167 MB): - veza-backend-api/{server,modern-server,encrypt_oauth_tokens,seed,seed-v2} Generated reports purged: - 9 apps/web/lint_report*.json (~32 MB) - 8 apps/web/tsc_*.{log,txt} + ts_*.log (TS error snapshots) - 3 apps/web/storybook_*.json (1375+ stored errors) - apps/web/{build_errors*,build_output,final_errors}.txt - 70 veza-backend-api/coverage*.out + coverage_groups/ (~4 MB) - 3 veza-backend-api/internal/handlers/*.bak Root cleanup: - 54 audit-*.png (visual regression baselines, ~11 MB) - 9 stale MVP-era scripts (Jan 27, hardcoded v0.101): start_{iteration,mvp,recovery}.sh, test_{mvp_endpoints,protected_endpoints,user_journey}.sh, validate_v0101.sh, verify_logs_setup.sh, gen_hash.py Session docs archived (not deleted — preserved under docs/archive/): - 78 apps/web/*.md → docs/archive/frontend-sessions-2026/ - 43 veza-backend-api/*.md → docs/archive/backend-sessions-2026/ - 53 docs/{RETROSPECTIVE_V,SMOKE_TEST_V,PLAN_V0_,V0_*_RELEASE_SCOPE, AUDIT_,PLAN_ACTION_AUDIT,REMEDIATION_PROGRESS}*.md → docs/archive/v0-history/ README.md and CONTRIBUTING.md preserved in apps/web/ and veza-backend-api/. Note: The .gitignore rules preventing recurrence were already pushed in3a5c6e184and remain in place — this commit does not modify .gitignore. Refs: AUDIT_REPORT.md §11
8.9 KiB
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(variables CSS, Tailwind layers) - ThemeProvider : existant dans
apps/web/src/ - Settings :
SettingsAppearance.tsx - Player hooks :
apps/web/src/features/player/hooks/ - UI components :
apps/web/src/components/ui/ - Manifest :
apps/web/public/manifest.json - Storybook config :
apps/web/.storybook/
Step 1 : Migration + User preferences backend (UX1-04, UX1-05)
Fichier : veza-backend-api/migrations/118_users_preferences.sql (nouveau)
ALTER TABLE users ADD COLUMN IF NOT EXISTS preferences JSONB DEFAULT '{}';
Fichier : veza-backend-api/internal/models/user.go — ajouter :
Preferences datatypes.JSON `gorm:"type:jsonb;default:'{}'" json:"preferences,omitempty"`
Fichier : veza-backend-api/internal/handlers/user_handler.go — ajouter PUT /users/me/preferences :
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 :
/* 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 fermeInput.tsx: aria-describedby pour messages erreurTabs.tsx: role="tablist", role="tab", aria-selectedMenu.tsx/Dropdown.tsx: role="menu", Arrow key navigationTable.tsx: role="table", scope="col" sur headersPagination.tsx: aria-label="Pagination", aria-current="page"
Fichier : apps/web/src/index.css — focus visible amélioré :
:focus-visible {
outline: 2px solid var(--color-primary);
outline-offset: 2px;
}
Fichier : apps/web/src/components/layout/ — skip-to-content :
<a href="#main-content" className="sr-only focus:not-sr-only ...">Skip to content</a>
Fichier : apps/web/src/hooks/useReducedMotion.ts (nouveau) :
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)
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 :
{
"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)
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)
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
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
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