diff --git a/web-player/.agents/skills/accessibility/SKILL.md b/web-player/.agents/skills/accessibility/SKILL.md new file mode 100644 index 0000000..af2299a --- /dev/null +++ b/web-player/.agents/skills/accessibility/SKILL.md @@ -0,0 +1,440 @@ +--- +name: accessibility +description: Audit and improve web accessibility following WCAG 2.2 guidelines. Use when asked to "improve accessibility", "a11y audit", "WCAG compliance", "screen reader support", "keyboard navigation", or "make accessible". +license: MIT +metadata: + author: web-quality-skills + version: "1.1" +--- + +# Accessibility (a11y) + +Comprehensive accessibility guidelines based on WCAG 2.2 and Lighthouse accessibility audits. Goal: make content usable by everyone, including people with disabilities. + +## WCAG Principles: POUR + +| Principle | Description | +|-----------|-------------| +| **P**erceivable | Content can be perceived through different senses | +| **O**perable | Interface can be operated by all users | +| **U**nderstandable | Content and interface are understandable | +| **R**obust | Content works with assistive technologies | + +## Conformance levels + +| Level | Requirement | Target | +|-------|-------------|--------| +| **A** | Minimum accessibility | Must pass | +| **AA** | Standard compliance | Should pass (legal requirement in many jurisdictions) | +| **AAA** | Enhanced accessibility | Nice to have | + +--- + +## Perceivable + +### Text alternatives (1.1) + +**Images require alt text:** +```html + + + + +Bar chart showing 40% increase in Q3 sales + + + + + +
+ 2024 market trends infographic +
+ +
+
+``` + +**Icon buttons need accessible names:** +```html + + + + + + + + +``` + +**Visually hidden class:** +```css +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +``` + +### Color contrast (1.4.3, 1.4.6) + +| Text Size | AA minimum | AAA enhanced | +|-----------|------------|--------------| +| Normal text (< 18px / < 14px bold) | 4.5:1 | 7:1 | +| Large text (≥ 18px / ≥ 14px bold) | 3:1 | 4.5:1 | +| UI components & graphics | 3:1 | 3:1 | + +```css +/* ❌ Low contrast (2.5:1) */ +.low-contrast { + color: #999; + background: #fff; +} + +/* ✅ Sufficient contrast (7:1) */ +.high-contrast { + color: #333; + background: #fff; +} + +/* ✅ Focus states need contrast too */ +:focus-visible { + outline: 2px solid #005fcc; + outline-offset: 2px; +} +``` + +**Don't rely on color alone:** +```html + + + + + +
+ + + + Please enter a valid email address + +
+``` + +### Media alternatives (1.2) + +```html + + + + + +
+ Transcript +

Full transcript text...

+
+``` + +--- + +## Operable + +### Keyboard accessible (2.1) + +**All functionality must be keyboard accessible:** +```javascript +// ❌ Only handles click +element.addEventListener('click', handleAction); + +// ✅ Handles both click and keyboard +element.addEventListener('click', handleAction); +element.addEventListener('keydown', (e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleAction(); + } +}); +``` + +**No keyboard traps.** Users must be able to Tab into and out of every component. Use the [modal focus trap pattern](references/A11Y-PATTERNS.md#modal-focus-trap) for dialogs—the native `` element handles this automatically. + +### Focus visible (2.4.7) + +```css +/* ❌ Never remove focus outlines */ +*:focus { outline: none; } + +/* ✅ Use :focus-visible for keyboard-only focus */ +:focus { + outline: none; +} + +:focus-visible { + outline: 2px solid #005fcc; + outline-offset: 2px; +} + +/* ✅ Or custom focus styles */ +button:focus-visible { + box-shadow: 0 0 0 3px rgba(0, 95, 204, 0.5); +} +``` + +### Focus not obscured (2.4.11) — new in 2.2 + +When an element receives keyboard focus, it must not be entirely hidden by other author-created content such as sticky headers, footers, or overlapping panels. At Level AAA (2.4.12), no part of the focused element may be hidden. + +```css +/* ✅ Account for sticky headers when scrolling to focused elements */ +:target { + scroll-margin-top: 80px; +} + +/* ✅ Ensure focused items clear fixed/sticky bars */ +:focus { + scroll-margin-top: 80px; + scroll-margin-bottom: 60px; +} +``` + +### Skip links (2.4.1) + +Provide a skip link so keyboard users can bypass repetitive navigation. See the [skip link pattern](references/A11Y-PATTERNS.md#skip-link) for full markup and styles. + +### Target size (2.5.8) — new in 2.2 + +Interactive targets must be at least **24 × 24 CSS pixels** (AA). Exceptions: inline text links, elements where the browser controls the size, and targets where a 24px circle centered on the bounding box does not overlap another target. + +```css +/* ✅ Minimum target size */ +button, +[role="button"], +input[type="checkbox"] + label, +input[type="radio"] + label { + min-width: 24px; + min-height: 24px; +} + +/* ✅ Comfortable target size (recommended 44×44) */ +.touch-target { + min-width: 44px; + min-height: 44px; + display: inline-flex; + align-items: center; + justify-content: center; +} +``` + +### Dragging movements (2.5.7) — new in 2.2 + +Any action that requires dragging must have a single-pointer alternative (e.g., buttons, inputs). See the [dragging movements pattern](references/A11Y-PATTERNS.md#dragging-movements) for a sortable-list example. + +### Timing (2.2) + +```javascript +// Allow users to extend time limits +function showSessionWarning() { + const modal = createModal({ + title: 'Session Expiring', + content: 'Your session will expire in 2 minutes.', + actions: [ + { label: 'Extend session', action: extendSession }, + { label: 'Log out', action: logout } + ], + timeout: 120000 + }); +} +``` + +### Motion (2.3) + +```css +/* Respect reduced motion preference */ +@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; + } +} +``` + +--- + +## Understandable + +### Page language (3.1.1) + +```html + + + + + + + +

The French word for hello is bonjour.

+``` + +### Consistent navigation (3.2.3) + +```html + + +``` + +### Consistent help (3.2.6) — new in 2.2 + +If a help mechanism (contact info, chat widget, FAQ link, self-help option) is repeated across multiple pages, it must appear in the **same relative order** each time. Users who rely on consistent placement shouldn't have to hunt for help on every page. + +### Form labels (3.3.2) + +Every input needs a programmatically associated label. See the [form labels pattern](references/A11Y-PATTERNS.md#form-labels) for explicit, implicit, and instructional examples. + +### Error handling (3.3.1, 3.3.3) + +Announce errors to screen readers with `role="alert"` or `aria-live`, set `aria-invalid="true"` on invalid fields, and focus the first error on submit. See the [error handling pattern](references/A11Y-PATTERNS.md#error-handling) for full markup and JS. + +### Redundant entry (3.3.7) — new in 2.2 + +Don't force users to re-enter information they already provided in the same session. Auto-populate from earlier steps, or let users select from previously entered values. Exceptions: security re-confirmation and content that has expired. + +```html + +
+ Shipping address + + +
+``` + +### Accessible authentication (3.3.8) — new in 2.2 + +Login flows must not rely on cognitive function tests (e.g., remembering a password, solving a puzzle) unless at least one of: +- A copy-paste or autofill mechanism is available +- An alternative method exists (e.g., passkey, SSO, email link) +- The test uses object recognition or personal content (AA only; AAA removes this exception) + +```html + + + + + + +``` + +--- + +## Robust + +### ARIA usage (4.1.2) + +**Prefer native elements:** +```html + +
Click me
+ + + + + +
Option
+ + + +``` + +**When ARIA is needed,** use the correct roles and states. See the [ARIA tabs pattern](references/A11Y-PATTERNS.md#aria-tabs) for a complete tablist example. + +### Live regions (4.1.3) + +Use `aria-live` regions to announce dynamic content changes without moving focus. See the [live regions pattern](references/A11Y-PATTERNS.md#live-regions-and-notifications) for markup and a `showNotification()` helper. + +--- + +## Testing checklist + +### Automated testing +```bash +# Lighthouse accessibility audit +npx lighthouse https://example.com --only-categories=accessibility + +# axe-core +npm install @axe-core/cli -g +axe https://example.com +``` + +### Manual testing + +- [ ] **Keyboard navigation:** Tab through entire page, use Enter/Space to activate +- [ ] **Screen reader:** Test with VoiceOver (Mac), NVDA (Windows), or TalkBack (Android) +- [ ] **Zoom:** Content usable at 200% zoom +- [ ] **High contrast:** Test with Windows High Contrast Mode +- [ ] **Reduced motion:** Test with `prefers-reduced-motion: reduce` +- [ ] **Focus order:** Logical and follows visual order +- [ ] **Target size:** Interactive elements meet 24×24px minimum + +See the [screen reader commands reference](references/A11Y-PATTERNS.md#screen-reader-commands) for VoiceOver and NVDA shortcuts. + +--- + +## Common issues by impact + +### Critical (fix immediately) +1. Missing form labels +2. Missing image alt text +3. Insufficient color contrast +4. Keyboard traps +5. No focus indicators + +### Serious (fix before launch) +1. Missing page language +2. Missing heading structure +3. Non-descriptive link text +4. Auto-playing media +5. Missing skip links + +### Moderate (fix soon) +1. Missing ARIA labels on icons +2. Inconsistent navigation +3. Missing error identification +4. Timing without controls +5. Missing landmark regions + +## References + +- [WCAG 2.2 Quick Reference](https://www.w3.org/WAI/WCAG22/quickref/) +- [WAI-ARIA Authoring Practices](https://www.w3.org/WAI/ARIA/apg/) +- [Deque axe Rules](https://dequeuniversity.com/rules/axe/) +- [Web Quality Audit](../web-quality-audit/SKILL.md) +- [WCAG criteria reference](references/WCAG.md) +- [Accessibility code patterns](references/A11Y-PATTERNS.md) diff --git a/web-player/.agents/skills/accessibility/references/A11Y-PATTERNS.md b/web-player/.agents/skills/accessibility/references/A11Y-PATTERNS.md new file mode 100644 index 0000000..6d500ef --- /dev/null +++ b/web-player/.agents/skills/accessibility/references/A11Y-PATTERNS.md @@ -0,0 +1,233 @@ +# Accessibility Code Patterns + +Practical, copy-paste-ready patterns for common accessibility requirements. Each pattern is self-contained and linked from the main [SKILL.md](../SKILL.md). + +--- + +## Modal focus trap + +Trap keyboard focus inside a modal dialog so Tab/Shift+Tab cycle through its focusable elements and Escape closes it. + +```javascript +function openModal(modal) { + const focusableElements = modal.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' + ); + const firstElement = focusableElements[0]; + const lastElement = focusableElements[focusableElements.length - 1]; + + modal.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + if (e.shiftKey && document.activeElement === firstElement) { + e.preventDefault(); + lastElement.focus(); + } else if (!e.shiftKey && document.activeElement === lastElement) { + e.preventDefault(); + firstElement.focus(); + } + } + if (e.key === 'Escape') { + closeModal(); + } + }); + + firstElement.focus(); +} +``` + +The native `` element handles focus trapping automatically—prefer it when browser support allows. + +--- + +## Skip link + +Allows keyboard users to bypass repetitive navigation and jump straight to main content. + +```html + + +
+
+ +
+ +``` + +```css +.skip-link { + position: absolute; + top: -40px; + left: 0; + background: #000; + color: #fff; + padding: 8px 16px; + z-index: 100; +} + +.skip-link:focus { + top: 0; +} +``` + +--- + +## Error handling + +Announce errors to screen readers and focus the first invalid field on submit. + +```html +
+
+ + + +
+
+``` + +```javascript +form.addEventListener('submit', (e) => { + const firstError = form.querySelector('[aria-invalid="true"]'); + if (firstError) { + e.preventDefault(); + firstError.focus(); + + const errorSummary = document.getElementById('error-summary'); + errorSummary.textContent = + `${errors.length} errors found. Please fix them and try again.`; + errorSummary.focus(); + } +}); +``` + +--- + +## Form labels + +Every input needs an associated label—either explicit (`for`/`id`) or implicit (wrapping `
+ + + save()} + oncancel={() => reset()} + onclose={() => visible = false} +/> +``` + +### Typed Callbacks with TypeScript + +```svelte + +``` + +### Forwarding Native Events + +```svelte + + + +``` + +--- + +## Context API + +Context functions must be called synchronously during component initialization. + +### Correct Context Usage + +```svelte + +``` + +### Reactive Context Values + +Pass `$state` objects for reactive context: + +```svelte + + + +{@render children()} + + + + +

Theme: {themeContext.current}

+ +``` + +### Type-Safe Context Pattern + +```ts +// context.ts +import { setContext, getContext } from 'svelte'; + +const THEME_KEY = Symbol('theme'); + +interface ThemeContext { + current: 'light' | 'dark'; + toggle: () => void; +} + +export function setThemeContext(context: ThemeContext) { + setContext(THEME_KEY, context); +} + +export function getThemeContext(): ThemeContext { + const context = getContext(THEME_KEY); + if (!context) throw new Error('Theme context not found'); + return context; +} +``` + +### Avoid Generic Keys + +```svelte + +``` + +### hasContext Check + +```svelte + +``` + +### Context Boundaries + +Context flows down the component tree: + +```svelte + + + + +``` diff --git a/web-player/.agents/skills/svelte5-best-practices/references/migration.md b/web-player/.agents/skills/svelte5-best-practices/references/migration.md new file mode 100644 index 0000000..7d1d863 --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/migration.md @@ -0,0 +1,339 @@ +# Svelte 4 to Svelte 5 Migration Reference + +## Table of Contents +- [Migrating Reactive Statements](#migrating-reactive-statements) +- [Migrating Stores to Runes](#migrating-stores-to-runes) + +--- + +## Migrating Reactive Statements + +Svelte 5 replaces `$:` with `$derived` (for values) and `$effect` (for side effects). + +### Computed Values: $: to $derived + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Complex Computed Values: $derived.by + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Side Effects: $: to $effect + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Conditional Side Effects + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Props Migration + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Effect with Cleanup + +**Svelte 4:** +```svelte + +``` + +**Svelte 5:** +```svelte + +``` + +### Migration Cheat Sheet + +| Svelte 4 Pattern | Svelte 5 Replacement | +|------------------|----------------------| +| `let x = 0` (in component) | `let x = $state(0)` | +| `export let prop` | `let { prop } = $props()` | +| `$: derived = expr` | `let derived = $derived(expr)` | +| `$: { complex }` (value) | `let x = $derived.by(() => { ... })` | +| `$: console.log(x)` | `$effect(() => console.log(x))` | +| `$: if (x) { ... }` | `$effect(() => { if (x) { ... } })` | +| `$: document.title = x` | `$effect(() => { document.title = x })` | + +### Automated Migration + +```bash +npx sv migrate svelte-5 +``` + +--- + +## Migrating Stores to Runes + +Svelte 5 runes can replace most store use cases with simpler, more direct reactive state. + +### Local Component State + +**Svelte 4:** +```svelte + + + +``` + +**Svelte 5:** +```svelte + + + +``` + +### Shared State Across Components + +**Svelte 4 (stores.ts):** +```ts +import { writable } from 'svelte/store'; +export const user = writable(null); +export const theme = writable('light'); +``` + +**Svelte 5 (state.svelte.ts):** +```ts +export const user = $state(null); +export const theme = $state({ current: 'light' as 'light' | 'dark' }); + +export function setTheme(newTheme: 'light' | 'dark') { + theme.current = newTheme; +} +``` + +```svelte + + +

Theme: {theme.current}

+ +``` + +### Derived Store to $derived + +**Svelte 4:** +```ts +import { writable, derived } from 'svelte/store'; +export const items = writable([]); +export const completedCount = derived(items, $items => + $items.filter(i => i.done).length +); +``` + +**Svelte 5:** +```ts +// state.svelte.ts +export const items = $state([]); + +export function getCompletedCount() { + return items.filter(i => i.done).length; +} +``` + +```svelte + +``` + +### Custom Store to Reactive Class + +**Svelte 4:** +```ts +function createCounter() { + const { subscribe, set, update } = writable(0); + return { + subscribe, + increment: () => update(n => n + 1), + decrement: () => update(n => n - 1), + reset: () => set(0) + }; +} +export const counter = createCounter(); +``` + +**Svelte 5:** +```ts +// counter.svelte.ts +class Counter { + value = $state(0); + increment() { this.value++; } + decrement() { this.value--; } + reset() { this.value = 0; } +} + +export const counter = new Counter(); +``` + +```svelte + + +

{counter.value}

+ +``` + +### Async State Pattern + +**Svelte 5:** +```ts +// api.svelte.ts +export const state = $state({ + data: null as Data | null, + loading: false, + error: null as Error | null +}); + +export async function fetchData() { + state.loading = true; + state.error = null; + + try { + const res = await fetch('/api/data'); + state.data = await res.json(); + } catch (e) { + state.error = e as Error; + } finally { + state.loading = false; + } +} +``` + +### When to Still Use Stores + +1. **Library interop** - Libraries that expect Svelte stores +2. **Legacy code** - Gradual migration +3. **Observable patterns** - When you need the subscribe API +4. **Server-side rendering** - Stores have built-in SSR handling + +### Store to Rune Cheat Sheet + +| Store Pattern | Rune Replacement | +|---------------|------------------| +| `writable(value)` | `$state(value)` | +| `$store` (auto-subscribe) | Direct access to `$state` value | +| `store.set(x)` | `state = x` | +| `store.update(fn)` | Direct mutation or reassignment | +| `derived(store, fn)` | `$derived(fn())` in component | +| `readable(value, start)` | `$state` + `$effect` for setup | diff --git a/web-player/.agents/skills/svelte5-best-practices/references/performance.md b/web-player/.agents/skills/svelte5-best-practices/references/performance.md new file mode 100644 index 0000000..631f2d0 --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/performance.md @@ -0,0 +1,443 @@ +# Svelte 5 Performance Reference + +## Table of Contents +- [Universal Reactivity](#universal-reactivity) +- [Avoiding Over-Reactivity](#avoiding-over-reactivity) +- [Preventing Load Waterfalls](#preventing-load-waterfalls) +- [Streaming Non-Critical Data](#streaming-non-critical-data) +- [Component Testing](#component-testing) + +--- + +## Universal Reactivity + +Svelte 5 allows runes in `.svelte.js` or `.svelte.ts` files for shared state outside components. + +### Shared Counter State + +```ts +// counter.svelte.ts +export const counter = $state({ count: 0 }); + +export function increment() { counter.count++; } +export function decrement() { counter.count--; } +export function reset() { counter.count = 0; } +``` + +```svelte + + +

Count: {counter.count}

+ +``` + +### Object State with Getters + +```ts +// user.svelte.ts +const state = $state({ firstName: '', lastName: '', email: '' }); + +export const user = { + get firstName() { return state.firstName; }, + set firstName(v: string) { state.firstName = v; }, + get fullName() { return `${state.firstName} ${state.lastName}`; }, + get isValid() { return state.firstName && state.email.includes('@'); } +}; +``` + +### Reactive Class Pattern + +```ts +// todo.svelte.ts +class TodoStore { + items = $state([]); + filter = $state<'all' | 'active' | 'completed'>('all'); + + get filtered() { + switch (this.filter) { + case 'active': return this.items.filter(t => !t.done); + case 'completed': return this.items.filter(t => t.done); + default: return this.items; + } + } + + add(text: string) { + this.items.push({ id: Date.now(), text, done: false }); + } +} + +export const todos = new TodoStore(); +``` + +### Important Notes + +1. **File extension matters**: Must use `.svelte.js` or `.svelte.ts` +2. **No $derived in module scope**: Use getters for derived values +3. **SSR caution**: Avoid initializing browser-only state at module level + +--- + +## Avoiding Over-Reactivity + +Common mistakes with runes can cause unnecessary re-renders, infinite loops, or degraded performance. + +### Anti-Pattern 1: Using $effect to Set Derived Values + +**WRONG:** +```svelte + +``` + +**CORRECT:** +```svelte + +``` + +### Anti-Pattern 2: Circular Dependencies + +**WRONG:** +```svelte + +``` + +**CORRECT: Use separate effects or event handlers** + +### Anti-Pattern 3: Not Using untrack + +**WRONG:** +```svelte + +``` + +**CORRECT:** +```svelte + +``` + +### Anti-Pattern 4: Heavy Computations in $derived + +**WRONG:** +```svelte + +``` + +**CORRECT: Debounce expensive computations** +```svelte + +``` + +### Anti-Pattern 5: Effect for DOM Manipulation + +**WRONG:** +```svelte + +``` + +**CORRECT:** +```svelte +{#if visible}
Content
{/if} + +
Content
+``` + +### Performance Tips + +1. Use `$derived` over `$effect` for computed values +2. Debounce expensive reactive computations +3. Use `untrack` to prevent unnecessary dependencies +4. Group related state into objects +5. Let Svelte handle DOM updates + +--- + +## Preventing Load Waterfalls + +Sequential API calls multiply latency. Use parallel requests and streaming. + +### Anti-Pattern: Waterfall + +**WRONG (3 seconds total):** +```ts +export const load = async ({ fetch }) => { + const user = await fetch('/api/user').then(r => r.json()); // 1s + const posts = await fetch(`/api/users/${user.id}/posts`).then(r => r.json()); // 1s + const comments = await fetch('/api/comments').then(r => r.json()); // 1s + return { user, posts, comments }; +}; +``` + +### Pattern 1: Parallel with Promise.all + +**CORRECT (1 second total):** +```ts +export const load = async ({ fetch }) => { + const [user, posts, comments] = await Promise.all([ + fetch('/api/user').then(r => r.json()), + fetch('/api/posts').then(r => r.json()), + fetch('/api/comments').then(r => r.json()) + ]); + return { user, posts, comments }; +}; +``` + +### Pattern 2: Partial Dependencies + +```ts +export const load = async ({ fetch }) => { + const user = await fetch('/api/user').then(r => r.json()); + + const [posts, followers] = await Promise.all([ + fetch(`/api/users/${user.id}/posts`).then(r => r.json()), + fetch(`/api/users/${user.id}/followers`).then(r => r.json()) + ]); + + return { user, posts, followers }; +}; +``` + +### Pattern 3: Streaming Non-Critical Data + +```ts +export const load = async ({ fetch }) => { + const user = await fetch('/api/user').then(r => r.json()); + + return { + user, + recommendations: fetch('/api/recommendations').then(r => r.json()), + analytics: fetch('/api/analytics').then(r => r.json()) + }; +}; +``` + +### Performance Comparison + +| Pattern | 3 APIs x 1s each | +|---------|------------------| +| Sequential | 3 seconds | +| Parallel (Promise.all) | 1 second | +| Streaming | 0s initial, streams rest | + +--- + +## Streaming Non-Critical Data + +Return promises from load functions to stream non-essential data after initial page render. + +### Basic Streaming + +**WRONG (blocks on slow data):** +```ts +export const load = async ({ fetch }) => { + const user = await fetch('/api/user').then(r => r.json()); // 100ms + const analytics = await fetch('/api/analytics').then(r => r.json()); // 2000ms + return { user, analytics }; // Page blocked for 2.1 seconds +}; +``` + +**CORRECT (stream slow data):** +```ts +export const load = async ({ fetch }) => { + const user = await fetch('/api/user').then(r => r.json()); + const analytics = fetch('/api/analytics').then(r => r.json()); // Don't await + + return { + user, // Available in 100ms + analytics // Streams when ready + }; +}; +``` + +### Component Handling + +```svelte + + +

Welcome, {data.user.name}

+ + +``` + +### When to Stream + +| Data Type | Stream? | Reason | +|-----------|---------|--------| +| User info | No | Critical for layout | +| Main content | No | Users came for this | +| Analytics | Yes | Not user-facing | +| Recommendations | Yes | Supplementary | +| Comments | Maybe | Important but can load later | + +--- + +## Component Testing + +Svelte 5 components with runes require proper Vitest configuration. + +### Option 1: Vitest Browser Mode (Recommended) + +```ts +// vitest.config.ts +import { defineConfig } from 'vitest/config'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; + +export default defineConfig({ + plugins: [svelte()], + test: { + browser: { + enabled: true, + provider: 'playwright', + name: 'chromium' + } + } +}); +``` + +### Option 2: JSDOM with @testing-library/svelte + +```ts +// vitest.config.ts +import { defineConfig } from 'vitest/config'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; + +export default defineConfig({ + plugins: [svelte({ hot: !process.env.VITEST })], + test: { + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,ts}'], + globals: true + } +}); +``` + +### Basic Component Test + +```ts +import { render, screen, fireEvent } from '@testing-library/svelte'; +import { expect, test } from 'vitest'; +import Counter from './Counter.svelte'; + +test('increments count when clicked', async () => { + render(Counter); + + const button = screen.getByRole('button'); + expect(button).toHaveTextContent('Count: 0'); + + await fireEvent.click(button); + expect(button).toHaveTextContent('Count: 1'); +}); +``` + +### Testing Props + +```ts +test('renders with custom name', () => { + render(Greeting, { props: { name: 'Alice' } }); + expect(screen.getByText('Hello, Alice!')).toBeInTheDocument(); +}); +``` + +### Testing Callbacks + +```ts +import { vi } from 'vitest'; + +test('calls onclick callback when clicked', async () => { + const handleClick = vi.fn(); + render(Button, { props: { onclick: handleClick } }); + + await fireEvent.click(screen.getByRole('button')); + expect(handleClick).toHaveBeenCalledTimes(1); +}); +``` + +### Testing Async Components + +```ts +import { waitFor } from '@testing-library/svelte'; + +test('loads and displays user data', async () => { + global.fetch = vi.fn().mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ name: 'Alice' }) + }); + + render(UserProfile, { props: { userId: '123' } }); + expect(screen.getByText('Loading...')).toBeInTheDocument(); + + await waitFor(() => { + expect(screen.getByText('Alice')).toBeInTheDocument(); + }); +}); +``` diff --git a/web-player/.agents/skills/svelte5-best-practices/references/runes.md b/web-player/.agents/skills/svelte5-best-practices/references/runes.md new file mode 100644 index 0000000..2d773f2 --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/runes.md @@ -0,0 +1,394 @@ +# Svelte 5 Runes Reference + +## Table of Contents +- [$state - Reactive State](#state---reactive-state) +- [$derived - Computed Values](#derived---computed-values) +- [$effect - Side Effects](#effect---side-effects) +- [$props - Component Props](#props---component-props) +- [$bindable - Two-Way Binding](#bindable---two-way-binding) +- [$inspect - Debugging](#inspect---debugging) + +--- + +## $state - Reactive State + +In Svelte 5, plain `let` declarations are no longer automatically reactive. Use `$state()` for reactive state. + +### Basic Usage + +```svelte + + + +``` + +### Object and Array State + +Objects and arrays are deeply reactive by default: + +```svelte + + + + +``` + +### Class State + +```svelte + + + +``` + +### Raw State (Opt-out of Deep Reactivity) + +```svelte + +``` + +--- + +## $derived - Computed Values + +Replaces `$:` reactive statements for computed values. + +### Basic Usage + +```svelte + +``` + +### Complex Derivations with $derived.by + +```svelte + +``` + +### Deriving from Props + +```svelte + +``` + +### $derived vs $effect + +Use `$derived` for computing values, `$effect` for side effects: + +```svelte + +``` + +--- + +## $effect - Side Effects + +Runs code when component mounts and when dependencies change. Requires cleanup for subscriptions. + +### Basic Effect with Cleanup + +```svelte + +``` + +### DOM Event Listeners + +```svelte + +``` + +### External Subscriptions + +```svelte + +``` + +### $effect.pre for Pre-DOM Updates + +```svelte + +``` + +### Untracked Dependencies + +```svelte + +``` + +--- + +## $props - Component Props + +Replaces `export let` for declaring component props. + +### Basic Usage + +```svelte + +``` + +### Rest Props Pattern + +```svelte + + +
+ {@render children?.()} +
+``` + +### Renaming Reserved Words + +```svelte + +``` + +### TypeScript Props + +```svelte + +``` + +### Props with Children + +```svelte + + +
+

{title}

+
{@render children()}
+ {#if footer}
{@render footer()}
{/if} +
+``` + +--- + +## $bindable - Two-Way Binding + +Props must be explicitly marked with `$bindable()` to support `bind:`. + +### Basic Usage + +```svelte + + + + + + + + + +``` + +### Bindable with Default Value + +```svelte + +``` + +### Multiple Bindable Props + +```svelte + +``` + +### TypeScript with Bindable + +```svelte + +``` + +--- + +## $inspect - Debugging + +Development-only debugging that logs values when they change. Stripped in production. + +### Basic Usage + +```svelte + +``` + +### Multiple Values + +```svelte + +``` + +### Custom Logging with $inspect.with + +```svelte + +``` + +### Debugging with Breakpoints + +```svelte + +``` diff --git a/web-player/.agents/skills/svelte5-best-practices/references/snippets.md b/web-player/.agents/skills/svelte5-best-practices/references/snippets.md new file mode 100644 index 0000000..f5ad7cf --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/snippets.md @@ -0,0 +1,291 @@ +# Svelte 5 Snippets Reference + +## Table of Contents +- [Replacing Slots with Snippets](#replacing-slots-with-snippets) +- [Rendering with @render](#rendering-with-render) + +--- + +## Replacing Slots with Snippets + +Svelte 5 replaces `` with snippets - a more powerful and type-safe composition primitive. + +### Default Content (children) + +**Svelte 4:** +```svelte +
+``` + +**Svelte 5:** +```svelte + + +
{@render children?.()}
+``` + +### Named Slots to Named Snippets + +**Svelte 4:** +```svelte +
+
+
+ + + +

Title

+

Content

+ Footer +
+``` + +**Svelte 5:** +```svelte + + +
{@render header?.()}
+
{@render children?.()}
+
{@render footer?.()}
+ + + + {#snippet header()}

Title

{/snippet} +

Content

+ {#snippet footer()}Footer{/snippet} +
+``` + +### Slot Props to Snippet Parameters + +**Svelte 4:** +```svelte +
    + {#each items as item} +
  • + {/each} +
+ + + {item.name} + +``` + +**Svelte 5:** +```svelte + + +
    + {#each items as item} +
  • {@render children?.(item)}
  • + {/each} +
+ + + {#snippet children(item)} + {item.name} + {/snippet} + +``` + +### Slot Fallback to Snippet Fallback + +```svelte + + +{#if children} + {@render children()} +{:else} +

Default content

+{/if} +``` + +### Defining Snippets Within Components + +```svelte + + +{#snippet normalItem(item)} +
  • {item.name}
  • +{/snippet} + +{#snippet featuredItem(item)} + +{/snippet} + +
      + {#each items as item} + {#if item.type === 'featured'} + {@render featuredItem(item)} + {:else} + {@render normalItem(item)} + {/if} + {/each} +
    +``` + +### TypeScript Typing + +```svelte + +``` + +--- + +## Rendering with @render + +Snippets must be rendered using `{@render}`. Optional snippets need null-safe calling. + +### Basic Snippet Rendering + +```svelte + + +
    {@render children()}
    +``` + +### Optional Snippets + +Always use optional chaining for snippets that might not be provided: + +```svelte + + +{@render children?.()} +``` + +### Conditional Rendering + +```svelte + + +{#if header} +
    {@render header()}
    +{/if} + +
    {@render children?.()}
    + +{#if footer} +
    {@render footer()}
    +{/if} +``` + +### Rendering with Arguments + +```svelte + + +
      + {#each items as item, index} +
    • {@render itemTemplate(item, index)}
    • + {/each} +
    + + + + {#snippet itemTemplate(item, index)} + {index + 1}. {item.name} + {/snippet} + +``` + +### Rendering Multiple Times + +Snippets can be rendered multiple times: + +```svelte + + + +``` + +### Rendering Dynamic Snippets + +```svelte + + +{@render currentView?.()} +``` + +### TypeScript with @render + +```svelte + + +{@render header?.()} +{@render children()} +{@render row({ id: 1, name: 'Test' })} +``` + +### Passing Snippets to Child Components + +```svelte + + + + + + + + +{#each items as item} + {@render itemRenderer?.(item)} +{/each} +``` diff --git a/web-player/.agents/skills/svelte5-best-practices/references/sveltekit.md b/web-player/.agents/skills/svelte5-best-practices/references/sveltekit.md new file mode 100644 index 0000000..816b83b --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/sveltekit.md @@ -0,0 +1,401 @@ +# SvelteKit Patterns Reference + +## Table of Contents +- [Load Function Types](#load-function-types) +- [Page Props Typing](#page-props-typing) +- [Form Actions Error Handling](#form-actions-error-handling) +- [SSR State Isolation](#ssr-state-isolation) + +--- + +## Load Function Types + +SvelteKit has two load function types: universal (`+page.js`) and server-only (`+page.server.ts`). + +### When to Use Each + +| Use +page.server.ts | Use +page.js | +|---------------------|--------------| +| Accessing secrets/credentials | Public APIs only | +| Database connections | Non-serializable data (functions, classes) | +| Server-only APIs | Client-side caching benefits | +| Sensitive business logic | | + +### Security: Secrets in Server Load + +**WRONG (secrets exposed):** +```ts +// +page.js - DANGEROUS: runs in browser! +export const load = async ({ fetch }) => { + const response = await fetch('https://api.stripe.com/charges', { + headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` } // EXPOSED! + }); + return { charges: await response.json() }; +}; +``` + +**CORRECT:** +```ts +// +page.server.ts - only runs on server +import { STRIPE_SECRET_KEY } from '$env/static/private'; + +export const load = async ({ fetch }) => { + const response = await fetch('https://api.stripe.com/charges', { + headers: { 'Authorization': `Bearer ${STRIPE_SECRET_KEY}` } + }); + return { charges: await response.json() }; +}; +``` + +### Serialization: Non-serializable in Universal Load + +**WRONG (server load can't serialize functions):** +```ts +// +page.server.ts +export const load = async () => { + return { + formatDate: (date: Date) => date.toLocaleDateString(), // ERROR + parser: new DOMParser() // ERROR + }; +}; +``` + +**CORRECT:** +```ts +// +page.js +export const load = async () => { + return { + formatDate: (date: Date) => date.toLocaleDateString(), // OK + parser: typeof window !== 'undefined' ? new DOMParser() : null + }; +}; +``` + +### Database Access + +```ts +// +page.server.ts - CORRECT +import { db } from '$lib/server/database'; + +export const load = async () => { + const users = await db.query('SELECT * FROM users'); + return { users }; +}; +``` + +### Environment Variables + +```ts +// +page.server.ts - Private env vars +import { DATABASE_URL, API_SECRET } from '$env/static/private'; + +// +page.js - Only public env vars +import { PUBLIC_API_URL } from '$env/static/public'; +``` + +--- + +## Page Props Typing + +SvelteKit generates types in `./$types` for full type safety with `$props()`. + +### Basic Page Data Typing + +```svelte + + + +

    {data.title}

    +``` + +### Page Data with Form Actions + +```svelte + + +{#if form?.success} +

    {form.message}

    +{/if} + +{#if form?.error} +

    {form.error}

    +{/if} +``` + +### Layout Data Typing + +```svelte + + + + + +{@render children()} +``` + +### Corresponding Load Function + +```ts +// +page.server.ts +import type { PageServerLoad, Actions } from './$types'; + +export const load: PageServerLoad = async () => { + return { + title: 'My Page', + items: await fetchItems() + }; +}; + +export const actions: Actions = { + default: async ({ request }) => { + return { success: true, message: 'Saved successfully' }; + } +}; +``` + +### Error Page + +```svelte + + + +

    {page.status}: {page.error?.message}

    +``` + +--- + +## Form Actions Error Handling + +SvelteKit distinguishes between validation errors (`fail()`) and unexpected errors (`throw error()`). + +### Use fail() for Validation Errors + +**WRONG:** +```ts +import { error } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request }) => { + if (!email?.toString().includes('@')) { + throw error(400, 'Invalid email'); // Shows error page! + } + } +}; +``` + +**CORRECT:** +```ts +import { fail } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request }) => { + const data = await request.formData(); + const email = data.get('email')?.toString() ?? ''; + + if (!email.includes('@')) { + return fail(400, { + error: 'Invalid email address', + email // Return for repopulation + }); + } + + return { success: true }; + } +}; +``` + +### Use throw error() for Unexpected Errors + +```ts +import { fail, error } from '@sveltejs/kit'; + +export const actions = { + default: async ({ request }) => { + const data = await request.formData(); + + // Validation - use fail() + if (!data.get('title')) { + return fail(400, { error: 'Title is required' }); + } + + try { + await database.save(data); + return { success: true }; + } catch (e) { + // Unexpected error - use throw + throw error(500, 'Unable to save. Please try again later.'); + } + } +}; +``` + +### Structured Validation Responses + +```ts +interface FormErrors { + email?: string; + password?: string; +} + +export const actions = { + register: async ({ request }) => { + const data = await request.formData(); + const errors: FormErrors = {}; + + if (!email) errors.email = 'Email is required'; + if (password.length < 8) errors.password = 'Password must be at least 8 characters'; + + if (Object.keys(errors).length > 0) { + return fail(400, { errors, values: { email } }); + } + + return { success: true }; + } +}; +``` + +### Handle in Component + +```svelte + + +
    + + +
    +``` + +### Error Response Summary + +| Situation | Function | Result | +|-----------|----------|--------| +| Missing field | `fail(400, {...})` | Form state preserved | +| Invalid format | `fail(400, {...})` | Form state preserved | +| Not found | `throw error(404, ...)` | Error page | +| Server crash | `throw error(500, ...)` | Error page | +| Auth required | `throw redirect(303, ...)` | Redirect | + +--- + +## SSR State Isolation + +Shared server state persists across requests, potentially leaking data between users. + +### Dangerous: Module-Level State + +**WRONG:** +```ts +// +page.server.ts +let currentUser = null; // SHARED ACROSS ALL REQUESTS! + +export const load = async ({ locals }) => { + currentUser = locals.user; // User B overwrites User A + return { user: currentUser }; +}; +``` + +**CORRECT:** +```ts +export const load = async ({ locals }) => { + return { user: locals.user }; // Each request gets its own locals +}; +``` + +### Dangerous: Global Stores + +**WRONG:** +```ts +// stores.svelte.ts +export const user = $state(null); // Server-side singleton! +``` + +**CORRECT: Use locals and return data from load** +```ts +// +layout.server.ts +export const load = async ({ locals }) => { + return { user: locals.user }; +}; +``` + +### Safe Patterns + +**Pattern 1: Use locals** +```ts +// hooks.server.ts +export const handle = async ({ event, resolve }) => { + event.locals.user = await authenticate(event); + event.locals.requestId = crypto.randomUUID(); + return resolve(event); +}; + +// +page.server.ts +export const load = async ({ locals }) => { + return { user: locals.user }; +}; +``` + +**Pattern 2: Context for Component Trees** +```svelte + + + +{@render children()} +``` + +**Pattern 3: Client-Only State** +```ts +// stores.svelte.ts +import { browser } from '$app/environment'; + +function createClientStore() { + if (!browser) return { value: null }; + const state = $state({ value: null }); + return state; +} + +export const clientState = createClientStore(); +``` + +### SSR Safety Checklist + +- [ ] No module-level `let` variables that store user data +- [ ] No global `$state` that gets set during SSR +- [ ] No singleton service classes with mutable state +- [ ] All user-specific data flows through `locals` +- [ ] All page data comes from load function returns diff --git a/web-player/.agents/skills/svelte5-best-practices/references/typescript.md b/web-player/.agents/skills/svelte5-best-practices/references/typescript.md new file mode 100644 index 0000000..d736bb6 --- /dev/null +++ b/web-player/.agents/skills/svelte5-best-practices/references/typescript.md @@ -0,0 +1,305 @@ +# Svelte 5 TypeScript Reference + +## Table of Contents +- [Props Typing](#props-typing) +- [Generic Components](#generic-components) + +--- + +## Props Typing + +`$props()` requires specific patterns for TypeScript typing. + +### Basic Props Typing + +**Inline typing:** +```svelte + +``` + +**Interface typing (recommended):** +```svelte + +``` + +### Children and Snippets Typing + +```svelte + +``` + +### Callback Props Typing + +```svelte + +``` + +### Rest Props with HTML Attributes + +```svelte + + + +``` + +### Input Element Props + +```svelte + +``` + +### Union Type Props + +```svelte + + + +``` + +### Discriminated Union Props + +```svelte + + +{#if props.type === 'link'} + {@render props.children()} +{:else} + +{/if} +``` + +--- + +## Generic Components + +Svelte 5 uses the `generics` attribute for type-safe reusable components. + +### Basic Generic Component + +```svelte + + + +
      + {#each items as item, index} +
    • {@render children(item, index)}
    • + {/each} +
    + + + + + + {#snippet children(user, index)} + {index + 1}. {user.name} + {/snippet} + +``` + +### Multiple Type Parameters + +```svelte + + +
    + {#each entries as [key, value]} +
    {@render renderKey(key)}
    +
    {@render renderValue(value)}
    + {/each} +
    +``` + +### Constrained Generics + +```svelte + + +{#each items as item} +
    onselect?.(item)} + > + {@render children(item, selected?.id === item.id)} +
    +{/each} +``` + +### Generic with Default Type + +```svelte + +``` + +### Generic Select Component + +```svelte + + + +``` + +### Generic Async Component + +```svelte + + +{#await promise} + {#if loading}{@render loading()}{:else}

    Loading...

    {/if} +{:then data} + {@render children(data)} +{:catch err} + {#if error}{@render error(err)}{:else}

    Error: {err.message}

    {/if} +{/await} +``` diff --git a/web-player/.agents/skills/tailwind-css-patterns/SKILL.md b/web-player/.agents/skills/tailwind-css-patterns/SKILL.md new file mode 100644 index 0000000..fb9d254 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/SKILL.md @@ -0,0 +1,152 @@ +--- +name: tailwind-css-patterns +description: Provides comprehensive Tailwind CSS utility-first styling patterns including responsive design, layout utilities, flexbox, grid, spacing, typography, colors, and modern CSS best practices. Use when styling React/Vue/Svelte components, building responsive layouts, implementing design systems, or optimizing CSS workflow. +allowed-tools: Read, Write, Edit, Glob, Grep, Bash +--- + +# Tailwind CSS Development Patterns + +Expert guide for building modern, responsive user interfaces with Tailwind CSS utility-first framework. Covers v4.1+ features including CSS-first configuration, custom utilities, and enhanced developer experience. + +## Overview + +Provides actionable patterns for responsive, accessible UIs with Tailwind CSS v4.1+. Covers utility composition, dark mode, component patterns, and performance optimization. + +## When to Use + +- Styling React/Vue/Svelte components +- Building responsive layouts and grids +- Implementing design systems +- Adding dark mode support +- Optimizing CSS workflow + +## Quick Reference + +### Responsive Breakpoints + +| Prefix | Min Width | Description | +|--------|-----------|-------------| +| `sm:` | 640px | Small screens | +| `md:` | 768px | Tablets | +| `lg:` | 1024px | Desktops | +| `xl:` | 1280px | Large screens | +| `2xl:` | 1536px | Extra large | + +### Common Patterns + +```html + +
    + Content +
    + + +
    + +
    + + +
    +

    Title

    +

    Description

    +
    +``` + +## Instructions + +1. **Start Mobile-First**: Write base styles for mobile, add responsive prefixes (`sm:`, `md:`, `lg:`) for larger screens +2. **Use Design Tokens**: Leverage Tailwind's spacing, color, and typography scales +3. **Compose Utilities**: Combine multiple utilities for complex styles +4. **Extract Components**: Create reusable component classes for repeated patterns +5. **Configure Theme**: Customize design tokens in `tailwind.config.js` or using `@theme` +6. **Verify Changes**: Test at each breakpoint using DevTools responsive mode. Check for visual regressions and accessibility issues before committing. + +## Examples + +### Responsive Card Component + +```tsx +function ProductCard({ product }: { product: Product }) { + return ( +
    + +
    +

    {product.name}

    + +
    +
    + ); +} +``` + +### Dark Mode Toggle + +```html +
    +

    Title

    +
    +``` + +### Form Input + +```html + +``` + +## Best Practices + +1. **Consistent Spacing**: Use Tailwind's spacing scale (4, 8, 12, 16, etc.) +2. **Color Palette**: Stick to Tailwind's color system for consistency +3. **Component Extraction**: Extract repeated patterns into reusable components +4. **Utility Composition**: Prefer utility classes over `@apply` for maintainability +5. **Semantic HTML**: Use proper HTML elements with Tailwind classes +6. **Performance**: Ensure content paths include all template files for optimal purging +7. **Accessibility**: Include focus styles, ARIA labels, and respect user preferences (reduced-motion) + +## Troubleshooting + +### Classes Not Applying +- **Check content paths**: Ensure all template files are included in `content: []` in config +- **Verify build**: Run `npm run build` to regenerate purged CSS +- **Dev mode**: Use `npx tailwindcss -o` with `--watch` flag for live updates + +### Responsive Styles Not Working +- **Order matters**: Responsive prefixes must come before non-responsive (e.g., `md:flex` not `flex md:flex`) +- **Check breakpoint values**: Verify breakpoints match your design requirements +- **DevTools**: Use browser DevTools responsive mode to test at each breakpoint + +### Dark Mode Issues +- **Verify config**: Ensure `darkMode: 'class'` or `'media'` is set correctly +- **Toggle implementation**: Use `document.documentElement.classList.toggle('dark')` for class strategy +- **Initial flash**: Add `dark` class to `` before body renders + +## Constraints and Warnings + +- **Class Proliferation**: Long class strings reduce readability; extract into components +- **Content Paths**: Misconfigured paths cause classes to be purged in production +- **Arbitrary Values**: Use sparingly; prefer design tokens for consistency +- **Specificity Issues**: Avoid `@apply` with complex selectors +- **Dark Mode**: Requires correct configuration (`class` or `media` strategy) +- **Browser Support**: Check Tailwind docs for compatibility notes + +## References + +- **[references/layout-patterns.md](references/layout-patterns.md)** — Flexbox, grid, spacing, typography, colors +- **[references/component-patterns.md](references/component-patterns.md)** — Cards, navigation, forms, modals, React patterns +- **[references/responsive-design.md](references/responsive-design.md)** — Responsive patterns, dark mode, container queries +- **[references/animations.md](references/animations.md)** — Transitions, transforms, built-in animations, motion preferences +- **[references/performance.md](references/performance.md)** — Bundle optimization, CSS optimization, production builds +- **[references/accessibility.md](references/accessibility.md)** — Focus management, screen readers, color contrast, ARIA +- **[references/configuration.md](references/configuration.md)** — CSS-first config, JavaScript config, plugins, presets +- **[references/reference.md](references/reference.md)** — Additional reference materials + +## External Resources + +- [Tailwind CSS Docs](https://tailwindcss.com/docs) +- [Tailwind UI](https://tailwindui.com) +- [Tailwind Play](https://play.tailwindcss.com) diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/accessibility.md b/web-player/.agents/skills/tailwind-css-patterns/references/accessibility.md new file mode 100644 index 0000000..38f2b67 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/accessibility.md @@ -0,0 +1,167 @@ +# Tailwind CSS Accessibility Guidelines + +## Focus Management + +```html + + + + + + Skip to main content + +``` + +### Focus Visible vs Focus + +```html + + +``` + +--- + +## Screen Reader Support + +```html + + + + + + Documentation + +

    + Learn how to use our API and integration guides +

    + + +
    +

    3 new notifications

    +
    +``` + +### Screen Reader Only Content + +```html +Opens in new window +``` + +--- + +## Color Contrast + +```html + +
    + High contrast text (WCAG AAA) +
    + +
    + Good contrast on colored backgrounds +
    + + +
    + Adjusts for high contrast mode +
    +``` + +### Contrast Guidelines + +- **Normal text**: Minimum 4.5:1 contrast ratio +- **Large text**: Minimum 3:1 contrast ratio +- **UI components**: Minimum 3:1 contrast ratio + +--- + +## Motion Preferences + +```html + +
    + Doesn't animate when user prefers reduced motion +
    + + +
    + Only animates when motion is preferred +
    +``` + +### Reduced Motion Media Query + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +``` + +--- + +## ARIA Patterns with Tailwind + +### Toggle Button + +```html + +``` + +### Alert Dialog + +```html +
    +
    +

    + Are you sure? +

    +

    + This action cannot be undone. +

    +
    + + +
    +
    +
    +``` + +--- + +## Accessibility Checklist + +- [ ] All interactive elements have visible focus indicators +- [ ] Color contrast meets WCAG standards +- [ ] Images have alt text +- [ ] Form inputs have associated labels +- [ ] ARIA labels used for icon-only buttons +- [ ] Animations respect reduced motion preference +- [ ] Skip links provided for keyboard navigation +- [ ] Landmarks (header, main, nav, footer) used appropriately diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/animations.md b/web-player/.agents/skills/tailwind-css-patterns/references/animations.md new file mode 100644 index 0000000..1ea79a9 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/animations.md @@ -0,0 +1,141 @@ +# Tailwind CSS Animations & Transitions + +## Basic Transitions + +```html + +``` + +### Transition Properties + +| Class | Description | +|-------|-------------| +| `transition` | All properties | +| `transition-colors` | Color properties only | +| `transition-opacity` | Opacity only | +| `transition-transform` | Transform only | +| `duration-150` | 150ms duration | +| `duration-300` | 300ms duration | +| `ease-in` | Ease in timing | +| `ease-out` | Ease out timing | +| `ease-in-out` | Ease in-out timing | + +--- + +## Transform Effects + +```html + +
    + Scale on hover +
    + + + + + +
    + Scale up and move up +
    +``` + +--- + +## Built-in Animations + +```html +
    Spinning
    +
    Pulsing
    +
    Bouncing
    +
    Pinging (radar effect)
    +``` + +### Common Use Cases + +```html + + + + + + + +
    +
    +
    +
    +
    + + + + +``` + +--- + +## Custom Animations (v4.1+) + +```css +/* In your CSS with @theme */ +@theme { + --animate-fade-in: fadeIn 0.5s ease-in-out; + --animate-slide-up: slideUp 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +``` + +Usage: + +```html +
    Fades in on load
    +
    Slides up on load
    +``` + +--- + +## Motion Preferences + +Respect user's motion preferences: + +```html + +
    + Doesn't animate when user prefers reduced motion +
    + + +
    + Only animates when motion is preferred +
    +``` + +### Global Reduced Motion Support + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} +``` diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/component-patterns.md b/web-player/.agents/skills/tailwind-css-patterns/references/component-patterns.md new file mode 100644 index 0000000..3a3951a --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/component-patterns.md @@ -0,0 +1,169 @@ +# Tailwind CSS Component Patterns + +## Card Component + +```html +
    + Card image +
    +

    Card Title

    +

    Card description text goes here.

    + +
    +
    +``` + +## Responsive User Card + +```html +
    + Profile +
    +
    + Product Engineer +
    +

    + John Doe +

    +

    + Building amazing products with modern technology. +

    + +
    +
    +``` + +## Navigation Bar + +```html + +``` + +## Form Elements + +```html +
    +
    + + +
    + +
    + + +
    + +
    + + +
    + + +
    +``` + +## Modal/Dialog + +```html +
    +
    +
    +

    Modal Title

    + +
    +

    + Modal content goes here. +

    +
    + + +
    +
    +
    +``` + +## React Button Component with Variants + +```tsx +import { useState } from 'react'; + +function Button({ + variant = 'primary', + size = 'md', + children +}: { + variant?: 'primary' | 'secondary'; + size?: 'sm' | 'md' | 'lg'; + children: React.ReactNode; +}) { + const baseClasses = 'font-semibold rounded transition'; + + const variantClasses = { + primary: 'bg-blue-600 text-white hover:bg-blue-700', + secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300', + }; + + const sizeClasses = { + sm: 'px-3 py-1 text-sm', + md: 'px-4 py-2 text-base', + lg: 'px-6 py-3 text-lg', + }; + + return ( + + ); +} +``` diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/configuration.md b/web-player/.agents/skills/tailwind-css-patterns/references/configuration.md new file mode 100644 index 0000000..ef65ce5 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/configuration.md @@ -0,0 +1,198 @@ +# Tailwind CSS Configuration + +## CSS-First Configuration (v4.1+) + +Use the `@theme` directive for CSS-based configuration: + +```css +/* src/styles.css */ +@import "tailwindcss"; + +@theme { + /* Custom colors */ + --color-brand-50: #f0f9ff; + --color-brand-500: #3b82f6; + --color-brand-900: #1e3a8a; + + /* Custom fonts */ + --font-display: "Inter", system-ui, sans-serif; + --font-mono: "Fira Code", monospace; + + /* Custom spacing */ + --spacing-128: 32rem; + + /* Custom animations */ + --animate-fade-in: fadeIn 0.5s ease-in-out; + + /* Custom breakpoints */ + --breakpoint-3xl: 1920px; +} + +/* Define custom animations */ +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +/* Custom utilities */ +@utility content-auto { + content-visibility: auto; +} +``` + +--- + +## JavaScript Configuration (Legacy) + +```javascript +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx,vue,svelte}", + ], + theme: { + extend: { + colors: { + primary: { + 50: '#f0f9ff', + 500: '#3b82f6', + 900: '#1e3a8a', + }, + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + }, + spacing: { + '128': '32rem', + }, + }, + }, + plugins: [], +} +``` + +--- + +## Vite Integration (v4.1+) + +```javascript +// vite.config.ts +import { defineConfig } from 'vite' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [ + tailwindcss(), + ], +}) +``` + +--- + +## Advanced v4.1 Features + +### Native CSS Custom Properties + +```html +
    + Using CSS custom properties directly +
    +``` + +### Enhanced Arbitrary Values + +```html + +
    + Responsive grid without custom CSS +
    + + +
    + Bounce with custom easing +
    +``` + +### Custom Utilities + +```css +@utility content-auto { + content-visibility: auto; +} + +@utility text-shadow { + text-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +``` + +--- + +## Plugins + +### Custom Plugin Example + +```javascript +// tailwind.config.js +const plugin = require('tailwindcss/plugin') + +module.exports = { + plugins: [ + plugin(function({ addUtilities, addComponents, e, config }) { + // Add custom utilities + addUtilities({ + '.content-auto': { + contentVisibility: 'auto', + }, + }) + + // Add custom components + addComponents({ + '.btn': { + padding: '.5rem 1rem', + borderRadius: '.25rem', + fontWeight: '600', + }, + }) + }), + ], +} +``` + +--- + +## Presets + +### Creating a Reusable Preset + +```javascript +// tailwind-preset.js +module.exports = { + theme: { + extend: { + colors: { + brand: { + DEFAULT: '#3b82f6', + light: '#60a5fa', + dark: '#1d4ed8', + }, + }, + }, + }, + plugins: [ + require('@tailwindcss/forms'), + require('@tailwindcss/typography'), + ], +} +``` + +### Using a Preset + +```javascript +// tailwind.config.js +module.exports = { + presets: [ + require('./tailwind-preset.js'), + ], +} +``` diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/layout-patterns.md b/web-player/.agents/skills/tailwind-css-patterns/references/layout-patterns.md new file mode 100644 index 0000000..22a31ae --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/layout-patterns.md @@ -0,0 +1,222 @@ +# Tailwind CSS Layout Patterns + +## Layout Utilities + +### Flexbox Layouts + +Basic flex container: + +```html +
    +
    Left
    +
    Center
    +
    Right
    +
    +``` + +Responsive flex direction: + +```html +
    +
    Item 1
    +
    Item 2
    +
    Item 3
    +
    +``` + +Common flex patterns: + +```html + +
    +
    Centered Content
    +
    + + +
    + Left + Right +
    + + +
    +
    Item 1
    +
    Item 2
    +
    +``` + +### Grid Layouts + +Basic grid: + +```html +
    +
    Column 1
    +
    Column 2
    +
    Column 3
    +
    +``` + +Responsive grid: + +```html +
    + +
    Item 1
    +
    Item 2
    +
    Item 3
    +
    Item 4
    +
    +``` + +Auto-fit columns: + +```html +
    + +
    +``` + +### Container & Max Width + +Centered container with max width: + +```html +
    + +
    +``` + +Responsive max width: + +```html +
    + +
    +``` + +--- + +## Spacing + +### Padding & Margin + +Uniform spacing: + +```html +
    Padding all sides
    +
    Margin all sides
    +``` + +Individual sides: + +```html +
    + +
    +``` + +Axis-based spacing: + +```html +
    + +
    +``` + +Responsive spacing: + +```html +
    + +
    +``` + +Space between children: + +```html +
    +
    Item 1
    +
    Item 2
    +
    Item 3
    +
    +``` + +--- + +## Typography + +### Font Size & Weight + +```html +

    Large Heading

    +

    Subheading

    +

    Body text

    +Small text +``` + +Responsive typography: + +```html +

    + Responsive Heading +

    +``` + +### Line Height & Letter Spacing + +```html +

    + Text with relaxed line height and wide letter spacing +

    +``` + +### Text Alignment + +```html +

    + Left aligned on mobile, centered on tablet+ +

    +``` + +--- + +## Colors + +### Background Colors + +```html +
    Blue background
    +
    Light gray background
    +
    + Gradient background +
    +``` + +### Text Colors + +```html +

    Dark text

    +

    Blue text

    +

    Error text

    +``` + +### Opacity + +```html +
    + Semi-transparent blue +
    +``` + +--- + +## Responsive Breakpoints Reference + +| Prefix | Min Width | CSS Equivalent | +|--------|-----------|----------------| +| `sm:` | 640px | `@media (min-width: 640px)` | +| `md:` | 768px | `@media (min-width: 768px)` | +| `lg:` | 1024px | `@media (min-width: 1024px)` | +| `xl:` | 1280px | `@media (min-width: 1280px)` | +| `2xl:` | 1536px | `@media (min-width: 1536px)` | diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/performance.md b/web-player/.agents/skills/tailwind-css-patterns/references/performance.md new file mode 100644 index 0000000..8f91846 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/performance.md @@ -0,0 +1,119 @@ +# Tailwind CSS Performance Optimization + +## Bundle Size Optimization + +Configure content sources for optimal purging: + +```javascript +// tailwind.config.js +export default { + content: [ + "./index.html", + "./src/**/*.{js,ts,jsx,tsx,vue,svelte}", + "./node_modules/@mycompany/ui-lib/**/*.{js,ts,jsx,tsx}", + ], + // Enable JIT for faster builds + jit: true, +} +``` + +### Content Path Best Practices + +1. **Be specific**: Don't use `"./**/*"` - it scans too many files +2. **Include UI libraries**: Add paths to component libraries +3. **Exclude tests**: Don't include test files in content paths + +--- + +## CSS Optimization Techniques + +```html + +
    +
    Heavy content that's initially offscreen
    +
    + + +Video thumbnail + + +
    + Complex layout that doesn't affect outside elements +
    +``` + +--- + +## Development Performance (v4.1+) + +```css +/* Enable CSS-first configuration in v4.1 */ +@import "tailwindcss"; + +@theme { + /* Define once, use everywhere */ + --color-brand: #3b82f6; + --font-mono: "Fira Code", monospace; +} + +/* Critical CSS for above-the-fold content */ +@layer critical { + .hero-title { + @apply text-4xl md:text-6xl font-bold; + } +} +``` + +--- + +## Production Build Optimization + +### PurgeCSS Configuration + +```javascript +// tailwind.config.js +module.exports = { + purge: { + enabled: process.env.NODE_ENV === 'production', + content: [ + './src/**/*.html', + './src/**/*.jsx', + './src/**/*.tsx', + ], + options: { + safelist: [ + 'bg-red-500', + 'text-center', + // Classes that shouldn't be purged + ], + }, + }, +} +``` + +### Minification + +```bash +# Use cssnano for minification +npm install -D cssnano + +# In postcss.config.js +module.exports = { + plugins: [ + require('tailwindcss'), + require('autoprefixer'), + ...(process.env.NODE_ENV === 'production' ? [require('cssnano')] : []), + ], +} +``` + +--- + +## Best Practices for Performance + +1. **Use the JIT engine**: Faster builds, smaller output +2. **Configure content correctly**: Only include files that use Tailwind +3. **Minimize custom CSS**: Use Tailwind utilities over custom CSS +4. **Enable purging in production**: Removes unused styles +5. **Use `@layer` for custom styles**: Helps with organization and purging +6. **Avoid `@apply` in components**: Prefer composing utilities in markup diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/reference.md b/web-player/.agents/skills/tailwind-css-patterns/references/reference.md new file mode 100644 index 0000000..001c64b --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/reference.md @@ -0,0 +1,508 @@ +# Tailwind CSS Documentation + +Tailwind CSS is a utility-first CSS framework that generates styles by scanning HTML, JavaScript, and template files for class names. It provides a comprehensive design system through CSS utility classes, enabling rapid UI development without writing custom CSS. The framework operates at build-time, analyzing source files and generating only the CSS classes actually used in the project, resulting in optimized production bundles with zero runtime overhead. + +The framework includes an extensive default color palette (18 colors with 11 shades each), responsive breakpoint system, customizable design tokens via CSS custom properties, and support for dark mode, pseudo-classes, pseudo-elements, and media queries through variant prefixes. Tailwind CSS v4.1 introduces CSS-first configuration using the `@theme` directive, native support for custom utilities via `@utility`, seamless integration with modern build tools through Vite, PostCSS, and framework-specific plugins, and enhanced arbitrary value syntax for maximum flexibility. + +## Installation with Vite + +Installing Tailwind CSS using the Vite plugin for modern JavaScript frameworks. + +```bash +# Create a new Vite project +npm create vite@latest my-project +cd my-project + +# Install Tailwind CSS and Vite plugin +npm install tailwindcss @tailwindcss/vite +``` + +```javascript +// vite.config.ts +import { defineConfig } from 'vite' +import tailwindcss from '@tailwindcss/vite' + +export default defineConfig({ + plugins: [ + tailwindcss(), + ], +}) +``` + +```css +/* src/style.css */ +@import "tailwindcss"; +``` + +```html + + + +# Hello world! + +``` + +## Utility Classes with Variants + +Applying conditional styles using variant prefixes for hover, focus, and responsive breakpoints. + +```html + + Save changes + +Content adapts to color scheme preference + + Submit + +``` + +## Custom Theme Configuration + +Defining custom design tokens using the `@theme` directive in CSS. + +```css +/* app.css */ +@import "tailwindcss"; + +@theme { + /* Custom fonts */ + --font-display: "Satoshi", "sans-serif"; + --font-body: "Inter", system-ui, sans-serif; + + /* Custom colors */ + --color-brand-50: oklch(0.98 0.02 264); + --color-brand-100: oklch(0.95 0.05 264); + --color-brand-500: oklch(0.55 0.22 264); + --color-brand-900: oklch(0.25 0.12 264); + + /* Custom breakpoints */ + --breakpoint-3xl: 120rem; + --breakpoint-4xl: 160rem; + + /* Custom spacing */ + --spacing-18: calc(var(--spacing) * 18); + + /* Custom animations */ + --ease-fluid: cubic-bezier(0.3, 0, 0, 1); + --ease-snappy: cubic-bezier(0.2, 0, 0, 1); +} +``` + +```html + +Custom design system + +``` + +## Arbitrary Values + +Using square bracket notation for one-off custom values without leaving HTML. + +```html + +Pixel-perfect positioning + +Custom hex colors, font sizes, and content + +Any CSS property + +Reference custom properties + +Complex grid layouts + +Font size from CSS variable + +Color from CSS variable + +``` + +## Color System + +Working with Tailwind's comprehensive color palette and opacity modifiers. + +```html + +Color utilities across all properties + +Alpha channel with percentage + +Arbitrary opacity values + +Opacity from CSS variable + +Adapts to color scheme + + +``` + +## Dark Mode + +Implementing dark mode with CSS media queries or manual toggle. + +```html + +Content automatically adapts + + +``` + +```css +/* Manual dark mode toggle with class selector */ +@import "tailwindcss"; + +@custom-variant dark (&:where(.dark, .dark *)); +``` + +```html + +Controlled by .dark class + + + + +``` + +```javascript +// Dark mode toggle logic +// On page load or theme change +document.documentElement.classList.toggle( + "dark", + localStorage.theme === "dark" || + (!("theme" in localStorage) && window.matchMedia("(prefers-color-scheme: dark)").matches) +); + +// User chooses light mode +localStorage.theme = "light"; + +// User chooses dark mode +localStorage.theme = "dark"; + +// User chooses system preference +localStorage.removeItem("theme"); +``` + +## State Variants + +Styling elements based on pseudo-classes and parent/sibling state. + +```html + +- Item content + + +**Title** +Description + +Please provide a valid email address. + + + Option + +``` + +## Responsive Design + +Building mobile-first responsive layouts with breakpoint variants. + +```html + +# Responsive heading + +Text scales with viewport + + +Desktop only + +Mobile only + +Custom breakpoint + +Below medium + +``` + +## Custom Utilities + +Creating reusable custom utility classes with variant support. + +```css +/* Simple custom utility */ +@utility content-auto { + content-visibility: auto; +} + +/* Complex utility with nesting */ +@utility scrollbar-hidden { + &::-webkit-scrollbar { + display: none; + } +} + +/* Functional utility with theme values */ +@theme { + --tab-size-2: 2; + --tab-size-4: 4; + --tab-size-github: 8; +} + +@utility tab-* { + tab-size: --value(--tab-size-*); +} + +/* Supporting arbitrary, bare, and theme values */ +@utility opacity-* { + opacity: --value([percentage]); + opacity: calc(--value(integer) * 1%); + opacity: --value(--opacity-*); +} + +/* Utility with modifiers */ +@utility text-* { + font-size: --value(--text-*, [length]); + line-height: --modifier(--leading-*, [length], [*]); +} + +/* Negative value support */ +@utility inset-* { + inset: --spacing(--value(integer)); + inset: --value([percentage], [length]); +} + +@utility -inset-* { + inset: --spacing(--value(integer) * -1); + inset: calc(--value([percentage], [length]) * -1); +} +``` + +```html + +Custom utilities work with variants + +Variants and arbitrary values supported + +Utility with modifier (font-size/line-height) + +``` + +## Custom Variants + +Registering custom conditional styles with the `@custom-variant` directive. + +```css +/* Simple custom variant */ +@custom-variant theme-midnight (&:where([data-theme="midnight"] *)); + +/* Variant with media query */ +@custom-variant any-hover { + @media (any-hover: hover) { + &:hover { + @slot; + } + } +} + +/* ARIA state variant */ +@custom-variant aria-asc (&[aria-sort="ascending"]); +@custom-variant aria-desc (&[aria-sort="descending"]); + +/* Data attribute variant */ +@custom-variant data-checked (&[data-ui~="checked"]); +``` + +```html + + Midnight theme button + + + Sortable column + +Checked state + +One-off custom selectors + +``` + +## Applying Variants in CSS + +Using the `@variant` directive to apply variants within custom CSS. + +```css +/* Single variant */ +.my-element { + background: white; + + @variant dark { + background: black; + } +} + +/* Nested variants */ +.my-button { + background: white; + + @variant dark { + background: gray; + + @variant hover { + background: black; + } + } +} + +/* Compiled output */ +.my-element { + background: white; +} + +@media (prefers-color-scheme: dark) { + .my-element { + background: black; + } +} +``` + +## Layer Organization + +Organizing custom styles into Tailwind's cascade layers. + +```css +@import "tailwindcss"; + +/* Base styles for HTML elements */ +@layer base { + h1 { + font-size: var(--text-2xl); + font-weight: bold; + } + + h2 { + font-size: var(--text-xl); + font-weight: 600; + } + + body { + font-family: var(--font-body); + } +} + +/* Reusable component classes */ +@layer components { + .btn { + padding: --spacing(2) --spacing(4); + border-radius: var(--radius); + font-weight: 600; + transition: all 150ms; + } + + .btn-primary { + background-color: var(--color-blue-500); + color: white; + } + + .card { + background-color: var(--color-white); + border-radius: var(--radius-lg); + padding: --spacing(6); + box-shadow: var(--shadow-xl); + } + + /* Third-party component overrides */ + .select2-dropdown { + border-radius: var(--radius); + box-shadow: var(--shadow-lg); + } +} +``` + +```html + +Square corners despite card class + + Component with utility overrides + +``` + +## Functions and Directives + +Using Tailwind's CSS functions for dynamic values and opacity adjustments. + +```css +/* Alpha function for opacity */ +.my-element { + color: --alpha(var(--color-lime-300) / 50%); + background: --alpha(var(--color-blue-500) / 25%); +} + +/* Spacing function */ +.my-element { + margin: --spacing(4); + padding: calc(--spacing(6) - 1px); +} + +/* In arbitrary values */ + +/* Source directive for additional content */ +@source "../node_modules/@my-company/ui-lib"; + +/* Apply directive for inline utilities */ +.select2-dropdown { + @apply rounded-b-lg shadow-md; +} + +.select2-search { + @apply rounded border border-gray-300; +} + +.select2-results__group { + @apply text-lg font-bold text-gray-900; +} +``` + +## Pseudo-elements + +Styling ::before, ::after, ::placeholder, and other pseudo-elements. + +```html + + Email + + +- First item +- Second item + +Select this text to see custom colors + +Typography with pseudo-elements + +``` + +## Media Queries + +Conditional styling based on user preferences and device capabilities. + +```html + + Respects user preference + + Only animates if motion allowed + + Adjusts for contrast needs + + +Hidden in portrait mode + +Layout adapts to orientation + + Not shown when printing + +Only visible in print + +Progressive enhancement + +``` + +## Summary + +Tailwind CSS provides a complete utility-first design system that eliminates the need for writing custom CSS in most cases. The framework's primary use cases include rapid prototyping, building production applications with consistent design systems, creating responsive layouts, implementing dark mode, and maintaining design consistency across large teams. By using utility classes directly in markup, developers can iterate quickly, avoid naming conventions, and prevent CSS bloat since only used styles are generated. + +The v4.1 release enhances the developer experience with CSS-first configuration, eliminating JavaScript configuration files for most projects. Integration patterns include using the Vite plugin for modern frameworks, PostCSS for custom build pipelines, the Tailwind CLI for simple projects, and CDN scripts for rapid prototyping. The framework excels at component-driven development when combined with React, Vue, Svelte, or other modern frameworks, where utility classes are co-located with component logic. Custom design systems can be fully defined in CSS using `@theme`, with project-specific utilities and variants extending the framework's capabilities without writing JavaScript plugins. \ No newline at end of file diff --git a/web-player/.agents/skills/tailwind-css-patterns/references/responsive-design.md b/web-player/.agents/skills/tailwind-css-patterns/references/responsive-design.md new file mode 100644 index 0000000..3fad506 --- /dev/null +++ b/web-player/.agents/skills/tailwind-css-patterns/references/responsive-design.md @@ -0,0 +1,159 @@ +# Tailwind CSS Responsive Design & Dark Mode + +## Responsive Design Patterns + +### Mobile-First Responsive Layout + +```html +
    + +
    +
    +

    + Welcome to Our Site +

    +

    + Build amazing things with Tailwind CSS +

    + +
    +
    + +
    +
    +
    +``` + +### Responsive Grid Gallery + +```html +
    +
    + +
    +
    + +
    + +
    +``` + +### Responsive Card Component + +```tsx +function ProductCard({ product }: { product: Product }) { + return ( +
    + {product.name} +
    +

    + {product.name} +

    +

    + {product.description} +

    + +
    +
    + ); +} +``` + +--- + +## Dark Mode + +### Basic Dark Mode Support + +```html +
    +

    Title

    +

    Description

    +
    +``` + +Enable dark mode in tailwind.config.js: + +```javascript +module.exports = { + darkMode: 'class', // or 'media' + // ... +} +``` + +### Dark Mode Toggle (React) + +```tsx +function ThemeToggle() { + const [darkMode, setDarkMode] = useState(false); + + useEffect(() => { + if (darkMode) { + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + } + }, [darkMode]); + + return ( + + ); +} +``` + +### Dark Mode Best Practices + +1. **Use semantic color names**: Instead of `bg-white` use `bg-surface` custom color +2. **Test with real content**: Some colors look good in light but not in dark +3. **Respect system preference**: Use `darkMode: 'media'` for OS-level preference +4. **Smooth transitions**: Add transition utilities for theme changes + +```css +/* Global transition for theme changes */ +* { + @apply transition-colors duration-200; +} +``` + +--- + +## Container Queries (v4.1+) + +Component that responds to its container size, not viewport: + +```html +
    +
    + Text size based on container, not viewport +
    +
    +``` + +Usage in a card component: + +```html +
    +
    + +
    +

    Title

    +

    Description

    +
    +
    +
    +``` diff --git a/web-player/.agents/skills/typescript-advanced-types/SKILL.md b/web-player/.agents/skills/typescript-advanced-types/SKILL.md new file mode 100644 index 0000000..7b603df --- /dev/null +++ b/web-player/.agents/skills/typescript-advanced-types/SKILL.md @@ -0,0 +1,717 @@ +--- +name: typescript-advanced-types +description: Master TypeScript's advanced type system including generics, conditional types, mapped types, template literals, and utility types for building type-safe applications. Use when implementing complex type logic, creating reusable type utilities, or ensuring compile-time type safety in TypeScript projects. +--- + +# TypeScript Advanced Types + +Comprehensive guidance for mastering TypeScript's advanced type system including generics, conditional types, mapped types, template literal types, and utility types for building robust, type-safe applications. + +## When to Use This Skill + +- Building type-safe libraries or frameworks +- Creating reusable generic components +- Implementing complex type inference logic +- Designing type-safe API clients +- Building form validation systems +- Creating strongly-typed configuration objects +- Implementing type-safe state management +- Migrating JavaScript codebases to TypeScript + +## Core Concepts + +### 1. Generics + +**Purpose:** Create reusable, type-flexible components while maintaining type safety. + +**Basic Generic Function:** + +```typescript +function identity(value: T): T { + return value; +} + +const num = identity(42); // Type: number +const str = identity("hello"); // Type: string +const auto = identity(true); // Type inferred: boolean +``` + +**Generic Constraints:** + +```typescript +interface HasLength { + length: number; +} + +function logLength(item: T): T { + console.log(item.length); + return item; +} + +logLength("hello"); // OK: string has length +logLength([1, 2, 3]); // OK: array has length +logLength({ length: 10 }); // OK: object has length +// logLength(42); // Error: number has no length +``` + +**Multiple Type Parameters:** + +```typescript +function merge(obj1: T, obj2: U): T & U { + return { ...obj1, ...obj2 }; +} + +const merged = merge({ name: "John" }, { age: 30 }); +// Type: { name: string } & { age: number } +``` + +### 2. Conditional Types + +**Purpose:** Create types that depend on conditions, enabling sophisticated type logic. + +**Basic Conditional Type:** + +```typescript +type IsString = T extends string ? true : false; + +type A = IsString; // true +type B = IsString; // false +``` + +**Extracting Return Types:** + +```typescript +type ReturnType = T extends (...args: any[]) => infer R ? R : never; + +function getUser() { + return { id: 1, name: "John" }; +} + +type User = ReturnType; +// Type: { id: number; name: string; } +``` + +**Distributive Conditional Types:** + +```typescript +type ToArray = T extends any ? T[] : never; + +type StrOrNumArray = ToArray; +// Type: string[] | number[] +``` + +**Nested Conditions:** + +```typescript +type TypeName = T extends string + ? "string" + : T extends number + ? "number" + : T extends boolean + ? "boolean" + : T extends undefined + ? "undefined" + : T extends Function + ? "function" + : "object"; + +type T1 = TypeName; // "string" +type T2 = TypeName<() => void>; // "function" +``` + +### 3. Mapped Types + +**Purpose:** Transform existing types by iterating over their properties. + +**Basic Mapped Type:** + +```typescript +type Readonly = { + readonly [P in keyof T]: T[P]; +}; + +interface User { + id: number; + name: string; +} + +type ReadonlyUser = Readonly; +// Type: { readonly id: number; readonly name: string; } +``` + +**Optional Properties:** + +```typescript +type Partial = { + [P in keyof T]?: T[P]; +}; + +type PartialUser = Partial; +// Type: { id?: number; name?: string; } +``` + +**Key Remapping:** + +```typescript +type Getters = { + [K in keyof T as `get${Capitalize}`]: () => T[K]; +}; + +interface Person { + name: string; + age: number; +} + +type PersonGetters = Getters; +// Type: { getName: () => string; getAge: () => number; } +``` + +**Filtering Properties:** + +```typescript +type PickByType = { + [K in keyof T as T[K] extends U ? K : never]: T[K]; +}; + +interface Mixed { + id: number; + name: string; + age: number; + active: boolean; +} + +type OnlyNumbers = PickByType; +// Type: { id: number; age: number; } +``` + +### 4. Template Literal Types + +**Purpose:** Create string-based types with pattern matching and transformation. + +**Basic Template Literal:** + +```typescript +type EventName = "click" | "focus" | "blur"; +type EventHandler = `on${Capitalize}`; +// Type: "onClick" | "onFocus" | "onBlur" +``` + +**String Manipulation:** + +```typescript +type UppercaseGreeting = Uppercase<"hello">; // "HELLO" +type LowercaseGreeting = Lowercase<"HELLO">; // "hello" +type CapitalizedName = Capitalize<"john">; // "John" +type UncapitalizedName = Uncapitalize<"John">; // "john" +``` + +**Path Building:** + +```typescript +type Path = T extends object + ? { + [K in keyof T]: K extends string ? `${K}` | `${K}.${Path}` : never; + }[keyof T] + : never; + +interface Config { + server: { + host: string; + port: number; + }; + database: { + url: string; + }; +} + +type ConfigPath = Path; +// Type: "server" | "database" | "server.host" | "server.port" | "database.url" +``` + +### 5. Utility Types + +**Built-in Utility Types:** + +```typescript +// Partial - Make all properties optional +type PartialUser = Partial; + +// Required - Make all properties required +type RequiredUser = Required; + +// Readonly - Make all properties readonly +type ReadonlyUser = Readonly; + +// Pick - Select specific properties +type UserName = Pick; + +// Omit - Remove specific properties +type UserWithoutPassword = Omit; + +// Exclude - Exclude types from union +type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c" + +// Extract - Extract types from union +type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b" + +// NonNullable - Exclude null and undefined +type T3 = NonNullable; // string + +// Record - Create object type with keys K and values T +type PageInfo = Record<"home" | "about", { title: string }>; +``` + +## Advanced Patterns + +### Pattern 1: Type-Safe Event Emitter + +```typescript +type EventMap = { + "user:created": { id: string; name: string }; + "user:updated": { id: string }; + "user:deleted": { id: string }; +}; + +class TypedEventEmitter> { + private listeners: { + [K in keyof T]?: Array<(data: T[K]) => void>; + } = {}; + + on(event: K, callback: (data: T[K]) => void): void { + if (!this.listeners[event]) { + this.listeners[event] = []; + } + this.listeners[event]!.push(callback); + } + + emit(event: K, data: T[K]): void { + const callbacks = this.listeners[event]; + if (callbacks) { + callbacks.forEach((callback) => callback(data)); + } + } +} + +const emitter = new TypedEventEmitter(); + +emitter.on("user:created", (data) => { + console.log(data.id, data.name); // Type-safe! +}); + +emitter.emit("user:created", { id: "1", name: "John" }); +// emitter.emit("user:created", { id: "1" }); // Error: missing 'name' +``` + +### Pattern 2: Type-Safe API Client + +```typescript +type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE"; + +type EndpointConfig = { + "/users": { + GET: { response: User[] }; + POST: { body: { name: string; email: string }; response: User }; + }; + "/users/:id": { + GET: { params: { id: string }; response: User }; + PUT: { params: { id: string }; body: Partial; response: User }; + DELETE: { params: { id: string }; response: void }; + }; +}; + +type ExtractParams = T extends { params: infer P } ? P : never; +type ExtractBody = T extends { body: infer B } ? B : never; +type ExtractResponse = T extends { response: infer R } ? R : never; + +class APIClient>> { + async request( + path: Path, + method: Method, + ...[options]: ExtractParams extends never + ? ExtractBody extends never + ? [] + : [{ body: ExtractBody }] + : [ + { + params: ExtractParams; + body?: ExtractBody; + }, + ] + ): Promise> { + // Implementation here + return {} as any; + } +} + +const api = new APIClient(); + +// Type-safe API calls +const users = await api.request("/users", "GET"); +// Type: User[] + +const newUser = await api.request("/users", "POST", { + body: { name: "John", email: "john@example.com" }, +}); +// Type: User + +const user = await api.request("/users/:id", "GET", { + params: { id: "123" }, +}); +// Type: User +``` + +### Pattern 3: Builder Pattern with Type Safety + +```typescript +type BuilderState = { + [K in keyof T]: T[K] | undefined; +}; + +type RequiredKeys = { + [K in keyof T]-?: {} extends Pick ? never : K; +}[keyof T]; + +type OptionalKeys = { + [K in keyof T]-?: {} extends Pick ? K : never; +}[keyof T]; + +type IsComplete = + RequiredKeys extends keyof S + ? S[RequiredKeys] extends undefined + ? false + : true + : false; + +class Builder = {}> { + private state: S = {} as S; + + set(key: K, value: T[K]): Builder> { + this.state[key] = value; + return this as any; + } + + build(this: IsComplete extends true ? this : never): T { + return this.state as T; + } +} + +interface User { + id: string; + name: string; + email: string; + age?: number; +} + +const builder = new Builder(); + +const user = builder + .set("id", "1") + .set("name", "John") + .set("email", "john@example.com") + .build(); // OK: all required fields set + +// const incomplete = builder +// .set("id", "1") +// .build(); // Error: missing required fields +``` + +### Pattern 4: Deep Readonly/Partial + +```typescript +type DeepReadonly = { + readonly [P in keyof T]: T[P] extends object + ? T[P] extends Function + ? T[P] + : DeepReadonly + : T[P]; +}; + +type DeepPartial = { + [P in keyof T]?: T[P] extends object + ? T[P] extends Array + ? Array> + : DeepPartial + : T[P]; +}; + +interface Config { + server: { + host: string; + port: number; + ssl: { + enabled: boolean; + cert: string; + }; + }; + database: { + url: string; + pool: { + min: number; + max: number; + }; + }; +} + +type ReadonlyConfig = DeepReadonly; +// All nested properties are readonly + +type PartialConfig = DeepPartial; +// All nested properties are optional +``` + +### Pattern 5: Type-Safe Form Validation + +```typescript +type ValidationRule = { + validate: (value: T) => boolean; + message: string; +}; + +type FieldValidation = { + [K in keyof T]?: ValidationRule[]; +}; + +type ValidationErrors = { + [K in keyof T]?: string[]; +}; + +class FormValidator> { + constructor(private rules: FieldValidation) {} + + validate(data: T): ValidationErrors | null { + const errors: ValidationErrors = {}; + let hasErrors = false; + + for (const key in this.rules) { + const fieldRules = this.rules[key]; + const value = data[key]; + + if (fieldRules) { + const fieldErrors: string[] = []; + + for (const rule of fieldRules) { + if (!rule.validate(value)) { + fieldErrors.push(rule.message); + } + } + + if (fieldErrors.length > 0) { + errors[key] = fieldErrors; + hasErrors = true; + } + } + } + + return hasErrors ? errors : null; + } +} + +interface LoginForm { + email: string; + password: string; +} + +const validator = new FormValidator({ + email: [ + { + validate: (v) => v.includes("@"), + message: "Email must contain @", + }, + { + validate: (v) => v.length > 0, + message: "Email is required", + }, + ], + password: [ + { + validate: (v) => v.length >= 8, + message: "Password must be at least 8 characters", + }, + ], +}); + +const errors = validator.validate({ + email: "invalid", + password: "short", +}); +// Type: { email?: string[]; password?: string[]; } | null +``` + +### Pattern 6: Discriminated Unions + +```typescript +type Success = { + status: "success"; + data: T; +}; + +type Error = { + status: "error"; + error: string; +}; + +type Loading = { + status: "loading"; +}; + +type AsyncState = Success | Error | Loading; + +function handleState(state: AsyncState): void { + switch (state.status) { + case "success": + console.log(state.data); // Type: T + break; + case "error": + console.log(state.error); // Type: string + break; + case "loading": + console.log("Loading..."); + break; + } +} + +// Type-safe state machine +type State = + | { type: "idle" } + | { type: "fetching"; requestId: string } + | { type: "success"; data: any } + | { type: "error"; error: Error }; + +type Event = + | { type: "FETCH"; requestId: string } + | { type: "SUCCESS"; data: any } + | { type: "ERROR"; error: Error } + | { type: "RESET" }; + +function reducer(state: State, event: Event): State { + switch (state.type) { + case "idle": + return event.type === "FETCH" + ? { type: "fetching", requestId: event.requestId } + : state; + case "fetching": + if (event.type === "SUCCESS") { + return { type: "success", data: event.data }; + } + if (event.type === "ERROR") { + return { type: "error", error: event.error }; + } + return state; + case "success": + case "error": + return event.type === "RESET" ? { type: "idle" } : state; + } +} +``` + +## Type Inference Techniques + +### 1. Infer Keyword + +```typescript +// Extract array element type +type ElementType = T extends (infer U)[] ? U : never; + +type NumArray = number[]; +type Num = ElementType; // number + +// Extract promise type +type PromiseType = T extends Promise ? U : never; + +type AsyncNum = PromiseType>; // number + +// Extract function parameters +type Parameters = T extends (...args: infer P) => any ? P : never; + +function foo(a: string, b: number) {} +type FooParams = Parameters; // [string, number] +``` + +### 2. Type Guards + +```typescript +function isString(value: unknown): value is string { + return typeof value === "string"; +} + +function isArrayOf( + value: unknown, + guard: (item: unknown) => item is T, +): value is T[] { + return Array.isArray(value) && value.every(guard); +} + +const data: unknown = ["a", "b", "c"]; + +if (isArrayOf(data, isString)) { + data.forEach((s) => s.toUpperCase()); // Type: string[] +} +``` + +### 3. Assertion Functions + +```typescript +function assertIsString(value: unknown): asserts value is string { + if (typeof value !== "string") { + throw new Error("Not a string"); + } +} + +function processValue(value: unknown) { + assertIsString(value); + // value is now typed as string + console.log(value.toUpperCase()); +} +``` + +## Best Practices + +1. **Use `unknown` over `any`**: Enforce type checking +2. **Prefer `interface` for object shapes**: Better error messages +3. **Use `type` for unions and complex types**: More flexible +4. **Leverage type inference**: Let TypeScript infer when possible +5. **Create helper types**: Build reusable type utilities +6. **Use const assertions**: Preserve literal types +7. **Avoid type assertions**: Use type guards instead +8. **Document complex types**: Add JSDoc comments +9. **Use strict mode**: Enable all strict compiler options +10. **Test your types**: Use type tests to verify type behavior + +## Type Testing + +```typescript +// Type assertion tests +type AssertEqual = [T] extends [U] + ? [U] extends [T] + ? true + : false + : false; + +type Test1 = AssertEqual; // true +type Test2 = AssertEqual; // false +type Test3 = AssertEqual; // false + +// Expect error helper +type ExpectError = T; + +// Example usage +type ShouldError = ExpectError>; +``` + +## Common Pitfalls + +1. **Over-using `any`**: Defeats the purpose of TypeScript +2. **Ignoring strict null checks**: Can lead to runtime errors +3. **Too complex types**: Can slow down compilation +4. **Not using discriminated unions**: Misses type narrowing opportunities +5. **Forgetting readonly modifiers**: Allows unintended mutations +6. **Circular type references**: Can cause compiler errors +7. **Not handling edge cases**: Like empty arrays or null values + +## Performance Considerations + +- Avoid deeply nested conditional types +- Use simple types when possible +- Cache complex type computations +- Limit recursion depth in recursive types +- Use build tools to skip type checking in production diff --git a/web-player/.agents/skills/vite/GENERATION.md b/web-player/.agents/skills/vite/GENERATION.md new file mode 100644 index 0000000..a4b1c4a --- /dev/null +++ b/web-player/.agents/skills/vite/GENERATION.md @@ -0,0 +1,5 @@ +# Generation Info + +- **Source:** `sources/vite` +- **Git SHA:** `c47015eba4f0de255218c35769628d87152216ca` +- **Generated:** 2026-01-31 diff --git a/web-player/.agents/skills/vite/SKILL.md b/web-player/.agents/skills/vite/SKILL.md new file mode 100644 index 0000000..0a00766 --- /dev/null +++ b/web-player/.agents/skills/vite/SKILL.md @@ -0,0 +1,72 @@ +--- +name: vite +description: Vite build tool configuration, plugin API, SSR, and Vite 8 Rolldown migration. Use when working with Vite projects, vite.config.ts, Vite plugins, or building libraries/SSR apps with Vite. +metadata: + author: Anthony Fu + version: "2026.1.31" + source: Generated from https://github.com/vitejs/vite, scripts at https://github.com/antfu/skills +--- + +# Vite + +> Based on Vite 8 beta (Rolldown-powered). Vite 8 uses Rolldown bundler and Oxc transformer. + +Vite is a next-generation frontend build tool with fast dev server (native ESM + HMR) and optimized production builds. + +## Preferences + +- Use TypeScript: prefer `vite.config.ts` +- Always use ESM, avoid CommonJS + +## Core + +| Topic | Description | Reference | +|-------|-------------|-----------| +| Configuration | `vite.config.ts`, `defineConfig`, conditional configs, `loadEnv` | [core-config](references/core-config.md) | +| Features | `import.meta.glob`, asset queries (`?raw`, `?url`), `import.meta.env`, HMR API | [core-features](references/core-features.md) | +| Plugin API | Vite-specific hooks, virtual modules, plugin ordering | [core-plugin-api](references/core-plugin-api.md) | + +## Build & SSR + +| Topic | Description | Reference | +|-------|-------------|-----------| +| Build & SSR | Library mode, SSR middleware mode, `ssrLoadModule`, JavaScript API | [build-and-ssr](references/build-and-ssr.md) | + +## Advanced + +| Topic | Description | Reference | +|-------|-------------|-----------| +| Environment API | Vite 6+ multi-environment support, custom runtimes | [environment-api](references/environment-api.md) | +| Rolldown Migration | Vite 8 changes: Rolldown bundler, Oxc transformer, config migration | [rolldown-migration](references/rolldown-migration.md) | + +## Quick Reference + +### CLI Commands + +```bash +vite # Start dev server +vite build # Production build +vite preview # Preview production build +vite build --ssr # SSR build +``` + +### Common Config + +```ts +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [], + resolve: { alias: { '@': '/src' } }, + server: { port: 3000, proxy: { '/api': 'http://localhost:8080' } }, + build: { target: 'esnext', outDir: 'dist' }, +}) +``` + +### Official Plugins + +- `@vitejs/plugin-vue` - Vue 3 SFC support +- `@vitejs/plugin-vue-jsx` - Vue 3 JSX +- `@vitejs/plugin-react` - React with Oxc/Babel +- `@vitejs/plugin-react-swc` - React with SWC +- `@vitejs/plugin-legacy` - Legacy browser support diff --git a/web-player/.agents/skills/vite/references/build-and-ssr.md b/web-player/.agents/skills/vite/references/build-and-ssr.md new file mode 100644 index 0000000..ac46a9a --- /dev/null +++ b/web-player/.agents/skills/vite/references/build-and-ssr.md @@ -0,0 +1,164 @@ +--- +name: vite-build-ssr +description: Vite library mode, multi-page apps, JavaScript API, and SSR guidance +--- + +# Build and SSR + +## Library Mode + +Build a library for distribution: + +```ts +// vite.config.ts +import { resolve } from 'node:path' +import { defineConfig } from 'vite' + +export default defineConfig({ + build: { + lib: { + entry: resolve(import.meta.dirname, 'lib/main.ts'), + name: 'MyLib', + fileName: 'my-lib', + }, + rolldownOptions: { + external: ['vue', 'react'], + output: { + globals: { + vue: 'Vue', + react: 'React', + }, + }, + }, + }, +}) +``` + +### Multiple Entries + +```ts +build: { + lib: { + entry: { + 'my-lib': resolve(import.meta.dirname, 'lib/main.ts'), + secondary: resolve(import.meta.dirname, 'lib/secondary.ts'), + }, + name: 'MyLib', + }, +} +``` + +### Output Formats + +- Single entry: `es` and `umd` +- Multiple entries: `es` and `cjs` + +### Package.json Setup + +```json +{ + "name": "my-lib", + "type": "module", + "files": ["dist"], + "main": "./dist/my-lib.umd.cjs", + "module": "./dist/my-lib.js", + "exports": { + ".": { + "import": "./dist/my-lib.js", + "require": "./dist/my-lib.umd.cjs" + }, + "./style.css": "./dist/my-lib.css" + } +} +``` + +## Multi-Page App + +```ts +export default defineConfig({ + build: { + rolldownOptions: { + input: { + main: resolve(import.meta.dirname, 'index.html'), + nested: resolve(import.meta.dirname, 'nested/index.html'), + }, + }, + }, +}) +``` + +## SSR Development + +**Note:** Vite's SSR support is **low-level** and designed mostly for meta-framework authors, not application developers. If you need SSR for your app, use a Vite-based meta-framework instead: + +- **Nuxt** (Vue) - https://nuxt.com +- **SvelteKit** (Svelte) - https://svelte.dev/docs/kit +- **SolidStart** (Solid) - https://start.solidjs.com +- **TanStack Start** (React) - https://tanstack.com/start + +These frameworks build on top of Vite's SSR primitives so you don't have to wire them up yourself. + +**Need a server?** Consider [Nitro](https://nitro.build) -- think of it as "Vite for servers." Nitro provides a portable, framework-agnostic server layer with file-based API routing, auto-imports, and deployment presets for dozens of platforms (Node.js, Deno, Bun, Cloudflare Workers, Vercel, Netlify, etc.). It integrates naturally with Vite and is what powers Nuxt's server engine. See the [Nitro docs](https://nitro.build) for more details. + +## JavaScript API + +### createServer + +```ts +import { createServer } from 'vite' + +const server = await createServer({ + configFile: false, + root: import.meta.dirname, + server: { port: 1337 }, +}) + +await server.listen() +server.printUrls() +``` + +### build + +```ts +import { build } from 'vite' + +await build({ + root: './project', + build: { outDir: 'dist' }, +}) +``` + +### preview + +```ts +import { preview } from 'vite' + +const previewServer = await preview({ + preview: { port: 8080, open: true }, +}) +previewServer.printUrls() +``` + +### resolveConfig + +```ts +import { resolveConfig } from 'vite' + +const config = await resolveConfig({}, 'build') +``` + +### loadEnv + +```ts +import { loadEnv } from 'vite' + +const env = loadEnv('development', process.cwd(), '') +// Loads all env vars (empty prefix = no filtering) +``` + + diff --git a/web-player/.agents/skills/vite/references/core-config.md b/web-player/.agents/skills/vite/references/core-config.md new file mode 100644 index 0000000..039ba52 --- /dev/null +++ b/web-player/.agents/skills/vite/references/core-config.md @@ -0,0 +1,162 @@ +--- +name: vite-config +description: Vite configuration patterns using vite.config.ts +--- + +# Vite Configuration + +## Basic Setup + +```ts +// vite.config.ts +import { defineConfig } from 'vite' + +export default defineConfig({ + // config options +}) +``` + +Vite auto-resolves `vite.config.ts` from project root. Supports ES modules syntax regardless of `package.json` type. + +## Conditional Config + +Export a function to access command and mode: + +```ts +export default defineConfig(({ command, mode, isSsrBuild, isPreview }) => { + if (command === 'serve') { + return { /* dev config */ } + } else { + return { /* build config */ } + } +}) +``` + +- `command`: `'serve'` during dev, `'build'` for production +- `mode`: `'development'` or `'production'` (or custom via `--mode`) + +## Async Config + +```ts +export default defineConfig(async ({ command, mode }) => { + const data = await fetchSomething() + return { /* config */ } +}) +``` + +## Using Environment Variables in Config + +`.env` files are loaded **after** config resolution. Use `loadEnv` to access them in config: + +```ts +import { defineConfig, loadEnv } from 'vite' + +export default defineConfig(({ mode }) => { + // Load env files from cwd, include all vars (empty prefix) + const env = loadEnv(mode, process.cwd(), '') + + return { + define: { + __APP_ENV__: JSON.stringify(env.APP_ENV), + }, + server: { + port: env.APP_PORT ? Number(env.APP_PORT) : 5173, + }, + } +}) +``` + +## Key Config Options + +### resolve.alias + +```ts +export default defineConfig({ + resolve: { + alias: { + '@': '/src', + '~': '/src', + }, + }, +}) +``` + +### define (Global Constants) + +```ts +export default defineConfig({ + define: { + __APP_VERSION__: JSON.stringify('1.0.0'), + __API_URL__: 'window.__backend_api_url', + }, +}) +``` + +Values must be JSON-serializable or single identifiers. Non-strings auto-wrapped with `JSON.stringify`. + +### plugins + +```ts +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], +}) +``` + +Plugins array is flattened; falsy values ignored. + +### server.proxy + +```ts +export default defineConfig({ + server: { + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + rewrite: (path) => path.replace(/^\/api/, ''), + }, + }, + }, +}) +``` + +### build.target + +Default: Baseline Widely Available browsers. Customize: + +```ts +export default defineConfig({ + build: { + target: 'esnext', // or 'es2020', ['chrome90', 'firefox88'] + }, +}) +``` + +## TypeScript Intellisense + +For plain JS config files: + +```js +/** @type {import('vite').UserConfig} */ +export default { + // ... +} +``` + +Or use `satisfies`: + +```ts +import type { UserConfig } from 'vite' + +export default { + // ... +} satisfies UserConfig +``` + + diff --git a/web-player/.agents/skills/vite/references/core-features.md b/web-player/.agents/skills/vite/references/core-features.md new file mode 100644 index 0000000..1403ac9 --- /dev/null +++ b/web-player/.agents/skills/vite/references/core-features.md @@ -0,0 +1,205 @@ +--- +name: vite-features +description: Vite-specific import patterns and runtime features +--- + +# Vite Features + +## Glob Import + +Import multiple modules matching a pattern: + +```ts +const modules = import.meta.glob('./dir/*.ts') +// { './dir/foo.ts': () => import('./dir/foo.ts'), ... } + +for (const path in modules) { + modules[path]().then((mod) => { + console.log(path, mod) + }) +} +``` + +### Eager Loading + +```ts +const modules = import.meta.glob('./dir/*.ts', { eager: true }) +// Modules loaded immediately, no dynamic import +``` + +### Named Imports + +```ts +const modules = import.meta.glob('./dir/*.ts', { import: 'setup' }) +// Only imports the 'setup' export from each module + +const defaults = import.meta.glob('./dir/*.ts', { import: 'default', eager: true }) +``` + +### Multiple Patterns + +```ts +const modules = import.meta.glob(['./dir/*.ts', './another/*.ts']) +``` + +### Negative Patterns + +```ts +const modules = import.meta.glob(['./dir/*.ts', '!**/ignored.ts']) +``` + +### Custom Queries + +```ts +const svgRaw = import.meta.glob('./icons/*.svg', { query: '?raw', import: 'default' }) +const svgUrls = import.meta.glob('./icons/*.svg', { query: '?url', import: 'default' }) +``` + +## Asset Import Queries + +### URL Import + +```ts +import imgUrl from './img.png' +// Returns resolved URL: '/src/img.png' (dev) or '/assets/img.2d8efhg.png' (build) +``` + +### Explicit URL + +```ts +import workletUrl from './worklet.js?url' +``` + +### Raw String + +```ts +import shaderCode from './shader.glsl?raw' +``` + +### Inline/No-Inline + +```ts +import inlined from './small.png?inline' // Force base64 inline +import notInlined from './large.png?no-inline' // Force separate file +``` + +### Web Workers + +```ts +import Worker from './worker.ts?worker' +const worker = new Worker() + +// Or inline: +import InlineWorker from './worker.ts?worker&inline' +``` + +Preferred pattern using constructor: + +```ts +const worker = new Worker(new URL('./worker.ts', import.meta.url), { + type: 'module', +}) +``` + +## Environment Variables + +### Built-in Constants + +```ts +import.meta.env.MODE // 'development' | 'production' | custom +import.meta.env.BASE_URL // Base URL from config +import.meta.env.PROD // true in production +import.meta.env.DEV // true in development +import.meta.env.SSR // true when running in server +``` + +### Custom Variables + +Only `VITE_` prefixed vars exposed to client: + +``` +# .env +VITE_API_URL=https://api.example.com +DB_PASSWORD=secret # NOT exposed to client +``` + +```ts +console.log(import.meta.env.VITE_API_URL) // works +console.log(import.meta.env.DB_PASSWORD) // undefined +``` + +### Mode-specific Files + +``` +.env # always loaded +.env.local # always loaded, gitignored +.env.[mode] # only in specified mode +.env.[mode].local # only in specified mode, gitignored +``` + +### TypeScript Support + +```ts +// vite-env.d.ts +interface ImportMetaEnv { + readonly VITE_API_URL: string +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} +``` + +### HTML Replacement + +```html +

    Running in %MODE%

    + +``` + +## CSS Modules + +Any `.module.css` file treated as CSS module: + +```ts +import styles from './component.module.css' +element.className = styles.button +``` + +With camelCase conversion: + +```ts +// .my-class -> myClass (if css.modules.localsConvention configured) +import { myClass } from './component.module.css' +``` + +## JSON Import + +```ts +import pkg from './package.json' +import { version } from './package.json' // Named import with tree-shaking +``` + +## HMR API + +```ts +if (import.meta.hot) { + import.meta.hot.accept((newModule) => { + // Handle update + }) + + import.meta.hot.dispose((data) => { + // Cleanup before module is replaced + }) + + import.meta.hot.invalidate() // Force full reload +} +``` + + diff --git a/web-player/.agents/skills/vite/references/core-plugin-api.md b/web-player/.agents/skills/vite/references/core-plugin-api.md new file mode 100644 index 0000000..c115851 --- /dev/null +++ b/web-player/.agents/skills/vite/references/core-plugin-api.md @@ -0,0 +1,235 @@ +--- +name: vite-plugin-api +description: Vite plugin authoring with Vite-specific hooks +--- + +# Vite Plugin API + +Vite plugins extend Rolldown's plugin interface with Vite-specific hooks. + +## Basic Structure + +```ts +function myPlugin(): Plugin { + return { + name: 'my-plugin', + // hooks... + } +} +``` + +## Vite-Specific Hooks + +### config + +Modify config before resolution: + +```ts +const plugin = () => ({ + name: 'add-alias', + config: () => ({ + resolve: { + alias: { foo: 'bar' }, + }, + }), +}) +``` + +### configResolved + +Access final resolved config: + +```ts +const plugin = () => { + let config: ResolvedConfig + return { + name: 'read-config', + configResolved(resolvedConfig) { + config = resolvedConfig + }, + transform(code, id) { + if (config.command === 'serve') { /* dev */ } + }, + } +} +``` + +### configureServer + +Add custom middleware to dev server: + +```ts +const plugin = () => ({ + name: 'custom-middleware', + configureServer(server) { + server.middlewares.use((req, res, next) => { + // handle request + next() + }) + }, +}) +``` + +Return function to run **after** internal middlewares: + +```ts +configureServer(server) { + return () => { + server.middlewares.use((req, res, next) => { + // runs after Vite's middlewares + }) + } +} +``` + +### transformIndexHtml + +Transform HTML entry files: + +```ts +const plugin = () => ({ + name: 'html-transform', + transformIndexHtml(html) { + return html.replace(/(.*?)<\/title>/, '<title>New Title') + }, +}) +``` + +Inject tags: + +```ts +transformIndexHtml() { + return [ + { tag: 'script', attrs: { src: '/inject.js' }, injectTo: 'body' }, + ] +} +``` + +### handleHotUpdate + +Custom HMR handling: + +```ts +handleHotUpdate({ server, modules, timestamp }) { + server.ws.send({ type: 'custom', event: 'special-update', data: {} }) + return [] // empty = skip default HMR +} +``` + +## Virtual Modules + +Serve virtual content without files on disk: + +```ts +const plugin = () => { + const virtualModuleId = 'virtual:my-module' + const resolvedId = '\0' + virtualModuleId + + return { + name: 'virtual-module', + resolveId(id) { + if (id === virtualModuleId) return resolvedId + }, + load(id) { + if (id === resolvedId) { + return `export const msg = "from virtual module"` + } + }, + } +} +``` + +Usage: + +```ts +import { msg } from 'virtual:my-module' +``` + +Convention: prefix user-facing path with `virtual:`, prefix resolved id with `\0`. + +## Plugin Ordering + +Use `enforce` to control execution order: + +```ts +{ + name: 'pre-plugin', + enforce: 'pre', // runs before core plugins +} + +{ + name: 'post-plugin', + enforce: 'post', // runs after build plugins +} +``` + +Order: Alias → `enforce: 'pre'` → Core → User (no enforce) → Build → `enforce: 'post'` → Post-build + +## Conditional Application + +```ts +{ + name: 'build-only', + apply: 'build', // or 'serve' +} + +// Function form: +{ + apply(config, { command }) { + return command === 'build' && !config.build.ssr + } +} +``` + +## Universal Hooks (from Rolldown) + +These work in both dev and build: + +- `resolveId(id, importer)` - Resolve import paths +- `load(id)` - Load module content +- `transform(code, id)` - Transform module code + +```ts +transform(code, id) { + if (id.endsWith('.custom')) { + return { code: compile(code), map: null } + } +} +``` + +## Client-Server Communication + +Server to client: + +```ts +configureServer(server) { + server.ws.send('my:event', { msg: 'hello' }) +} +``` + +Client side: + +```ts +if (import.meta.hot) { + import.meta.hot.on('my:event', (data) => { + console.log(data.msg) + }) +} +``` + +Client to server: + +```ts +// Client +import.meta.hot.send('my:from-client', { msg: 'Hey!' }) + +// Server +server.ws.on('my:from-client', (data, client) => { + client.send('my:ack', { msg: 'Got it!' }) +}) +``` + + diff --git a/web-player/.agents/skills/vite/references/environment-api.md b/web-player/.agents/skills/vite/references/environment-api.md new file mode 100644 index 0000000..006ff7f --- /dev/null +++ b/web-player/.agents/skills/vite/references/environment-api.md @@ -0,0 +1,108 @@ +--- +name: vite-environment-api +description: Vite 6+ Environment API for multiple runtime environments +--- + +# Environment API (Vite 6+) + +The Environment API formalizes multiple runtime environments beyond the traditional client/SSR split. + +## Concept + +Before Vite 6: Two implicit environments (`client` and `ssr`). + +Vite 6+: Configure as many environments as needed (browser, node server, edge server, etc.). + +## Basic Configuration + +For SPA/MPA, nothing changes—options apply to the implicit `client` environment: + +```ts +export default defineConfig({ + build: { sourcemap: false }, + optimizeDeps: { include: ['lib'] }, +}) +``` + +## Multiple Environments + +```ts +export default defineConfig({ + build: { sourcemap: false }, // Inherited by all environments + optimizeDeps: { include: ['lib'] }, // Client only + environments: { + // SSR environment + server: {}, + // Edge runtime environment + edge: { + resolve: { noExternal: true }, + }, + }, +}) +``` + +Environments inherit top-level config. Some options (like `optimizeDeps`) only apply to `client` by default. + +## Environment Options + +```ts +interface EnvironmentOptions { + define?: Record + resolve?: EnvironmentResolveOptions + optimizeDeps: DepOptimizationOptions + consumer?: 'client' | 'server' + dev: DevOptions + build: BuildOptions +} +``` + +## Custom Environment Instances + +Runtime providers can define custom environments: + +```ts +import { customEnvironment } from 'vite-environment-provider' + +export default defineConfig({ + environments: { + ssr: customEnvironment({ + build: { outDir: '/dist/ssr' }, + }), + }, +}) +``` + +Example: Cloudflare's Vite plugin runs code in `workerd` runtime during development. + +## Backward Compatibility + +- `server.moduleGraph` returns mixed client/SSR view +- `ssrLoadModule` still works +- Existing SSR apps work unchanged + +## When to Use + +- **End users**: Usually don't need to configure—frameworks handle it +- **Plugin authors**: Use for environment-aware transformations +- **Framework authors**: Create custom environments for their runtime needs + +## Plugin Environment Access + +Plugins can access environment in hooks: + +```ts +{ + name: 'env-aware', + transform(code, id, options) { + if (options?.ssr) { + // SSR-specific transform + } + }, +} +``` + + diff --git a/web-player/.agents/skills/vite/references/rolldown-migration.md b/web-player/.agents/skills/vite/references/rolldown-migration.md new file mode 100644 index 0000000..28d6d76 --- /dev/null +++ b/web-player/.agents/skills/vite/references/rolldown-migration.md @@ -0,0 +1,157 @@ +--- +name: vite-rolldown +description: Vite 8 Rolldown bundler and Oxc transformer migration +--- + +# Rolldown Migration (Vite 8) + +Vite 8 replaces esbuild+Rollup with Rolldown, a unified Rust-based bundler. + +## What Changed + +| Before (Vite 7) | After (Vite 8) | +|-----------------|----------------| +| esbuild (dev transform) | Oxc Transformer | +| esbuild (dep pre-bundling) | Rolldown | +| Rollup (production build) | Rolldown | +| `rollupOptions` | `rolldownOptions` | +| `esbuild` option | `oxc` option | + +## Performance Impact + +- 10-30x faster than Rollup for production builds +- Matches esbuild's dev performance +- Unified behavior between dev and build + +## Config Migration + +### rollupOptions → rolldownOptions + +```ts +// Before (Vite 7) +export default defineConfig({ + build: { + rollupOptions: { + external: ['vue'], + output: { globals: { vue: 'Vue' } }, + }, + }, +}) + +// After (Vite 8) +export default defineConfig({ + build: { + rolldownOptions: { + external: ['vue'], + output: { globals: { vue: 'Vue' } }, + }, + }, +}) +``` + +### esbuild → oxc + +```ts +// Before (Vite 7) +export default defineConfig({ + esbuild: { + jsxFactory: 'h', + jsxFragment: 'Fragment', + }, +}) + +// After (Vite 8) +export default defineConfig({ + oxc: { + jsx: { + runtime: 'classic', + pragma: 'h', + pragmaFrag: 'Fragment', + }, + }, +}) +``` + +### JSX Configuration + +```ts +export default defineConfig({ + oxc: { + jsx: { + runtime: 'automatic', // or 'classic' + importSource: 'react', // for automatic runtime + }, + jsxInject: `import React from 'react'`, // auto-inject + }, +}) +``` + +### Custom Transform Targets + +```ts +export default defineConfig({ + oxc: { + include: ['**/*.ts', '**/*.tsx'], + exclude: ['node_modules/**'], + }, +}) +``` + +## Plugin Compatibility + +Most Vite plugins work unchanged. Rolldown supports Rollup's plugin API. + +If a plugin only works during build: + +```ts +{ + ...rollupPlugin(), + enforce: 'post', + apply: 'build', +} +``` + +## New Capabilities + +Rolldown unlocks features not possible before: + +- Full bundle mode (experimental) +- Module-level persistent cache +- More flexible chunk splitting +- Module Federation support + +## Gradual Migration + +For large projects, migrate via `rolldown-vite` first: + +```bash +# Step 1: Test with rolldown-vite +pnpm add -D rolldown-vite + +# Replace vite import in config +import { defineConfig } from 'rolldown-vite' + +# Step 2: Once stable, upgrade to Vite 8 +pnpm add -D vite@8 +``` + +## Overriding Vite in Frameworks + +When framework depends on older Vite: + +```json +{ + "pnpm": { + "overrides": { + "vite": "8.0.0" + } + } +} +``` + + diff --git a/web-player/.claude/skills/accessibility b/web-player/.claude/skills/accessibility new file mode 120000 index 0000000..33d9d92 --- /dev/null +++ b/web-player/.claude/skills/accessibility @@ -0,0 +1 @@ +../../.agents/skills/accessibility \ No newline at end of file diff --git a/web-player/.claude/skills/frontend-design b/web-player/.claude/skills/frontend-design new file mode 120000 index 0000000..712f694 --- /dev/null +++ b/web-player/.claude/skills/frontend-design @@ -0,0 +1 @@ +../../.agents/skills/frontend-design \ No newline at end of file diff --git a/web-player/.claude/skills/nodejs-backend-patterns b/web-player/.claude/skills/nodejs-backend-patterns new file mode 120000 index 0000000..1c65884 --- /dev/null +++ b/web-player/.claude/skills/nodejs-backend-patterns @@ -0,0 +1 @@ +../../.agents/skills/nodejs-backend-patterns \ No newline at end of file diff --git a/web-player/.claude/skills/nodejs-best-practices b/web-player/.claude/skills/nodejs-best-practices new file mode 120000 index 0000000..885c2c2 --- /dev/null +++ b/web-player/.claude/skills/nodejs-best-practices @@ -0,0 +1 @@ +../../.agents/skills/nodejs-best-practices \ No newline at end of file diff --git a/web-player/.claude/skills/seo b/web-player/.claude/skills/seo new file mode 120000 index 0000000..946efb7 --- /dev/null +++ b/web-player/.claude/skills/seo @@ -0,0 +1 @@ +../../.agents/skills/seo \ No newline at end of file diff --git a/web-player/.claude/skills/svelte-code-writer b/web-player/.claude/skills/svelte-code-writer new file mode 120000 index 0000000..2ee6909 --- /dev/null +++ b/web-player/.claude/skills/svelte-code-writer @@ -0,0 +1 @@ +../../.agents/skills/svelte-code-writer \ No newline at end of file diff --git a/web-player/.claude/skills/svelte5-best-practices b/web-player/.claude/skills/svelte5-best-practices new file mode 120000 index 0000000..c2af0d8 --- /dev/null +++ b/web-player/.claude/skills/svelte5-best-practices @@ -0,0 +1 @@ +../../.agents/skills/svelte5-best-practices \ No newline at end of file diff --git a/web-player/.claude/skills/tailwind-css-patterns b/web-player/.claude/skills/tailwind-css-patterns new file mode 120000 index 0000000..03dab7d --- /dev/null +++ b/web-player/.claude/skills/tailwind-css-patterns @@ -0,0 +1 @@ +../../.agents/skills/tailwind-css-patterns \ No newline at end of file diff --git a/web-player/.claude/skills/typescript-advanced-types b/web-player/.claude/skills/typescript-advanced-types new file mode 120000 index 0000000..4d2e523 --- /dev/null +++ b/web-player/.claude/skills/typescript-advanced-types @@ -0,0 +1 @@ +../../.agents/skills/typescript-advanced-types \ No newline at end of file diff --git a/web-player/.claude/skills/vite b/web-player/.claude/skills/vite new file mode 120000 index 0000000..1216e95 --- /dev/null +++ b/web-player/.claude/skills/vite @@ -0,0 +1 @@ +../../.agents/skills/vite \ No newline at end of file diff --git a/web-player/skills-lock.json b/web-player/skills-lock.json new file mode 100644 index 0000000..79e6063 --- /dev/null +++ b/web-player/skills-lock.json @@ -0,0 +1,55 @@ +{ + "version": 1, + "skills": { + "accessibility": { + "source": "addyosmani/web-quality-skills", + "sourceType": "autoskills-registry", + "computedHash": "bffe3d08cfe92ebad63699f74ce29e35c19850ebfbf474c1463183cfe34d6a09" + }, + "frontend-design": { + "source": "anthropics/skills", + "sourceType": "autoskills-registry", + "computedHash": "82fb11a63fb1e35ee2469516ed02d54695f783115b1540c0e783197af4240a3a" + }, + "nodejs-backend-patterns": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "710a5e6f83c46e8f6c43356df55c143a7375c4414559a581654ce51709138c55" + }, + "nodejs-best-practices": { + "source": "sickn33/antigravity-awesome-skills", + "sourceType": "autoskills-registry", + "computedHash": "7361ab02fb6b09913e3bdd9cf61c629ed6c17de9485e6a781054e5d437ccfc29" + }, + "seo": { + "source": "addyosmani/web-quality-skills", + "sourceType": "autoskills-registry", + "computedHash": "c184da724d1c61ad077f27418ea8e7e88fd54bcdf98165e18be7e4681cbd5e20" + }, + "svelte-code-writer": { + "source": "sveltejs/ai-tools", + "sourceType": "autoskills-registry", + "computedHash": "b5e12f4a6369f9e482d863fd0dde5cfc215041947902dacc416a2f9a50da166a" + }, + "svelte5-best-practices": { + "source": "ejirocodes/agent-skills", + "sourceType": "autoskills-registry", + "computedHash": "89df0819dcaf5292635267d6e4262f274845c0efa3db7bc4c2a0d5aac2431117" + }, + "tailwind-css-patterns": { + "source": "giuseppe-trisciuoglio/developer-kit", + "sourceType": "autoskills-registry", + "computedHash": "8fd534b64b3f305ade80c207e2f9ba4338d5e409e355af9f724c36764b6709c1" + }, + "typescript-advanced-types": { + "source": "wshobson/agents", + "sourceType": "autoskills-registry", + "computedHash": "5ca0e177c6aaaba1889255691224daafdb7d71f317cc70bede1590d3907ded42" + }, + "vite": { + "source": "antfu/skills", + "sourceType": "autoskills-registry", + "computedHash": "e0b52fe3ffea533e1e543ac8a8a20fccca3beed9f8bdc31a0934fe9f554439e8" + } + } +}