feat(web): update all features, stories, e2e tests, and auth interceptor
Update auth, playlists, tracks, search, profile, dashboard, player,
settings, and social features. Add e2e audit specs for all major pages.
Update ESLint config, vitest config, and route configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:16:36 +00:00
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
import storybook from "eslint-plugin-storybook" ;
2026-02-19 15:08:05 +00:00
// eslint-plugin-storybook optional: install if needed for Storybook-specific lint rules
// import storybook from "eslint-plugin-storybook";
2026-02-02 18:34:14 +00:00
2025-12-03 21:56:50 +00:00
import js from '@eslint/js' ;
import typescript from '@typescript-eslint/eslint-plugin' ;
import typescriptParser from '@typescript-eslint/parser' ;
import react from 'eslint-plugin-react' ;
import reactHooks from 'eslint-plugin-react-hooks' ;
import reactRefresh from 'eslint-plugin-react-refresh' ;
import jsxA11y from 'eslint-plugin-jsx-a11y' ;
2026-02-02 18:34:14 +00:00
export default [ js . configs . recommended , {
files : [ '**/*.{ts,tsx,js,jsx}' ] ,
languageOptions : {
parser : typescriptParser ,
parserOptions : {
ecmaFeatures : {
jsx : true ,
2025-12-03 21:56:50 +00:00
} ,
2026-02-02 18:34:14 +00:00
ecmaVersion : 2022 ,
sourceType : 'module' ,
2025-12-03 21:56:50 +00:00
} ,
2026-02-02 18:34:14 +00:00
globals : {
// Browser globals
window : 'readonly' ,
document : 'readonly' ,
localStorage : 'readonly' ,
sessionStorage : 'readonly' ,
console : 'readonly' ,
setTimeout : 'readonly' ,
clearTimeout : 'readonly' ,
setInterval : 'readonly' ,
clearInterval : 'readonly' ,
fetch : 'readonly' ,
WebSocket : 'readonly' ,
File : 'readonly' ,
FormData : 'readonly' ,
CustomEvent : 'readonly' ,
Event : 'readonly' ,
CloseEvent : 'readonly' ,
MessageEvent : 'readonly' ,
KeyboardEvent : 'readonly' ,
HTMLElement : 'readonly' ,
HTMLDivElement : 'readonly' ,
HTMLInputElement : 'readonly' ,
HTMLButtonElement : 'readonly' ,
HTMLAnchorElement : 'readonly' ,
HTMLParagraphElement : 'readonly' ,
HTMLHeadingElement : 'readonly' ,
HTMLTextAreaElement : 'readonly' ,
HTMLSelectElement : 'readonly' ,
HTMLImageElement : 'readonly' ,
HTMLAudioElement : 'readonly' ,
Element : 'readonly' ,
Node : 'readonly' ,
MouseEvent : 'readonly' ,
Blob : 'readonly' ,
FileReader : 'readonly' ,
Image : 'readonly' ,
global : 'readonly' ,
NodeJS : 'readonly' ,
Buffer : 'readonly' ,
crypto : 'readonly' ,
performance : 'readonly' ,
require : 'readonly' ,
process : 'readonly' ,
// URL API globals
URL : 'readonly' ,
URLSearchParams : 'readonly' ,
// DOM API globals
DOMRect : 'readonly' ,
DOMRectReadOnly : 'readonly' ,
Headers : 'readonly' ,
navigator : 'readonly' ,
WindowEventMap : 'readonly' ,
requestAnimationFrame : 'readonly' ,
cancelAnimationFrame : 'readonly' ,
Notification : 'readonly' ,
NotificationOptions : 'readonly' ,
NotificationPermission : 'readonly' ,
IntersectionObserver : 'readonly' ,
IntersectionObserverInit : 'readonly' ,
MessageChannel : 'readonly' ,
confirm : 'readonly' ,
2026-02-19 15:27:10 +00:00
alert : 'readonly' ,
// Web API globals
AudioContext : 'readonly' ,
AnalyserNode : 'readonly' ,
MediaElementAudioSourceNode : 'readonly' ,
HTMLIFrameElement : 'readonly' ,
HTMLMediaElement : 'readonly' ,
XMLHttpRequest : 'readonly' ,
BinaryType : 'readonly' ,
EventListenerOrEventListenerObject : 'readonly' ,
ReadableStream : 'readonly' ,
BeforeUnloadEvent : 'readonly' ,
CryptoKey : 'readonly' ,
btoa : 'readonly' ,
atob : 'readonly' ,
TextEncoder : 'readonly' ,
TextDecoder : 'readonly' ,
CanvasRenderingContext2D : 'readonly' ,
MutationObserver : 'readonly' ,
Window : 'readonly' ,
Storage : 'readonly' ,
TransformStream : 'readonly' ,
2026-02-02 18:34:14 +00:00
// Service Worker globals
self : 'readonly' ,
caches : 'readonly' ,
ServiceWorkerRegistration : 'readonly' ,
Cache : 'readonly' ,
CacheStorage : 'readonly' ,
Response : 'readonly' ,
Request : 'readonly' ,
clients : 'readonly' ,
// React globals
React : 'readonly' ,
// Test globals
beforeAll : 'readonly' ,
afterAll : 'readonly' ,
afterEach : 'readonly' ,
beforeEach : 'readonly' ,
describe : 'readonly' ,
it : 'readonly' ,
test : 'readonly' ,
expect : 'readonly' ,
vi : 'readonly' ,
vitest : 'readonly' ,
waitFor : 'readonly' ,
jest : 'readonly' ,
AbortController : 'readonly' ,
AbortSignal : 'readonly' ,
BroadcastChannel : 'readonly' ,
DOMException : 'readonly' ,
atob : 'readonly' ,
PerformanceNavigationTiming : 'readonly' ,
PerformanceObserver : 'readonly' ,
HTMLFormElement : 'readonly' ,
HTMLTableElement : 'readonly' ,
HTMLTableSectionElement : 'readonly' ,
HTMLTableRowElement : 'readonly' ,
HTMLTableCellElement : 'readonly' ,
HTMLTableCaptionElement : 'readonly' ,
HTMLSpanElement : 'readonly' ,
HTMLCanvasElement : 'readonly' ,
HTMLLabelElement : 'readonly' ,
FileList : 'readonly' ,
MediaQueryListEvent : 'readonly' ,
IntersectionObserver : 'readonly' ,
IntersectionObserverEntry : 'readonly' ,
IntersectionObserverCallback : 'readonly' ,
ResizeObserver : 'readonly' ,
ResizeObserverEntry : 'readonly' ,
HeadersInit : 'readonly' ,
EventListener : 'readonly' ,
2025-12-03 21:56:50 +00:00
} ,
2026-02-02 18:34:14 +00:00
} ,
plugins : {
'@typescript-eslint' : typescript ,
'react' : react ,
'react-hooks' : reactHooks ,
'react-refresh' : reactRefresh ,
'jsx-a11y' : jsxA11y ,
} ,
rules : {
// TypeScript
2026-04-30 21:05:32 +00:00
'@typescript-eslint/no-unused-vars' : [
'warn' ,
{
// v1.0.10 dette tech : `_`-prefix is the convention to mark a
// declared name as intentionally unused. argsIgnorePattern was
// already set ; widening it to vars + caught errors aligns the
// rule across the three contexts so a `catch (_err)` or `const
// _foo = …` reads consistently. Names without the prefix still
// warn — this is opt-in suppression, not a backdoor.
argsIgnorePattern : '^_' ,
varsIgnorePattern : '^_' ,
caughtErrorsIgnorePattern : '^_' ,
destructuredArrayIgnorePattern : '^_' ,
} ,
] ,
2026-02-02 18:34:14 +00:00
'@typescript-eslint/explicit-function-return-type' : 'off' ,
'@typescript-eslint/explicit-module-boundary-types' : 'off' ,
2026-03-09 18:36:33 +00:00
'@typescript-eslint/no-explicit-any' : 'warn' ,
2026-02-02 18:34:14 +00:00
'@typescript-eslint/no-non-null-assertion' : 'warn' ,
2026-01-07 18:39:21 +00:00
2026-02-02 18:34:14 +00:00
// React
'react/react-in-jsx-scope' : 'off' ,
'react/prop-types' : 'off' ,
'react-hooks/rules-of-hooks' : 'error' ,
'react-hooks/exhaustive-deps' : 'warn' ,
'react-refresh/only-export-components' : [
'warn' ,
{ allowConstantExport : true }
] ,
2026-01-07 18:39:21 +00:00
2026-02-02 18:34:14 +00:00
// General
'no-console' : 'off' ,
'no-debugger' : 'error' ,
'prefer-const' : 'error' ,
'no-var' : 'error' ,
'object-shorthand' : 'error' ,
'prefer-template' : 'error' ,
'no-unused-vars' : 'off' , // Handled by @typescript-eslint/no-unused-vars
fix: stabilize builds, tests, and lint across all stacks
Complete stabilization pass bringing all 3 stacks to green:
Frontend (apps/web/):
- Fix TypeScript nullability in useSeason.ts, useTimeOfDay.ts hooks
- Disable no-undef in ESLint config (TypeScript handles it; JSX misidentified)
- Rename 306 story imports from @storybook/react to @storybook/react-vite
- Fix conditional hook call in useMediaQuery.ts useIsTablet
- Move useQuery to top of LoginPage.tsx component
- Remove useless try/catch in GearFormModal.tsx
- Fix stale closure in ResetPasswordPage.tsx handleChange
- Make Storybook decorators (withRouter, withQueryClient, withToast, withAudio)
no-ops since global StorybookDecorator already provides these — prevents
nested Router / duplicate provider crashes in vitest-browser
- Fix nested MemoryRouter in 3 page stories (TrackDetail, PlaylistDetail, UserProfile)
- Update i18n initialization in test setup (await init before changeLanguage)
- Update ~30 test assertions from English to French to match i18n translations
- Update test assertions to match SUMI V3 design changes (shadow vs border)
- Fix remaining story type errors (PlayerError, PlaylistBatchActions,
TrackFilters, VirtualizedChatMessages)
Backend (veza-backend-api/):
- Fix response_test.go RespondWithAppError signature (2 args, not 3)
- Fix TestErrorContractAuthEndpoints expected error codes
(ErrCodeUnauthorized vs ErrCodeInvalidCredentials)
- Fix TestTrackHandler_GetTrackLikes_Success missing auth middleware setup
- Fix TestPlaybackAnalyticsService_GetTrackStats k-anonymity threshold
(needs 5 unique users, not 1)
- Replace NOW() PostgreSQL function with time.Now() parameter in marketplace
service for SQLite test compatibility
- Add missing AutoMigrate entries in marketplace_test.go
(ProductImage, ProductPreview, ProductLicense, ProductReview)
Results:
- Frontend TypeCheck: 617 errors -> 0 errors
- Frontend ESLint: 349 errors -> 0 errors
- Frontend Vitest: 196 failing tests -> 1 skipped (3396/3397 passing)
- Backend go vet: 1 error -> 0 errors
- Backend tests: 5 failing -> all 13 packages passing
- Rust: 150/150 tests passing (unchanged)
- Storybook audit: 0 errors across 1244 stories
Triage report: docs/TRIAGE_REPORT.md
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 14:48:07 +00:00
'no-undef' : 'off' , // TypeScript handles this; no-undef doesn't understand TS types (JSX, etc.)
2026-02-02 18:34:14 +00:00
'no-useless-escape' : 'error' ,
'no-prototype-builtins' : 'warn' ,
2026-01-15 20:23:31 +00:00
2026-02-02 18:34:14 +00:00
// Typography: Enforce type scale usage
// Warn on arbitrary text sizes in className strings (e.g., text-[10px], text-[9px])
// Note: SVG chart text (text-[2px], text-[1.5px]) may need exceptions - review case by case
'no-restricted-syntax' : [
'warn' ,
{
selector :
"Literal[value=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/text-\\[\\d+(\\.\\d+)?(px|rem)\\]/]" ,
message :
'Use type scale classes (text-xs, text-sm, text-base, text-lg, text-xl, text-2xl, text-3xl, text-4xl) instead of arbitrary sizes. SVG chart text (text-[2px], text-[1.5px]) may be an exception - add eslint-disable comment if needed.' ,
} ,
// Spacing: Enforce spacing scale usage
// Warn on arbitrary spacing values that don't follow 4px base scale
// Valid scale values: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24
// Arbitrary values like gap-[7px], p-[9px], etc. should use scale values instead
{
selector :
"Literal[value=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/], TemplateElement[value.raw=/(gap-|p-|m-|px-|py-|mx-|my-|space-[xy]-)\\[\\d+(\\.\\d+)?(px|rem)\\]/]" ,
message :
'Use spacing scale classes (gap-0 through gap-24, p-0 through p-24, etc.) instead of arbitrary sizes. Valid scale values follow 4px base: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24. For exceptions, add eslint-disable comment.' ,
} ,
2026-02-12 01:15:11 +00:00
// Colors: Prevent Tailwind default colors (use SUMI design system semantic tokens instead)
2026-02-02 18:34:14 +00:00
// Warn on default Tailwind color classes: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose
2026-02-12 01:15:11 +00:00
// Allow: semantic tokens (primary, secondary, destructive, success, warning, muted, etc.) and sumi-* tokens
2026-02-02 18:34:14 +00:00
{
selector :
"Literal[value=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/], TemplateElement[value.raw=/(text-|bg-|border-|ring-|outline-|divide-|placeholder-|from-|via-|to-|accent-|caret-|fill-|stroke-)(slate|gray|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-(50|100|200|300|400|500|600|700|800|900|950)/]" ,
message :
2026-02-12 01:15:11 +00:00
'Use SUMI design system semantic tokens (primary, secondary, destructive, success, warning, muted, foreground, etc.) instead of Tailwind default colors. See apps/web/docs/DESIGN_TOKENS.md for token mapping. For exceptions (e.g., test files), add eslint-disable comment.' ,
2026-02-02 18:34:14 +00:00
} ,
2026-04-27 14:44:58 +00:00
// Hex colors: Prevent literal hex colors in JS/TS strings (use tokens instead).
// Matches strings like '#7c9dd6', '#fff', '#0d0d0fAA' — anywhere in code.
// Use var(--sumi-*) in CSS contexts (JSX style props, template literals)
// OR import { ColorXxx } from '@veza/design-system/tokens-generated' for canvas/runtime.
// Exceptions: rgba()/hsla() (not # prefix), the design-system package itself (different rule scope).
{
selector :
"Literal[value=/^#[0-9a-fA-F]{3,8}$/]" ,
message :
2026-04-30 21:05:32 +00:00
'Hardcoded hex color literal forbidden. Use SUMI tokens: var(--sumi-*) for CSS strings (JSX style/className), or import from \'@veza/design-system/tokens-generated\'.' ,
} ,
// Pixels/Rem: Prevent arbitrary pixel or rem values in JS/TS strings (use scale/tokens instead).
// Matches strings like '10px', '2.5rem' — anywhere in code that might be a style value.
// Exceptions: 0, 1px (borders), 50% (centered), 100%, 100vh, 100vw, auto.
{
selector :
"Literal[value=/^(\\d+(\\.\\d+)?(px|rem))$/][value!=/^(0|1px|0px)$/]" ,
message :
'Hardcoded pixel/rem value forbidden. Use Tailwind scale (w-4, p-2) or SUMI layout tokens: var(--sumi-layout-*).' ,
2026-04-27 14:44:58 +00:00
} ,
2026-02-02 18:34:14 +00:00
// Components: Enforce Button component usage (prevent native button elements)
// Warn on native <button> elements - use <Button> component from @/components/ui/button instead
{
selector : 'JSXOpeningElement[name.name="button"]' ,
message :
'Use the Button component from @/components/ui/button instead of native <button> elements. This ensures consistent styling, accessibility, and design system compliance. For exceptions (e.g., test files, third-party components), add eslint-disable comment.' ,
2025-12-03 21:56:50 +00:00
} ,
feat(web): UI premium Discord/Spotify-like — tokens, shadows, focus, layout
Plan UI premium 6–8 semaines (design system, shell, Storybook, a11y):
- Design system: DESIGN_TOKENS.md, APP_SHELL.md, FULL_LAYOUT_PAGE.md. Single source
for layout/shell (index.css), shadows (design-system.css), durations/easing.
- Tokens: shadow-cover-depth, shadow-gold-glow, shadow-fab-glow; layout max-height
(max-h-layout-drawer, max-h-layout-panel, max-h-layout-list). All duration-200/300/500
replaced by --duration-fast/normal/slow. Arbitrary shadows replaced by token classes.
- Shell & player: Sidebar, Header, GlobalPlayer, MiniPlayer, PlayerQueue, PlayerControls,
AudioPlayer use tokens; focus-visible on Sidebar, PlayerQueue, DropdownMenuTrigger/Item,
TabsTrigger. Typography: text-[10px]/[9px] → text-xs where applicable.
- ESLint: no-restricted-syntax (warn) for w-/h-/rounded-/shadow-/text-/spacing arbitrary.
- Scripts: report-arbitrary-values.mjs, capture/compare/generate visual; visual-complete.spec.ts.
- Stories full layout: Dashboard, Playlists, Library, Settings, Profile in DashboardLayout.stories.
- .cursorrules + README: DESIGN_TOKENS, APP_SHELL, visual commands, no arbitrary without justification.
- apps/web/.gitignore: e2e test artifacts (test-results-visual, playwright-report-visual).
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 16:15:58 +00:00
// Width: Avoid arbitrary width classes — use layout tokens or scale (w-4, max-w-layout-content, etc.)
{
selector :
"Literal[value=/.*(w-|min-w-|max-w-)\\[[^]]+\\].*/], TemplateElement[value.raw=/.*(w-|min-w-|max-w-)\\[[^]]+\\].*/]" ,
message :
'Avoid arbitrary width classes (w-[...], min-w-[...], max-w-[...]). Use scale (w-4, min-w-80) or layout tokens (max-w-layout-content). See docs/DESIGN_TOKENS.md. For exceptions (e.g. SVG), add eslint-disable.' ,
} ,
// Height: Avoid arbitrary height classes
{
selector :
"Literal[value=/.*(h-|min-h-|max-h-)\\[[^]]+\\].*/], TemplateElement[value.raw=/.*(h-|min-h-|max-h-)\\[[^]]+\\].*/]" ,
message :
'Avoid arbitrary height classes (h-[...], min-h-[...], max-h-[...]). Use scale (h-4, min-h-8) or layout tokens. See docs/DESIGN_TOKENS.md. For exceptions, add eslint-disable.' ,
} ,
// Rounded: Avoid arbitrary rounded — use rounded, rounded-lg, rounded-xl, rounded-full or var(--radius-xl)
{
selector :
"Literal[value=/.*rounded-\\[[^]]+\\].*/], TemplateElement[value.raw=/.*rounded-\\[[^]]+\\].*/]" ,
message :
'Avoid arbitrary rounded classes (rounded-[...]). Use rounded, rounded-lg, rounded-xl, rounded-full or rounded-[var(--radius-xl)]. See docs/DESIGN_TOKENS.md.' ,
} ,
// Shadow: Avoid arbitrary shadow — use tokens (shadow-card, shadow-modal, shadow-lg, etc.)
{
selector :
"Literal[value=/.*shadow-\\[[^]]+\\].*/], TemplateElement[value.raw=/.*shadow-\\[[^]]+\\].*/]" ,
message :
'Avoid arbitrary shadow classes (shadow-[...]). Use design tokens (shadow-card, shadow-modal, shadow-button-primary-glow) or Tailwind shadow-lg, shadow-xl. See docs/DESIGN_TOKENS.md. For exceptions, add eslint-disable.' ,
} ,
2025-12-03 21:56:50 +00:00
] ,
} ,
2026-02-02 18:34:14 +00:00
settings : {
react : {
version : 'detect' ,
} ,
} ,
2026-02-19 15:27:10 +00:00
} , {
files : [ '**/*.stories.tsx' , '**/*.stories.ts' ] ,
rules : {
'react-hooks/rules-of-hooks' : 'off' ,
} ,
2026-03-09 18:36:33 +00:00
} , {
files : [ '**/*.test.ts' , '**/*.test.tsx' , '**/__tests__/**' ] ,
rules : {
'@typescript-eslint/no-explicit-any' : 'off' ,
} ,
2026-02-02 18:34:14 +00:00
} , {
ignores : [
'node_modules/' ,
'dist/' ,
2026-02-19 15:08:05 +00:00
'dist_verification/' ,
2026-02-02 18:34:14 +00:00
'build/' ,
'target/' ,
2026-02-19 15:08:05 +00:00
'storybook-static/' ,
2026-02-19 15:27:10 +00:00
'e2e/' ,
'playwright-report/' ,
'public/sw.js' ,
'scripts/' ,
'src/types/generated/' ,
chore(web): install orval + mutator for OpenAPI code generation (v1.0.8 P1)
Phase 1 of the OpenAPI typegen migration. Brings orval@8.8.1 into the
monorepo (workspace-hoisted) and wires a custom mutator so generated
calls route through the existing Axios instance — interceptors for
auth / CSRF / retry / offline-queue / logging keep firing unchanged.
200 .ts files generated from veza-backend-api/openapi.yaml (3441 LOC),
covering 13 tags (auth, track, user, playlist, marketplace, chat,
dashboard, webhook, validation, logging, audit, comment, users).
Changes:
- apps/web/orval.config.ts (NEW): generator config, output
src/services/generated/, tags-split mode, vezaMutator.
- apps/web/src/services/api/orval-mutator.ts (NEW): translates
orval's (url, RequestInit) convention into AxiosRequestConfig
then apiClient. Forwards AbortSignal for React Query cancellation.
- apps/web/scripts/generate-types.sh: runs BOTH generators during
the migration (legacy typescript-axios + orval). B9 drops step 1.
- apps/web/scripts/check-types-sync.sh: extended to check drift on
both output trees.
- apps/web/eslint.config.js: ignores src/services/generated/
(orval emits overloaded function declarations that trip no-redeclare).
- .gitignore: narrowed the bare `api` SELinux rule to `/api` plus
`/veza-backend-api/api`. The old rule silently ignored
apps/web/src/services/api/ new files including orval-mutator.ts.
- apps/web/package.json + package-lock.json: orval@^8.8.1 added
as devDependency, plus @commitlint/cli + @commitlint/config-conventional
(referenced by .husky/commit-msg but missing from deps).
Out of scope: no hand-written service changes. Pilot developer.ts
lands in B2, bulk migration in B3-B8, cleanup in B9.
npm run typecheck and npm run lint both green (0 errors).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:18:14 +00:00
'src/services/generated/' ,
2026-02-02 18:34:14 +00:00
'_archive/' ,
'archive/' ,
'*.config.js' ,
'*.config.ts' ,
'*.config.cjs' ,
'**/ui.backup/**' ,
] ,
feat(web): update all features, stories, e2e tests, and auth interceptor
Update auth, playlists, tracks, search, profile, dashboard, player,
settings, and social features. Add e2e audit specs for all major pages.
Update ESLint config, vitest config, and route configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:16:36 +00:00
} , ... storybook . configs [ "flat/recommended" ] ] ;