# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Overview The **Video.js v10 documentation site** is an Astro-based static site generator with React islands for interactivity. The site serves documentation, blog posts, and interactive demos for the Video.js v10 library. Key architectural feature: **Multi-framework documentation** — the same content generates separate routes for different framework/style combinations (e.g., HTML + CSS, React + CSS), allowing framework-specific documentation from shared MDX sources. ## Commands From `site/` directory: | Command | Purpose | | -------------------- | ---------------------------------------------------- | | `pnpm dev` | Start dev server at `localhost:4321` | | `pnpm build` | Build production site to `./dist/` | | `pnpm preview` | Preview production build locally | | `pnpm test` | Run all tests once | | `pnpm test:watch` | Run tests in watch mode | | `pnpm test:ui` | Run Vitest with web UI | | `pnpm test:coverage` | Generate coverage report | | `pnpm astro ...` | Run Astro CLI (e.g., `pnpm astro check`) | **From monorepo root:** - `pnpm dev:site` — Start site dev server - `pnpm build:site` — Build site **Running single test file:** ```bash pnpm test sidebar.test.ts ``` ## Astro MCP Server An Astro MCP server is likely available. **Always use Astro best practices and standard patterns** for maintainability. Consult the MCP for Astro-specific guidance when working with: - Astro components and layouts - Content collections - Routing patterns - Integrations - Build optimizations Following Astro conventions ensures consistency and makes the codebase easier to maintain. ## Tailwind v4 Configuration & Gotchas This project uses **Tailwind v4** with a **custom configuration**. Standard Tailwind tokens may not be available. ### CRITICAL: Always Check globals.css First **Before using any Tailwind utility class**, read `src/styles/globals.css` to verify: - Custom color tokens (e.g., `dark-110`, `light-100`, not standard Tailwind colors) - Custom text size tokens (e.g., `text-h1`, `text-h2`, not standard `text-4xl`, `text-5xl`) - Custom tracking values - Custom font weight values - Available custom variants - And possibly more ### Custom Variant: `intent:` (NOT `hover:` or `focus-visible:`) Use the **`intent:` variant** instead of `hover:` and `focus-visible:`: ```astro ``` The `intent:` variant is defined as: ```css @custom-variant intent (&:hover, &:focus-within); ``` ### Arbitrary Tailwind: Last Resort Only **Avoid arbitrary variants like `[&:hover]` or `text-[pink]`**. Use them only as a last resort. **Prefer inline styles** when Tailwind utilities don't exist: ```astro
Content
Content
``` ### Use `clsx` for Class Concatenation Always use **`clsx`** (or `cn` helper if available) for conditional classes: ```tsx import clsx from 'clsx'; ``` ## Project Structure ``` site/ ├── src/ │ ├── components/ # Astro + React components │ ├── content/ # Content collections (blog/, docs/, authors.json) │ ├── layouts/ # Page layouts (Base, Blog, Docs, Markdown) │ ├── pages/ # Route pages (file-based routing) │ ├── stores/ # Nanostores for cross-island state │ ├── styles/ # Global CSS, Tailwind imports │ ├── types/ # TypeScript type definitions │ ├── utils/ # Utilities and helpers │ │ └── docs/ # Documentation-specific utilities │ │ ├── sidebar.ts # Sidebar filtering and navigation │ │ ├── routing.ts # Docs URL building and redirects │ │ └── __tests__/ # Tests for docs utilities │ ├── consts.ts # Site-wide constants │ ├── content.config.ts # Content collection schemas │ ├── docs.config.ts # Documentation sidebar structure │ └── test-setup.ts # Vitest setup file ├── public/ # Static assets (served untransformed) ├── integrations/ # Custom Astro integrations │ └── pagefind.ts # Pagefind search integration ├── astro.config.mjs # Astro configuration ├── tsconfig.json # TypeScript config with path aliases └── vitest.config.ts # Test configuration ``` ## Multi-Framework Documentation Architecture ### Framework/Style Combinations Documentation is generated for **multiple framework and style combinations** from the same MDX source files. **Current support** (defined in `src/types/docs.ts`): - **Frameworks**: `html`, `react` - **Styles**: `css` (more may be added) **URL pattern:** ``` /docs/framework/{framework}/style/{style}/{...slug}/ ``` **Example:** - `src/content/docs/how-to/installation.mdx` generates: - `/docs/framework/html/style/css/how-to/installation/` - `/docs/framework/react/style/css/how-to/installation/` ### Content Restriction Mechanisms **1. Within MDX content:** Use `` or `` components to show framework/style-specific content: ```mdx Use `useState` to manage state. Use `data-` attributes to manage state. ``` **2. In sidebar config (`src/docs.config.ts`):** ```ts const sidebar: Sidebar = [ { sidebarLabel: 'Getting started', contents: [ { slug: 'how-to/installation' }, // Available to all { slug: 'how-to/react-hooks', frameworks: ['react'] // Only for React }, ], }, ]; ``` ### Sidebar Configuration **Structure** (`src/docs.config.ts`): - Export a `sidebar` constant of type `Sidebar` - Hierarchical: Sections contain Guides - Each Guide has: - `slug`: Path relative to `src/content/docs/` (without `.mdx`) - `sidebarLabel` (optional): Override display name - `frameworks` (optional): Restrict to specific frameworks - `styles` (optional): Restrict to specific styles - `devOnly` (optional): Show only in development mode **Example:** ```ts export const sidebar: Sidebar = [ { sidebarLabel: 'Components', contents: [ { slug: 'reference/play-button' }, { slug: 'reference/mute-button', sidebarLabel: 'Mute' }, ], }, ]; ``` ## Documentation Utilities ### Key Utility Functions (`src/utils/docs/`) **`sidebar.ts`** — Sidebar filtering and navigation: - `filterSidebar()`: Filter sidebar by framework/style, remove empty sections - `findFirstGuide()`: Get first available guide for framework/style combo - `findGuideBySlug()`: Search sidebar recursively for a guide - `getAdjacentGuides()`: Get prev/next guides for navigation - `getValidStylesForGuide()`: Determine valid styles for a guide - `getSectionsForGuide()`: Get breadcrumb trail to a guide **`routing.ts`** — URL building and redirect logic: - `buildDocsUrl()`: Construct docs URLs from framework/style/slug - `resolveIndexRedirect()`: Intelligent redirect for index pages - Handles user preferences from localStorage - Validates framework/style combinations - Falls back to defaults when invalid ### Docs Routing Pattern **Nested index pages** handle redirects at each level: ``` /docs/ → redirect to first guide /docs/framework/ → redirect to first guide /docs/framework/{framework}/ → redirect to first guide /docs/framework/{framework}/style/ → redirect to first guide /docs/framework/{framework}/style/{style}/ → redirect to first guide /docs/framework/{framework}/style/{style}/{...slug} → render guide ``` Each index page uses `resolveIndexRedirect()` to determine where to redirect based on: 1. URL params (framework, style) 2. User preferences (from localStorage via Nanostores) 3. Defaults (when invalid or missing) ## Content Collections Defined in `src/content.config.ts` using Astro's Content Collections API. ### IMPORTANT: Only MDX Files Supported **We only support `.mdx` files, NOT `.md` files.** All content must be written in **MDX format** to support: - React components within content - Framework/style conditional rendering (``, ``) - Custom typography components - Interactive examples ### Blog Collection (`src/content/blog/`) **Filename convention:** `YYYY-MM-DD-slug.mdx` - Date prefix automatically removed from slug - Example: `2024-01-15-new-release.mdx` → slug: `new-release`, URL: `/blog/new-release/` **Schema:** ```ts { title: string; description: string; pubDate: Date; // From filename or git history authors: string[]; // Reference to authors.json devOnly?: boolean; // Show only in development } ``` ### Docs Collection (`src/content/docs/`) **Subdirectories:** - `how-to/` — Outcome-focused guides (per Diátaxis framework) - `concepts/` — Understanding-focused guides - `reference/` — API documentation **Schema:** ```ts { title: string; description: string; frameworkTitle?: { // Per-framework title overrides html?: string; react?: string; }; updatedDate?: Date; // From git history } ``` ### Authors Collection (`src/content/authors.json`) ```ts { [key: string]: { name: string; bio?: string; avatar?: string; socialLinks?: { platform: string; url: string }[]; } } ``` ### Git Integration `src/utils/gitService.ts` uses `simple-git` to enrich content with metadata: - Blog posts: `pubDate` from filename or first commit - All content: `updatedDate` from last modification ## State Management with Nanostores **Why Nanostores?** Astro's island architecture means each React component with `client:load` is an isolated React root. React Context doesn't work across islands, so we use Nanostores for cross-island state. **Store locations** (`src/stores/`): - `preferences.ts`: User framework/style preferences (persisted to localStorage) - `homePageDemos.ts`: Home page demo state - `tabs.ts`: Tab component state **Usage pattern:** ```ts import { useStore } from '@nanostores/react'; import { $preferences } from '@/stores/preferences'; function MyComponent() { const prefs = useStore($preferences); // ... } ``` ## Testing ### Configuration (`vitest.config.ts`) ```ts { globals: true, // No import needed for describe, it, expect environment: 'jsdom', // Browser-like environment setupFiles: ['./src/test-setup.ts'], // Imports @testing-library/jest-dom coverage: { provider: 'v8', include: ['src/utils/**', 'src/components/**', 'src/types/**'], exclude: ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'], }, } ``` ### Test Organization Tests are **colocated** with source code in `__tests__/` directories: ``` src/utils/docs/ ├── sidebar.ts ├── routing.ts └── __tests__/ ├── sidebar.test.ts └── routing.test.ts ``` ### Testing Patterns **Mock framework/style configuration:** ```ts vi.mock('@/types/docs', async () => { const actual = await vi.importActual('@/types/docs'); return { ...actual, FRAMEWORK_STYLES: { html: ['css'], react: ['css'] }, }; }); ``` **Test complex utilities:** - Sidebar filtering with nested sections - Route resolution and redirect logic - Framework/style validation ## Technology Stack - **[Astro 5.14.4](https://astro.build)**: Static site generation with island architecture - **[React 18](https://react.dev)**: Client-side interactive components (`client:load`) - **[Tailwind v4](https://tailwindcss.com)**: CSS utility classes via `@tailwindcss/vite` - **[Nanostores 1.0.1](https://github.com/nanostores/nanostores)**: Cross-island state - **[Base UI 1.0.0-beta.4](https://base-ui.com)**: Headless accessible components - **[Pagefind 1.4.0](https://pagefind.app)**: Static search with build-time indexing - **[Shiki 3.13.0](https://shiki.style)**: Syntax highlighting - **[Vitest 3.2.4](https://vitest.dev)**: Testing framework - **[clsx](https://github.com/lukeed/clsx)**: Class name concatenation utility ## Custom Astro Integration: Pagefind **Location:** `integrations/pagefind.ts` **Purpose:** Integrates Pagefind static search into Astro build pipeline. **Development mode:** - Serves Pagefind index from previous production build - Uses `sirv` middleware to serve `/pagefind/*` routes - Warns if index doesn't exist (needs `pnpm build` first) **Production mode:** - Runs Pagefind CLI after Astro build completes - Indexes all HTML files in `dist/` - Maps Astro logger levels to Pagefind CLI flags **Usage in `astro.config.mjs`:** ```js import pagefind from './integrations/pagefind'; export default defineConfig({ integrations: [pagefind()], }); ``` ## TypeScript Configuration **Path aliases** (`tsconfig.json`): ```json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } } ``` **Import examples:** ```ts import { sidebar } from '@/docs.config'; import type { Sidebar } from '@/types/docs'; import { filterSidebar } from '@/utils/docs/sidebar'; ``` **Strict mode enabled:** - `noUncheckedIndexedAccess: true` - `exactOptionalPropertyTypes: true` ## Key Architecture Patterns ### 1. Recursive Sidebar Filtering Sidebar filtering is recursive because sections can contain guides or nested sections: ```ts export function filterSidebar( sidebar: Sidebar, framework: SupportedFramework, style: AnySupportedStyle, ): Sidebar { return sidebar .map((section) => ({ ...section, contents: section.contents.filter((item) => isItemVisible(item, framework, style) ), })) .filter((section) => section.contents.length > 0); } ``` ### 2. Type Guards for Framework/Style Validation **Defined in `src/types/docs.ts`:** ```ts export const FRAMEWORK_STYLES = { html: ['css'], react: ['css'], } as const; export function isValidFramework(value: unknown): value is SupportedFramework { return typeof value === 'string' && value in FRAMEWORK_STYLES; } export function isValidStyleForFramework( framework: SupportedFramework, style: unknown, ): style is AnySupportedStyle { return typeof style === 'string' && FRAMEWORK_STYLES[framework].includes(style as any); } ``` ### 3. Git-Enriched Content Metadata Content collections automatically enrich metadata from git history: ```ts // In content.config.ts const blog = defineCollection({ loader: globWithParser({ pattern: '**/*.mdx', base: './src/content/blog', async parseData(frontmatter, fileUrl) { const filePath = fileURLToPath(fileUrl); const updatedDate = await getLastModifiedDate(filePath); return { ...frontmatter, updatedDate }; }, }), }); ``` ### 4. Island Architecture with React Each React component with `client:load` is an **independent React root**: ```astro --- import Tabs from '@/components/Tabs.tsx'; import Search from '@/components/Search/Search.tsx'; --- ``` **Consequence:** React Context doesn't work across islands. Use Nanostores instead. ## MDX Component Typography **Location:** `src/components/typography/` Standard MDX elements (headings, paragraphs, lists, etc.) are defined here and used across all MDX layouts (blog, docs, markdown pages). **Usage in layouts:** ```astro --- import { components } from '@/components/typography'; --- ``` ## Important Development Notes ### Writing Documentation Read `src/content/docs/how-to/write-guides.mdx` for comprehensive guide-writing instructions. Key points: - **Use `.mdx` files only** (not `.md`) - Use `` and `` for framework/style-specific content - Follow Diátaxis framework: how-to vs. concept guides - Add new guides to `src/docs.config.ts` sidebar - Use `devOnly: true` for internal documentation ### Search Indexing Pagefind indexes HTML files after build. During development: 1. Run `pnpm build` at least once to generate search index 2. Dev server serves the index from previous build 3. Search won't include new content until next build ### Adding Framework/Style Support To add a new framework or style: 1. Update `FRAMEWORK_STYLES` in `src/types/docs.ts` 2. Update type definitions (`SupportedFramework`, `AnySupportedStyle`) 3. Add corresponding page routes in `src/pages/docs/framework/[framework]/` 4. Update sidebar filtering logic if needed (usually automatic) 5. Update tests to include new framework/style ### Blog Post Naming **CRITICAL:** Blog post filenames MUST be date-prefixed: ``` YYYY-MM-DD-slug.mdx ``` The date prefix is automatically removed from the slug during content collection transformation by `src/utils/globWithParser.ts`. **Example:** - File: `2024-01-15-new-release.mdx` - Slug: `new-release` - URL: `/blog/new-release/` ### Development-Only Content Use `devOnly: true` in frontmatter or sidebar config to hide content in production: ```ts // In docs.config.ts { slug: 'how-to/write-guides', devOnly: true } ``` ```mdx --- title: Internal Documentation devOnly: true --- ``` ## Common Tasks ### Adding a New Docs Guide 1. Create MDX file in `src/content/docs/{how-to|concepts|reference}/your-guide.mdx` 2. Add frontmatter with `title` and `description` 3. Add to sidebar in `src/docs.config.ts` 4. Optional: Restrict to specific frameworks/styles 5. Test with `pnpm dev` and verify all framework/style combinations ### Running Tests for Specific Utility ```bash # Run sidebar tests pnpm test sidebar.test.ts # Run in watch mode pnpm test:watch sidebar.test.ts # With UI pnpm test:ui ``` ### Debugging Redirect Logic The `resolveIndexRedirect()` function in `src/utils/docs/routing.ts` returns a `reason` field explaining why a particular redirect was chosen: ```ts const result = resolveIndexRedirect({ preferences, params }); console.log(result.reason); // e.g., "using preference framework and style" ``` ### Checking TypeScript ```bash pnpm astro check ``` This runs Astro's built-in TypeScript checker across `.astro`, `.ts`, and `.tsx` files.