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
'@typescript-eslint/no-unused-vars' : [ 'warn' , { argsIgnorePattern : '^_' } ] ,
'@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
'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
} ,
// 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/' ,
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" ] ] ;