diff --git a/.claude/skills/css-feature-detection-with-fallbacks/SKILL.md b/.claude/skills/css-feature-detection-with-fallbacks/SKILL.md new file mode 100644 index 000000000..33e1c35a0 --- /dev/null +++ b/.claude/skills/css-feature-detection-with-fallbacks/SKILL.md @@ -0,0 +1,7 @@ +--- +name: css-feature-detection-with-fallbacks +description: Runtime CSS feature detection that defends against missing CSS.supports, normalizes the -webkit-backdrop-filter prefix for Safari, and exposes a hasCriticalSupport predicate that treats scroll-behavior and backdrop-filter as optional. Use when a component needs to branch on browser CSS support or a test must enforce graceful fallbacks. +scope: project +--- + +See [README.md](references/README.md) for full documentation. diff --git a/.claude/skills/css-feature-detection-with-fallbacks/references/README.md b/.claude/skills/css-feature-detection-with-fallbacks/references/README.md new file mode 100644 index 000000000..839d59713 --- /dev/null +++ b/.claude/skills/css-feature-detection-with-fallbacks/references/README.md @@ -0,0 +1,81 @@ +# CSS Feature Detection With Fallbacks + +A small TypeScript module that wraps `CSS.supports()` for the homepage's cross-browser strategy. Used both at runtime (when a component wants to branch on engine support) and from Jest unit tests (to validate that the global stylesheet declares fallback chains). + +## Lives at + +`homepage/tests/unit/cross-browser/cssFeatureDetection.ts` + +(It currently lives in the test folder because only tests and one runtime utility consume it. Promote to `homepage/src/lib/` if a non-test caller starts importing it.) + +## API + +```ts +supportsCSSFeature(property: string, value: string): boolean +supportsCSSVariables(): boolean +supportsFlexbox(): boolean +supportsGrid(): boolean +supportsScrollBehavior(): boolean +supportsBackdropFilter(): boolean // probes both standard and -webkit- +checkCriticalCSSFeatures(): CriticalFeatureSupport +hasCriticalSupport(support): boolean +``` + +## Three rules baked into the implementation + +1. **Defensive `CSS.supports` call.** Old browsers and SSR may not expose `CSS` or `CSS.supports`. We type-check both, then call inside try/catch because some engines throw on malformed property/value strings: + + ```ts + if (typeof CSS === 'undefined' || typeof CSS.supports !== 'function') return false; + try { return CSS.supports(property, value); } catch { return false; } + ``` + +2. **`-webkit-backdrop-filter` is a valid yes.** Safari shipped this without dropping the prefix. `supportsBackdropFilter` returns true if either property reports support, so a Safari-style partial-support shape is treated as supported: + + ```ts + return ( + supportsCSSFeature('backdrop-filter', 'blur(10px)') || + supportsCSSFeature('-webkit-backdrop-filter', 'blur(10px)') + ); + ``` + +3. **`scroll-behavior` and `backdrop-filter` are optional.** `hasCriticalSupport` only requires `cssVariables`, `flexbox`, and `grid`. The other two have natural degradations: missing `scroll-behavior` becomes instant scroll; missing `backdrop-filter` falls back to the solid `background-color` we set on the header. + +## Companion test pattern (static CSS analysis) + +The Jest suite for this module also reads `globals.css`, `postcss.config.js`, and `tailwind.config.ts` and asserts contracts that would be hard to lint: + +```ts +expect(globalsCss).toMatch(/var\(--/); // variables in use +expect(globalsCss).toMatch(/(monospace|sans-serif|serif)/); // generic family fallback +expect(globalsCss).toMatch(/scroll-behavior:\s*smooth/); // smooth declared +expect(postcssConfig).toMatch(/autoprefixer/); // autoprefixer wired +``` + +A risky-selector guard requires `@supports` whenever `:has()`, `color-mix()`, or `@property` appear: + +```ts +const risky = [/:has\(/, /color-mix\(/, /@property\s/]; +for (const re of risky) { + if (re.test(globalsCss)) { + expect(globalsCss).toMatch(/@supports/); + } +} +``` + +This keeps progressive-enhancement enforced as a unit test rather than a code-review checklist. + +## Mocking pattern in unit tests + +The Jest tests replace `global.CSS` with a Jest mock in `beforeEach` and restore it in `afterEach`. Avoid polluting other suites by always pairing the override: + +```ts +let originalCSS: typeof CSS | undefined; +beforeEach(() => { + originalCSS = (global as unknown as { CSS?: typeof CSS }).CSS; + (global as unknown as { CSS: { supports: jest.Mock } }).CSS = { supports: jest.fn() }; +}); +afterEach(() => { (global as unknown as { CSS?: typeof CSS }).CSS = originalCSS; }); +``` + +The Safari shape test is worth keeping as a regression: it makes `CSS.supports` return false for the standard property and true for `-webkit-backdrop-filter`, then asserts `checkCriticalCSSFeatures().backdropFilter === true`. diff --git a/.claude/skills/nextjs-public-asset-bootstrap/SKILL.md b/.claude/skills/nextjs-public-asset-bootstrap/SKILL.md new file mode 100644 index 000000000..701e1cfa1 --- /dev/null +++ b/.claude/skills/nextjs-public-asset-bootstrap/SKILL.md @@ -0,0 +1,8 @@ +--- +name: nextjs-public-asset-bootstrap +description: Copy shared assets from the workspace's /assets/ folder into homepage/public/ so the Next.js dev server serves them at runtime. Use when an image at src=/foo.gif returns 404 in dev/test, when e2e tests need real (not placeholder) images, or when scaffold rule 6 (Asset References) is in play. +metadata: + scope: project +--- + +See [README.md](references/README.md) for full documentation. diff --git a/.claude/skills/nextjs-public-asset-bootstrap/references/README.md b/.claude/skills/nextjs-public-asset-bootstrap/references/README.md new file mode 100644 index 000000000..1836adefd --- /dev/null +++ b/.claude/skills/nextjs-public-asset-bootstrap/references/README.md @@ -0,0 +1,60 @@ +# Next.js Public Asset Bootstrap + +## Overview + +The MirDB project keeps shared image assets at `/workspace/assets/` (referenced by scaffold rule #6, "Asset References"), but the Next.js app at `homepage/` only serves files placed under `homepage/public/`. This skill captures the bootstrap step: copying the shared assets into the homepage public folder so the dev server, e2e tests, and production build all see the real images. + +## When to Use This Skill + +Use this skill when: + +- An `` 404s in the dev server but the file exists under `/workspace/assets/foo.gif` +- An e2e test inspects images and the page returns a broken image (no `naturalWidth`, axe `image-alt`/`image-redundant-alt` flapping) +- You are scaffolding a new homepage scenario and need to be sure the scenario's component can actually render its image +- Scaffold rule #6 (Asset References) is in play + +## Core Capabilities + +### 1. Identify which public/ assets the page references + +Grep the homepage source for `src="/"` and `url(/...)`: + +```bash +grep -rn 'src="/' homepage/src/ +grep -rn 'url(/' homepage/src/ +``` + +Each unique `/foo.gif` / `/bar.png` is a candidate that must exist under `homepage/public/`. + +### 2. Copy from shared `/workspace/assets/` + +```bash +cp /workspace/assets/logo.gif homepage/public/logo.gif +cp /workspace/assets/usage.gif homepage/public/usage.gif +``` + +This is intentionally a copy, not a symlink — the homepage Next.js build (especially the `output: 'export'` static path) needs concrete files under `public/`. + +### 3. Verify the dev server serves them + +```bash +curl -sI http://localhost:3000/usage.gif | head -1 # expect 200, not 404 +``` + +If the dev server is already running, no restart is needed — Next.js serves `public/` files dynamically. + +## Best Practices + +- Treat `homepage/public/` as **bootstrap territory**, not a per-scenario folder. Copying an asset here is appropriate even if your scenario only owns `tests/`. +- Do not modify or rename the original under `/workspace/assets/`. Other scenarios reference it by name. +- After copying, commit both the copy and any test that depends on it in the same commit so reviewers see the dependency in one diff. + +## Reference Implementation + +`homepage/public/usage.gif`, `homepage/public/logo.gif` (copies of `/workspace/assets/usage.gif`, `/workspace/assets/logo.gif`). + +## Resources + +### references/ + +- `README.md` — This documentation diff --git a/.claude/skills/playwright-axe-a11y-testing/SKILL.md b/.claude/skills/playwright-axe-a11y-testing/SKILL.md new file mode 100644 index 000000000..9586e4ae6 --- /dev/null +++ b/.claude/skills/playwright-axe-a11y-testing/SKILL.md @@ -0,0 +1,8 @@ +--- +name: playwright-axe-a11y-testing +description: Run WCAG 2.1 AA audits inside Playwright tests using @axe-core/playwright with a Lighthouse-style pass-rate gate. Use when adding accessibility tests for a Next.js homepage, when a scenario requires zero critical violations plus a 90 or higher score, or when you need an axe audit that produces assertable JSON instead of a Lighthouse HTML report. +metadata: + scope: project +--- + +See [README.md](references/README.md) for full documentation. diff --git a/.claude/skills/playwright-axe-a11y-testing/references/README.md b/.claude/skills/playwright-axe-a11y-testing/references/README.md new file mode 100644 index 000000000..4190384e4 --- /dev/null +++ b/.claude/skills/playwright-axe-a11y-testing/references/README.md @@ -0,0 +1,82 @@ +# Playwright Axe A11y Testing + +## Overview + +This skill captures the pattern used in `homepage/tests/e2e/accessibility.spec.ts` for running an in-test WCAG 2.1 AA audit using `@axe-core/playwright`. Instead of spinning up the Lighthouse CLI (heavy, HTML report, requires headful Chrome), we run the same WCAG rule set inside a Playwright test, return a structured violations array, and reproduce Lighthouse's rule-pass-rate scoring with assertable JSON. + +## When to Use This Skill + +Use this skill when: + +- A scenario or PRD asks for "Lighthouse accessibility score >= 90 with zero critical violations" +- You need a deterministic, sub-second accessibility check that runs in CI without extra browser binaries +- You want to filter axe violations by impact level (`minor` / `moderate` / `serious` / `critical`) to scope a gate +- A page has color-contrast issues owned by other components/teams that you do not want to block on, but still want logged + +## Core Capabilities + +### 1. Tagged WCAG audit via AxeBuilder + +```ts +import AxeBuilder from '@axe-core/playwright'; + +const results = await new AxeBuilder({ page }) + .withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa']) + .analyze(); +``` + +The four tags select WCAG 2.0 Level A + AA and WCAG 2.1 Level A + AA — the exact surface called out by PRD NFR-3. + +### 2. Critical-only gate + +```ts +const critical = results.violations.filter((v) => v.impact === 'critical'); +expect(critical).toHaveLength(0); +``` + +`critical` issues are the ones that actually block assistive tech (missing accessible names, broken landmark structure). `serious` issues like color-contrast are real WCAG failures but often live in design-token territory owned by other scenarios — log them but do not gate on them here. + +### 3. Lighthouse-style rule pass-rate + +```ts +const totalRulesEvaluated = + results.violations.length + results.passes.length + results.incomplete.length; +const violationCount = results.violations.length; +const passRate = + totalRulesEvaluated === 0 + ? 100 + : ((totalRulesEvaluated - violationCount) / totalRulesEvaluated) * 100; +expect(passRate).toBeGreaterThanOrEqual(90); +``` + +Counts rule TYPES (not flagged DOM nodes). One failing rule with 12 nodes is one failed audit, mirroring how Lighthouse computes its 0–100 score. A single offending rule cannot tank the score, but multiple distinct accessibility problems will. + +### 4. Surfacing failures in the test log + +Always print the violations summary before asserting so a failing CI run includes the offending rule IDs: + +```ts +if (critical.length > 0) { + console.log('Critical violations:', JSON.stringify( + critical.map((v) => ({ id: v.id, impact: v.impact, help: v.help })), + null, 2, + )); +} +``` + +## Best Practices + +- Always `await page.waitForLoadState('networkidle')` in `beforeEach` so axe sees the final DOM. +- Filter on `impact === 'critical'` only when the test_case wording uses that word — if the wording is "AA conformance", consider including `serious` too. +- Prefer the rule-pass-rate formula above over a flagged-node penalty: it matches Lighthouse semantics and is more stable across runs. +- Add `@axe-core/playwright` and `axe-core` as **devDependencies** of the homepage package, not the root. + +## Reference Implementation + +`homepage/tests/e2e/accessibility.spec.ts` lines 10–46 (Test Case 1). + +## Resources + +### references/ + +- `README.md` — This documentation diff --git a/.claude/skills/playwright-cross-browser-projects/SKILL.md b/.claude/skills/playwright-cross-browser-projects/SKILL.md new file mode 100644 index 000000000..1f7a97ab8 --- /dev/null +++ b/.claude/skills/playwright-cross-browser-projects/SKILL.md @@ -0,0 +1,7 @@ +--- +name: playwright-cross-browser-projects +description: Configure Playwright projects for Chrome, Firefox, Safari (WebKit), and Edge with selective testMatch scoping so cross-browser tests run on all engines while other suites stay on chromium. Use when adding cross-browser coverage to a Next.js or web project that already uses Playwright. +scope: project +--- + +See [README.md](references/README.md) for full documentation. diff --git a/.claude/skills/playwright-cross-browser-projects/references/README.md b/.claude/skills/playwright-cross-browser-projects/references/README.md new file mode 100644 index 000000000..55d20402c --- /dev/null +++ b/.claude/skills/playwright-cross-browser-projects/references/README.md @@ -0,0 +1,72 @@ +# Playwright Cross-Browser Projects + +How the MirDB homepage runs cross-browser e2e tests on Chrome, Firefox, Safari (WebKit), and Edge while keeping the rest of the e2e suite scoped to chromium for CI cost. + +## When to use + +- Adding a new e2e spec that must validate engine-specific behavior (CSS, layout, font fallbacks). +- Verifying that cross-browser regressions show up in CI without quadrupling the runtime of unrelated specs. + +## The pattern + +In `homepage/playwright.config.ts`: + +```ts +projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + { + name: 'firefox', + testMatch: /cross-browser\.spec\.ts/, + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + testMatch: /cross-browser\.spec\.ts/, + use: { ...devices['Desktop Safari'] }, + }, + { + name: 'edge', + testMatch: /cross-browser\.spec\.ts/, + use: { ...devices['Desktop Edge'] }, + }, +] +``` + +Key points: + +1. **`chromium` has no `testMatch`** so it runs every spec under `tests/e2e/` (the default). All non-cross-browser tests still execute. +2. **Firefox / WebKit / Edge each set `testMatch: /cross-browser\.spec\.ts/`** which restricts that project to a single file. +3. **Edge uses `Desktop Edge`** rather than a separate channel install. Modern Edge is Chromium-based and Playwright's Desktop Edge device exercises that engine through the Edge channel — matches reality, keeps CI hermetic. +4. **Run all four engines on the cross-browser file**: `npx playwright test tests/e2e/cross-browser.spec.ts` runs the spec on each project (~72 tests for the suite documented here). +5. **Run a single engine for debugging**: `npx playwright test --project=webkit tests/e2e/cross-browser.spec.ts`. + +## Browser install (for fresh hosts) + +Firefox and WebKit need system libs. The flow that worked on this Ubuntu 22.04 host: + +```bash +npx playwright install chromium # ok without sudo +sudo -n npx playwright install-deps chromium # apt deps +sudo -n npx playwright install --with-deps firefox webkit +``` + +The `--with-deps` form of `install` runs `apt-get install` for the required X/GTK libraries before downloading the browser binaries. Without sudo, Firefox/WebKit downloads still succeed but launching fails with the host validation banner. + +## Test patterns that worked across all four engines + +In the cross-browser spec, every assertion includes `${browserName}` in the message so a CI failure tells you which engine regressed: + +```ts +expect(consoleErrors, `${browserName}: page should load without console errors`).toEqual([]); +``` + +For features with engine-specific prefixes (e.g. `backdrop-filter`), accept either the standard property OR a documented graceful fallback (background-color), so the test stays green on engines that lawfully lack the property without weakening the contract on engines that have it. + +## Reference: this scenario + +- `homepage/playwright.config.ts` lines 20–40 +- `homepage/tests/e2e/cross-browser.spec.ts` (18 tests, all engines) +- All 4 projects pass: chromium 18/18, firefox 18/18, webkit 18/18, edge 18/18. diff --git a/.claude/skills/status-tagged-list-section/SKILL.md b/.claude/skills/status-tagged-list-section/SKILL.md new file mode 100644 index 000000000..755b28d4e --- /dev/null +++ b/.claude/skills/status-tagged-list-section/SKILL.md @@ -0,0 +1,8 @@ +--- +name: status-tagged-list-section +description: Render a homepage section that splits a single tagged data array (e.g. status='implemented'|'planned') into two visually distinct columns - implemented in green with checkmark icons, planned in amber with clock icons - in the MirDB Next.js homepage. Use whenever a section needs to compare "shipped" vs "upcoming" items sourced from src/lib/constants.ts. +metadata: + scope: project +--- + +See [README.md](references/README.md) for full documentation. diff --git a/.claude/skills/status-tagged-list-section/references/README.md b/.claude/skills/status-tagged-list-section/references/README.md new file mode 100644 index 000000000..9e4f6b1c5 --- /dev/null +++ b/.claude/skills/status-tagged-list-section/references/README.md @@ -0,0 +1,101 @@ +# Status Tagged List Section + +## Overview + +This skill captures the pattern used by the MirDB homepage `RoadmapSection` (and similar sections) to render a single status-tagged data array as two side-by-side columns with distinct visual styling. The data lives in `src/lib/constants.ts` as one array, the type lives in `src/types/index.ts`, and the section component derives the two groups via `Array.filter`. + +## When to Use This Skill + +Use this skill when: + +- A homepage section needs to compare "completed/shipped" items vs "planned/in-progress" items. +- Items share the same shape but differ by a single status tag. +- You want both color-based and icon-based differentiation (for accessibility / colorblind users). +- The data should be addressable by other sections (status indicator, sitemap, future analytics) without duplication. + +## Core Capabilities + +### 1. Single-array, status-tagged data shape + +Define the type in `src/types/index.ts`: + +```ts +export interface RoadmapItem { + title: string; + status: 'implemented' | 'planned'; +} +``` + +Place the data in `src/lib/constants.ts` as one tagged array: + +```ts +export const ROADMAP_ITEMS = [ + { title: 'Tokio-based async networking with memcached protocol', status: 'implemented' as const }, + { title: 'Memtable with skip list data structure', status: 'implemented' as const }, + { title: 'Minor compaction (memtable to SSTable)', status: 'implemented' as const }, + { title: 'Major compaction (SSTable level compaction)', status: 'implemented' as const }, + { title: 'Raft consensus for distributed operation', status: 'planned' as const }, +]; +``` + +### 2. Two-column responsive layout with distinct styling + +In the component (`src/components/RoadmapSection.tsx`): + +```tsx +const implemented = ROADMAP_ITEMS.filter((item) => item.status === 'implemented'); +const planned = ROADMAP_ITEMS.filter((item) => item.status === 'planned'); +``` + +Layout grid: `grid md:grid-cols-2 gap-12` (stacks on mobile, side-by-side at md+). + +Color tokens used: + +| Status | Background | Icon badge | Border | +|-------------|-------------------------------------------|-------------------|------------------------------------------| +| implemented | bg-green-50 dark:bg-green-900/20 | bg-green-500 | border-green-200 dark:border-green-800 | +| planned | bg-amber-50 dark:bg-amber-900/20 | bg-amber-500 | border-amber-200 dark:border-amber-800 | + +Inline SVG icons (Heroicons paths) — no runtime icon dependency. `aria-hidden="true"` on the decorative icon span. + +### 3. Stable test hooks + +Add `data-testid` attributes that the unit tests rely on: + +- `data-testid="roadmap-section"` on the `
` root +- `data-testid="implemented-list"` / `data-testid="planned-list"` on the `