From fa3140281ecb66872c903fa10f836b41321170e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 13:16:22 +0000 Subject: [PATCH 1/8] task: add blocking workspace quality surfaces --- ...-08-blocking-workspace-quality-surfaces.md | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md diff --git a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md new file mode 100644 index 0000000000..eca1803674 --- /dev/null +++ b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md @@ -0,0 +1,86 @@ +# Make every workspace test surface blocking and runnable + +## Problem + +The root `test:coverage` command delegates to Turbo, but Turbo treats a missing workspace script as a successful `NONEXISTENT` task. Five tested pnpm workspaces are therefore absent from root coverage. The shared UI package also contains a Storybook configuration and stories without the CLI, framework dependencies, or runnable scripts. Terminal `.test.tsx` files are executed by Vitest but excluded from both ESLint and TypeScript. The public documentation link checker and local Playwright suite exist but are not blocking CI surfaces. + +This is a quality-gate repair only. It must not change runtime UI, API, CLI, cloud-init, website, or tail-worker behavior; lower coverage thresholds; delete tests; or absorb the separate vulnerability/static-analysis and ruleset-enforcement packets. + +## Accepted audit roots + +- `6050d049…`: `packages/ui/.storybook/main.ts` imports undeclared Storybook packages; stories have no installable/buildable command or CI gate. +- `253325b4…`: `packages/terminal` lint covers `tests/**/*.ts` but not `.tsx`, while the build/typecheck project excludes all tests. +- `R9-007`: `apps/www` browser/link checks and `apps/tail-worker` tests are not all represented by blocking root/CI contracts. +- `R10-009`: Turbo reports missing `test:coverage` scripts as `NONEXISTENT` instead of failing. + +## Research findings + +- Latest `origin/main` and the output branch both resolve to `8eed3b7402d2e036900a67db0232fe6c8623155a`; no rebase delta existed before editing. +- On the unmodified baseline, `pnpm turbo run test:coverage --dry=json` reports `NONEXISTENT` for `@simple-agent-manager/cloud-init`, `infra`, `tail-worker`, `ui`, and `www`, even though each has tests. +- On the unmodified baseline, `pnpm --filter @simple-agent-manager/ui build-storybook` exits **zero** while printing `None of the selected packages has a "build-storybook" script`, demonstrating a silent omission. +- The same Turbo dry run also reports `NONEXISTENT` for `tools/og-image`, but that workspace has no tests and is correctly outside the coverage contract. +- `apps/www` already has a deterministic local Playwright config and an internal `/docs` page/anchor checker; neither is invoked by CI. +- `packages/ui` uses Tailwind utility classes and SAM design tokens, so Storybook must load the same token/Tailwind pipeline for meaningful screenshots and axe checks. +- No runtime source changes are required. The user explicitly forbids shared-staging deployment and merge for this source PR. + +## Implementation checklist + +- [ ] Add a fail-closed workspace quality validator that discovers tested pnpm workspaces and requires a real `test:coverage` command. +- [ ] Add fixture tests proving an omitted script/package fails and a complete workspace set passes. +- [ ] Add `test:coverage` and the V8 provider dependency to every currently omitted tested workspace. +- [ ] Install a single compatible Storybook toolchain for Vite 8/React 19, add development/production scripts, and typecheck stories/configuration. +- [ ] Add deterministic Storybook browser screenshots and axe assertions for both current shared component stories at mobile and desktop sizes. +- [ ] Include terminal `tests/**/*.tsx` in ESLint and add a dedicated no-emit TypeScript test project. +- [ ] Expose the public site Playwright suite as a package script and keep link checking build-backed. +- [ ] Add one blocking CI job for the workspace validator, Storybook production build/audit, public docs links, and public browser tests. +- [ ] Run affected tests/coverage, Storybook build/audit, public docs link/browser checks, tail-worker tests, terminal lint/typecheck, and the full root quality suite. +- [ ] Complete all mandated local specialist reviews and address every material finding. +- [ ] Open one non-draft PR against `main`, wait for every applicable GitHub check to turn green, and stop without staging, merge, or task archive. + +## Acceptance criteria + +- Every pnpm workspace that has a test script also has a non-placeholder `test:coverage` script; future omissions fail a checked-in behavioral fixture and blocking CI. +- Root `pnpm test:coverage` executes coverage commands for `apps/tail-worker`, `apps/www`, `packages/cloud-init`, `packages/ui`, and `infra` instead of Turbo `NONEXISTENT` tasks. +- `packages/ui` installs from the frozen lockfile, typechecks its Storybook config/stories, builds static Storybook output, and passes mobile/desktop screenshot plus serious axe checks. +- A lint-invalid or type-invalid terminal `.test.tsx` file is within the configured lint/typecheck inputs; the existing terminal suite remains green. +- `apps/www` internal docs links and its local production-preview Playwright flow are blocking CI checks. +- `apps/tail-worker` tests execute through root coverage and pass directly. +- Full `pnpm lint`, `pnpm typecheck`, `pnpm test:coverage`, and `pnpm build` pass without threshold reductions or test skips added by this change. +- The PR body records exact changed package/workflow contracts, before/after omission evidence, local reviewer outcomes, and the explicit no-staging/no-merge exception. + +## Post-mortem + +### What broke + +Quality surfaces existed in source but were unreachable from the commands and CI jobs developers relied on. A green root coverage run did not mean every tested workspace ran, Storybook files could rot without installation, terminal TSX tests could contain static errors, and public docs browser/link tests were optional. + +### Root cause + +Turbo intentionally tolerates missing workspace tasks. The repository had no invariant checker distinguishing a workspace with no relevant surface from a tested workspace that accidentally omitted the required script. Standalone Storybook, link, browser, and static-test-input configurations were added without a durable CI wiring contract. + +### Why it was not caught + +CI asserted only that aggregate commands exited successfully. It did not test the package-graph completeness invariant or mutate a representative fixture to prove an omitted package makes the gate fail. + +### Class of bug + +Silent aggregate-gate omission: orchestration reports green while an intended participant is absent. + +### Process fix + +The checked-in workspace quality validator and its discriminating fixture make package participation an explicit, fail-closed invariant. The CI workflow runs both the validator and each non-unit production/browser surface directly. + +## References + +- `package.json` +- `pnpm-workspace.yaml` +- `turbo.json` +- `.github/workflows/ci.yml` +- `packages/ui` +- `packages/terminal` +- `packages/cloud-init` +- `apps/www` +- `apps/tail-worker` +- `.claude/rules/02-quality-gates.md` +- `.claude/rules/17-ui-visual-testing.md` +- `.specify/memory/constitution.md` From 75fa4ded820b38766b516a2a9034f9dc78d21d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 13:34:38 +0000 Subject: [PATCH 2/8] test: make workspace quality surfaces blocking --- .github/workflows/ci.yml | 50 + .gitignore | 2 + apps/tail-worker/package.json | 2 + apps/www/package.json | 10 +- .../playwright/public-surface-a11y.spec.ts | 25 + apps/www/tests/tracker.test.ts | 8 +- infra/package.json | 2 + package.json | 1 + packages/cloud-init/package.json | 2 + packages/terminal/package.json | 4 +- .../tests/unit/MultiTerminal.test.tsx | 11 +- .../tests/unit/components/TabBar.test.tsx | 17 +- .../unit/hooks/useTerminalSessions.test.ts | 45 +- packages/terminal/tests/unit/protocol.test.ts | 14 +- packages/terminal/tsconfig.test.json | 9 + packages/ui/.storybook/main.ts | 7 +- packages/ui/.storybook/preview.css | 18 + packages/ui/.storybook/preview.ts | 38 + packages/ui/package.json | 19 +- packages/ui/playwright.storybook.config.ts | 29 + .../tests/playwright/storybook-a11y.spec.ts | 49 + packages/ui/tsconfig.storybook.json | 14 + pnpm-lock.yaml | 1253 +++++++++++++++-- .../check-workspace-test-surfaces.test.ts | 102 ++ .../quality/check-workspace-test-surfaces.ts | 144 ++ scripts/quality/ci-workspace-surfaces.test.ts | 46 + ...-08-blocking-workspace-quality-surfaces.md | 16 +- 27 files changed, 1767 insertions(+), 170 deletions(-) create mode 100644 apps/www/tests/playwright/public-surface-a11y.spec.ts create mode 100644 packages/terminal/tsconfig.test.json create mode 100644 packages/ui/.storybook/preview.css create mode 100644 packages/ui/.storybook/preview.ts create mode 100644 packages/ui/playwright.storybook.config.ts create mode 100644 packages/ui/tests/playwright/storybook-a11y.spec.ts create mode 100644 packages/ui/tsconfig.storybook.json create mode 100644 scripts/quality/check-workspace-test-surfaces.test.ts create mode 100644 scripts/quality/check-workspace-test-surfaces.ts create mode 100644 scripts/quality/ci-workspace-surfaces.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f86d2824ba..369c22d550 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,6 +225,54 @@ jobs: - name: Build all packages run: pnpm build + workspace-quality-surfaces: + name: Workspace Quality Surfaces + if: github.event_name == 'pull_request' || github.repository == 'raphaeltm/simple-agent-manager' + timeout-minutes: 30 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '22' + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Require test coverage scripts for every tested workspace + run: pnpm quality:workspace-test-surfaces + + - name: Build Storybook production bundle + run: pnpm --filter @simple-agent-manager/ui build-storybook + + - name: Build public docs and check internal links + run: pnpm --filter @simple-agent-manager/www build && pnpm --filter @simple-agent-manager/www check:links + + - name: Install Chromium + working-directory: packages/ui + run: pnpm exec playwright install --with-deps chromium + + - name: Audit Storybook in desktop and mobile browsers + run: pnpm --filter @simple-agent-manager/ui test:storybook + + - name: Test public site in desktop and mobile browsers + run: pnpm --filter @simple-agent-manager/www test:browser + + - name: Upload workspace browser evidence + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: workspace-browser-evidence + path: | + .codex/tmp/playwright-screenshots/ + apps/www/.codex/tmp/playwright-screenshots/ + if-no-files-found: ignore + retention-days: 7 + playwright-visual: name: Playwright Visual Tests needs: [changes] @@ -369,6 +417,8 @@ jobs: - name: Dependency governance pinning check run: pnpm quality:dependency-governance + - name: Workspace test surface completeness + run: pnpm quality:workspace-test-surfaces - name: CI workflow wiring tests run: pnpm exec vitest run --config scripts/quality/vitest.config.ts scripts/quality/ci-worker-suite.test.ts diff --git a/.gitignore b/.gitignore index f27c307879..44df3b8184 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ build/ # Test coverage coverage/ packages/cli/coverage.out +**/test-results/ +**/storybook-static/ # Generated Go binaries packages/cli/bin/ diff --git a/apps/tail-worker/package.json b/apps/tail-worker/package.json index 26029c9a8e..cbc266dc26 100644 --- a/apps/tail-worker/package.json +++ b/apps/tail-worker/package.json @@ -8,10 +8,12 @@ "deploy:staging": "wrangler deploy --env staging", "deploy:production": "wrangler deploy --env production", "test": "vitest run", + "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit" }, "devDependencies": { "@cloudflare/workers-types": "catalog:", + "@vitest/coverage-v8": "catalog:", "typescript": "catalog:", "vitest": "catalog:", "wrangler": "catalog:" diff --git a/apps/www/package.json b/apps/www/package.json index 54f1fa6cdf..ad70ec085d 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -9,7 +9,9 @@ "build:blog-mermaid": "pnpm exec esbuild src/scripts/blog-mermaid.ts --bundle --format=esm --platform=browser --target=es2022 --outfile=public/scripts/blog-mermaid.js", "build:docs-mermaid": "pnpm exec esbuild src/scripts/docs-mermaid.ts --bundle --format=esm --platform=browser --target=es2022 --outfile=public/scripts/docs-mermaid.js", "build:tracker": "npx tsx scripts/build-tracker.ts", - "test": "vitest run --exclude tests/playwright/**", + "test": "pnpm build:tracker && vitest run --exclude 'tests/playwright/**'", + "test:coverage": "pnpm build:tracker && vitest run --coverage --exclude 'tests/playwright/**'", + "test:browser": "playwright test", "build": "pnpm build:assets && astro build", "preview": "astro preview", "check:links": "python3 scripts/check-doc-links.py" @@ -21,8 +23,10 @@ "mermaid": "11.14.0" }, "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.62.1", + "@vitest/coverage-v8": "catalog:", "esbuild": "0.28.1", - "typescript": "catalog:", - "@playwright/test": "1.62.1" + "typescript": "catalog:" } } diff --git a/apps/www/tests/playwright/public-surface-a11y.spec.ts b/apps/www/tests/playwright/public-surface-a11y.spec.ts new file mode 100644 index 0000000000..4060d113d5 --- /dev/null +++ b/apps/www/tests/playwright/public-surface-a11y.spec.ts @@ -0,0 +1,25 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +test('self-host surface has no overflow or serious axe violations', async ({ page }, testInfo) => { + await page.goto('/self-host/'); + await expect(page.getByRole('heading', { name: 'Deploy your own SAM instance' })).toBeVisible(); + + const hasHorizontalOverflow = await page.evaluate( + () => document.documentElement.scrollWidth > document.documentElement.clientWidth + ); + expect(hasHorizontalOverflow).toBe(false); + + const axeResults = await new AxeBuilder({ page }).analyze(); + const seriousViolations = axeResults.violations.filter( + (violation) => violation.impact === 'critical' || violation.impact === 'serious' + ); + expect(seriousViolations, JSON.stringify(seriousViolations, null, 2)).toEqual([]); + + const project = testInfo.project.name.toLowerCase().replace(/\W+/g, '-'); + await page.screenshot({ + path: `../../.codex/tmp/playwright-screenshots/www-self-host-${project}.png`, + fullPage: true, + animations: 'disabled', + }); +}); diff --git a/apps/www/tests/tracker.test.ts b/apps/www/tests/tracker.test.ts index 8cf4a9bfd5..9ee9c84f4a 100644 --- a/apps/www/tests/tracker.test.ts +++ b/apps/www/tests/tracker.test.ts @@ -1,5 +1,4 @@ /** @vitest-environment jsdom */ -import trackerScript from '../public/scripts/tracker.js?raw'; import { beforeEach, describe, expect, it, vi } from 'vitest'; async function readBeaconEvents(sendBeacon: ReturnType) { @@ -7,7 +6,7 @@ async function readBeaconEvents(sendBeacon: ReturnType) { return JSON.parse(await blob.text()).events as Array>; } -function installTracker(url: string, referrer = '') { +async function installTracker(url: string, referrer = '') { window.history.pushState({}, '', url); Object.defineProperty(document, 'referrer', { value: referrer, configurable: true }); @@ -19,7 +18,8 @@ function installTracker(url: string, referrer = '') { Object.defineProperty(navigator, 'sendBeacon', { value: sendBeacon, configurable: true }); vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-4000-8000-000000000001'); - window.eval(trackerScript); + vi.resetModules(); + await import('../src/scripts/tracker'); return { sendBeacon }; } @@ -31,7 +31,7 @@ describe('public website tracker', () => { }); it('redacts sensitive URL data, preserves allowed metadata, and avoids duplicate page views', async () => { - const { sendBeacon } = installTracker( + const { sendBeacon } = await installTracker( '/projects/01KZ941C7W5JRFDA9RDZASV8EE/repos/raphaeltm/simple-agent-manager/blob/apps/www/src/scripts/tracker.ts?utm_source=newsletter&utm_medium=email&utm_campaign=launch&token=secret#frag', 'https://user:pass@example.com/oauth/callback/user@example.com?code=secret#frag' ); diff --git a/infra/package.json b/infra/package.json index c0a87cc953..bdfaec8069 100644 --- a/infra/package.json +++ b/infra/package.json @@ -6,6 +6,7 @@ "main": "index.ts", "scripts": { "test": "vitest run", + "test:coverage": "vitest run --coverage", "test:watch": "vitest", "typecheck": "tsc --noEmit" }, @@ -17,6 +18,7 @@ }, "devDependencies": { "@types/node": "catalog:", + "@vitest/coverage-v8": "catalog:", "typescript": "catalog:", "vitest": "catalog:" } diff --git a/package.json b/package.json index 1534cff444..0d2765126e 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "quality:do-wall-time": "tsx scripts/quality/check-do-wall-time.ts", "quality:observability-noise": "tsx scripts/quality/check-observability-noise.ts", "quality:dependency-governance": "npx vitest run --config scripts/quality/vitest.config.ts scripts/quality/dependency-governance.test.ts", + "quality:workspace-test-surfaces": "tsx scripts/quality/check-workspace-test-surfaces.ts", "test:infra": "pnpm --filter @simple-agent-manager/infra test", "prepare": "husky", "quality:migration-ordering": "tsx scripts/quality/check-migration-ordering.ts", diff --git a/packages/cloud-init/package.json b/packages/cloud-init/package.json index 6357b85301..36f2b9d7b3 100644 --- a/packages/cloud-init/package.json +++ b/packages/cloud-init/package.json @@ -15,9 +15,11 @@ "build": "tsup src/index.ts --format esm --dts", "dev": "tsup src/index.ts --format esm --dts --watch", "test": "vitest run", + "test:coverage": "vitest run --coverage", "typecheck": "tsc --noEmit" }, "devDependencies": { + "@vitest/coverage-v8": "catalog:", "tsup": "8.5.1", "typescript": "catalog:", "vitest": "catalog:", diff --git a/packages/terminal/package.json b/packages/terminal/package.json index b5a0ea8691..30db82f1cd 100644 --- a/packages/terminal/package.json +++ b/packages/terminal/package.json @@ -17,8 +17,8 @@ "test": "vitest run", "test:watch": "vitest", "test:coverage": "vitest run --coverage", - "typecheck": "tsc --noEmit", - "lint": "eslint 'src/**/*.ts' 'src/**/*.tsx' 'tests/**/*.ts'" + "typecheck": "tsc --noEmit && tsc --project tsconfig.test.json --noEmit", + "lint": "eslint 'src/**/*.ts' 'src/**/*.tsx' 'tests/**/*.{ts,tsx}'" }, "dependencies": { "@xterm/xterm": "catalog:", diff --git a/packages/terminal/tests/unit/MultiTerminal.test.tsx b/packages/terminal/tests/unit/MultiTerminal.test.tsx index 07954cf7ab..8dce022be2 100644 --- a/packages/terminal/tests/unit/MultiTerminal.test.tsx +++ b/packages/terminal/tests/unit/MultiTerminal.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { act, render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + import { MultiTerminal } from '../../src/MultiTerminal'; // Mock xterm.js @@ -154,9 +155,9 @@ Object.defineProperty(globalThis, 'WebSocket', { // Mock ResizeObserver (not available in jsdom) Object.defineProperty(globalThis, 'ResizeObserver', { value: class MockResizeObserver { - observe() {} - unobserve() {} - disconnect() {} + observe() {} + unobserve() {} + disconnect() {} }, writable: true, configurable: true, diff --git a/packages/terminal/tests/unit/components/TabBar.test.tsx b/packages/terminal/tests/unit/components/TabBar.test.tsx index d3cda5c7a2..4d72b6a175 100644 --- a/packages/terminal/tests/unit/components/TabBar.test.tsx +++ b/packages/terminal/tests/unit/components/TabBar.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + import { TabBar } from '../../../src/components/TabBar'; import type { TerminalSession } from '../../../src/types/multi-terminal'; @@ -81,8 +82,8 @@ describe('TabBar', () => { status: 'connected' as const, createdAt: new Date(), lastActivityAt: new Date(), - isActive: false, - order: 0, + isActive: false, + order: 0, workingDirectory: '/workspace', })); render(); @@ -205,7 +206,7 @@ describe('TabBar', () => { const tab = screen.getByText('Terminal 1'); fireEvent.doubleClick(tab); - const input = await screen.findByDisplayValue('Terminal 1') as HTMLInputElement; + const input = (await screen.findByDisplayValue('Terminal 1')) as HTMLInputElement; expect(input.selectionStart).toBe(0); expect(input.selectionEnd).toBe('Terminal 1'.length); }); @@ -225,9 +226,7 @@ describe('TabBar', () => { workingDirectory: '/workspace', })); - const { container } = render( - - ); + const { container } = render(); // Mock scrollWidth > clientWidth const tabContainer = container.querySelector('.tab-container'); @@ -473,4 +472,4 @@ describe('TabBar', () => { expect(tabs.length).toBe(mockSessions.length); }); }); -}); \ No newline at end of file +}); diff --git a/packages/terminal/tests/unit/hooks/useTerminalSessions.test.ts b/packages/terminal/tests/unit/hooks/useTerminalSessions.test.ts index ee244a6616..e93c54474e 100644 --- a/packages/terminal/tests/unit/hooks/useTerminalSessions.test.ts +++ b/packages/terminal/tests/unit/hooks/useTerminalSessions.test.ts @@ -1,5 +1,5 @@ -import { act,renderHook } from '@testing-library/react'; -import { afterEach,assert, beforeEach, describe, expect, it, vi } from 'vitest'; +import { act, renderHook } from '@testing-library/react'; +import { afterEach, assert, beforeEach, describe, expect, it, vi } from 'vitest'; import { useTerminalSessions } from '../../../src/hooks/useTerminalSessions'; @@ -187,7 +187,7 @@ describe('useTerminalSessions', () => { const initialTime = result.current.sessions.get(sessionId)?.lastActivityAt; // Wait a bit to ensure time difference - await new Promise(resolve => setTimeout(resolve, 10)); + await new Promise((resolve) => setTimeout(resolve, 10)); act(() => { result.current.activateSession(sessionId); @@ -251,7 +251,9 @@ describe('useTerminalSessions', () => { }); // Sort by order to verify reordering (Map iteration order is insertion order) - const sessions = Array.from(result.current.sessions.values()).sort((a, b) => a.order - b.order); + const sessions = Array.from(result.current.sessions.values()).sort( + (a, b) => a.order - b.order + ); expect(sessions[0]?.order).toBe(0); expect(sessions[1]?.order).toBe(1); expect(sessions[2]?.order).toBe(2); @@ -351,10 +353,10 @@ describe('useTerminalSessions', () => { const loaded = result.current.getPersistedSessions(); assert(loaded !== null, 'expected loaded sessions'); expect(loaded).toHaveLength(2); - expect(loaded[0].serverSessionId).toBe('server-111'); - expect(loaded[0].name).toBe('Tab 1'); - expect(loaded[1].serverSessionId).toBe('server-222'); - expect(loaded[1].name).toBe('Tab 2'); + expect(loaded[0]?.serverSessionId).toBe('server-111'); + expect(loaded[0]?.name).toBe('Tab 1'); + expect(loaded[1]?.serverSessionId).toBe('server-222'); + expect(loaded[1]?.name).toBe('Tab 2'); }); it('should restore counter from persisted state', () => { @@ -427,7 +429,7 @@ describe('useTerminalSessions', () => { // Sessions should be persisted in order const sorted = [...parsed.sessions].sort( - (a: { order: number }, b: { order: number }) => a.order - b.order, + (a: { order: number }, b: { order: number }) => a.order - b.order ); expect(sorted[0].name).toBe('First'); expect(sorted[0].serverSessionId).toBe('server-aaa'); @@ -452,16 +454,19 @@ describe('useTerminalSessions', () => { const loaded = result.current.getPersistedSessions(); assert(loaded !== null, 'expected loaded sessions'); expect(loaded).toHaveLength(2); - expect(loaded[0].name).toBe('Dev Server'); - expect(loaded[1].name).toBe('Build'); + expect(loaded[0]?.name).toBe('Dev Server'); + expect(loaded[1]?.name).toBe('Build'); + + const [firstPersisted, secondPersisted] = loaded; + assert(firstPersisted && secondPersisted, 'expected both persisted sessions'); // Simulate what MultiTerminal does when server returns empty session_list: // create fresh sessions with the persisted names let freshId1 = ''; let freshId2 = ''; act(() => { - freshId1 = result.current.createSession(loaded[0].name); - freshId2 = result.current.createSession(loaded[1].name); + freshId1 = result.current.createSession(firstPersisted.name); + freshId2 = result.current.createSession(secondPersisted.name); }); // Verify sessions were created with the original names @@ -479,7 +484,7 @@ describe('useTerminalSessions', () => { assert(raw !== null, 'expected persisted state'); const parsed = JSON.parse(raw); const sorted = [...parsed.sessions].sort( - (a: { order: number }, b: { order: number }) => a.order - b.order, + (a: { order: number }, b: { order: number }) => a.order - b.order ); expect(sorted[0].serverSessionId).toBe('new-srv-AAA'); expect(sorted[1].serverSessionId).toBe('new-srv-BBB'); @@ -513,14 +518,12 @@ describe('useTerminalSessions', () => { }; sessionStorage.setItem(PERSISTENCE_KEY, JSON.stringify(persisted)); - const { result } = renderHook(() => - useTerminalSessions(10, PERSISTENCE_KEY, wsUrl) - ); + const { result } = renderHook(() => useTerminalSessions(10, PERSISTENCE_KEY, wsUrl)); const loaded = result.current.getPersistedSessions(); assert(loaded !== null, 'expected loaded sessions'); expect(loaded).toHaveLength(1); - expect(loaded[0].name).toBe('Tab 1'); + expect(loaded[0]?.name).toBe('Tab 1'); }); it('should clear malformed storage on parse failure', () => { @@ -536,9 +539,7 @@ describe('useTerminalSessions', () => { it('should persist wsUrl in storage for scope validation', () => { const wsUrl = 'ws://myhost/ws'; - const { result } = renderHook(() => - useTerminalSessions(10, PERSISTENCE_KEY, wsUrl) - ); + const { result } = renderHook(() => useTerminalSessions(10, PERSISTENCE_KEY, wsUrl)); act(() => { result.current.createSession('Tab 1'); @@ -700,4 +701,4 @@ describe('useTerminalSessions', () => { expect(result.current.activeSessionId).toBe(id1); }); }); -}); \ No newline at end of file +}); diff --git a/packages/terminal/tests/unit/protocol.test.ts b/packages/terminal/tests/unit/protocol.test.ts index 7d66a3307c..1fc7171fe8 100644 --- a/packages/terminal/tests/unit/protocol.test.ts +++ b/packages/terminal/tests/unit/protocol.test.ts @@ -1,4 +1,4 @@ -import { assert, describe, expect,it } from 'vitest'; +import { assert, describe, expect, it } from 'vitest'; import { encodeTerminalWsCreateSession, @@ -15,11 +15,15 @@ import { describe('terminal WebSocket protocol', () => { it('encodes input messages expected by the VM Agent', () => { - expect(encodeTerminalWsInput('ls\n')).toBe(JSON.stringify({ type: 'input', data: { data: 'ls\n' } })); + expect(encodeTerminalWsInput('ls\n')).toBe( + JSON.stringify({ type: 'input', data: { data: 'ls\n' } }) + ); }); it('encodes resize messages expected by the VM Agent', () => { - expect(encodeTerminalWsResize(24, 80)).toBe(JSON.stringify({ type: 'resize', data: { rows: 24, cols: 80 } })); + expect(encodeTerminalWsResize(24, 80)).toBe( + JSON.stringify({ type: 'resize', data: { rows: 24, cols: 80 } }) + ); }); it('encodes ping messages expected by the VM Agent', () => { @@ -170,8 +174,8 @@ describe('isSessionListMessage', () => { if (isSessionListMessage(msg) && msg.data) { expect(msg.data.sessions).toHaveLength(2); - expect(msg.data.sessions[0].status).toBe('running'); - expect(msg.data.sessions[1].status).toBe('exited'); + expect(msg.data.sessions[0]?.status).toBe('running'); + expect(msg.data.sessions[1]?.status).toBe('exited'); } }); diff --git a/packages/terminal/tsconfig.test.json b/packages/terminal/tsconfig.test.json new file mode 100644 index 0000000000..fa8d5c1aae --- /dev/null +++ b/packages/terminal/tsconfig.test.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": ["src/**/*", "tests/**/*"], + "exclude": ["node_modules", "dist", "coverage"] +} diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts index 70eada1843..b6062b961d 100644 --- a/packages/ui/.storybook/main.ts +++ b/packages/ui/.storybook/main.ts @@ -1,4 +1,6 @@ import type { StorybookConfig } from '@storybook/react-vite'; +import tailwindcss from '@tailwindcss/vite'; +import { mergeConfig } from 'vite'; const config: StorybookConfig = { framework: { @@ -6,7 +8,10 @@ const config: StorybookConfig = { options: {}, }, stories: ['../src/**/*.stories.@(ts|tsx)'], - addons: ['@storybook/addon-essentials', '@storybook/addon-a11y'], + addons: ['@storybook/addon-docs', '@storybook/addon-a11y'], + async viteFinal(config) { + return mergeConfig(config, { plugins: [tailwindcss()] }); + }, }; export default config; diff --git a/packages/ui/.storybook/preview.css b/packages/ui/.storybook/preview.css new file mode 100644 index 0000000000..6addcceb16 --- /dev/null +++ b/packages/ui/.storybook/preview.css @@ -0,0 +1,18 @@ +@import 'tailwindcss'; +@import '../src/styles.css'; +@source '../src/**/*.{ts,tsx}'; + +html, +body, +#storybook-root { + min-height: 100%; +} + +body { + box-sizing: border-box; + margin: 0; + padding: var(--sam-space-6); + background: var(--sam-color-bg-canvas); + color: var(--sam-color-fg-primary); + font-family: Inter, ui-sans-serif, system-ui, sans-serif; +} diff --git a/packages/ui/.storybook/preview.ts b/packages/ui/.storybook/preview.ts new file mode 100644 index 0000000000..63af3327fb --- /dev/null +++ b/packages/ui/.storybook/preview.ts @@ -0,0 +1,38 @@ +import './preview.css'; + +import type { Preview } from '@storybook/react-vite'; + +const preview: Preview = { + globalTypes: { + theme: { + description: 'SAM color theme', + defaultValue: 'sam', + toolbar: { + icon: 'paintbrush', + items: [ + { value: 'sam', title: 'Dark' }, + { value: 'sam-light', title: 'Light' }, + ], + }, + }, + }, + initialGlobals: { + theme: 'sam', + }, + decorators: [ + (Story, context) => { + const theme = context.globals.theme === 'sam-light' ? 'sam-light' : 'sam'; + document.documentElement.dataset.uiTheme = theme; + document.body.dataset.uiTheme = theme; + return Story(); + }, + ], + parameters: { + a11y: { + test: 'error', + }, + layout: 'centered', + }, +}; + +export default preview; diff --git a/packages/ui/package.json b/packages/ui/package.json index b0db477815..cfa784d25e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -17,9 +17,13 @@ }, "scripts": { "build": "tsc", - "typecheck": "tsc --noEmit", + "typecheck": "tsc --noEmit && tsc --project tsconfig.storybook.json --noEmit", "test": "vitest run", - "lint": "eslint 'src/**/*.{ts,tsx}'" + "test:coverage": "vitest run --coverage", + "storybook": "storybook dev --port 6006", + "build-storybook": "storybook build", + "test:storybook": "playwright test --config playwright.storybook.config.ts", + "lint": "eslint 'src/**/*.{ts,tsx}' '.storybook/**/*.{ts,tsx}' 'tests/playwright/**/*.{ts,tsx}' 'playwright.storybook.config.ts'" }, "peerDependencies": { "lucide-react": "catalog:", @@ -29,18 +33,29 @@ "tailwindcss": "^4.0.0" }, "devDependencies": { + "@axe-core/playwright": "4.12.1", + "@playwright/test": "1.62.1", + "@storybook/addon-a11y": "10.5.7", + "@storybook/addon-docs": "10.5.7", + "@storybook/react": "10.5.7", + "@storybook/react-vite": "10.5.7", + "@tailwindcss/vite": "4.2.3", "@testing-library/dom": "catalog:", "@testing-library/jest-dom": "catalog:", "@testing-library/react": "catalog:", "@types/react": "catalog:", "@types/react-dom": "catalog:", "@vitejs/plugin-react": "catalog:", + "@vitest/coverage-v8": "catalog:", "jsdom": "catalog:", "lucide-react": "catalog:", "react": "catalog:", "react-dom": "catalog:", "react-router": "catalog:", + "storybook": "10.5.7", + "tailwindcss": "4.3.2", "typescript": "catalog:", + "vite": "catalog:", "vitest": "catalog:" } } diff --git a/packages/ui/playwright.storybook.config.ts b/packages/ui/playwright.storybook.config.ts new file mode 100644 index 0000000000..6566ab8c28 --- /dev/null +++ b/packages/ui/playwright.storybook.config.ts @@ -0,0 +1,29 @@ +import { defineConfig, devices } from '@playwright/test'; + +export default defineConfig({ + testDir: './tests/playwright', + timeout: 30_000, + expect: { timeout: 5_000 }, + fullyParallel: true, + use: { + baseURL: 'http://127.0.0.1:6006', + screenshot: 'only-on-failure', + trace: 'on-first-retry', + }, + projects: [ + { + name: 'Desktop Chrome', + use: { ...devices['Desktop Chrome'], viewport: { width: 1280, height: 800 } }, + }, + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'], viewport: { width: 375, height: 667 } }, + }, + ], + webServer: { + command: 'python3 -m http.server 6006 --bind 127.0.0.1 --directory storybook-static', + url: 'http://127.0.0.1:6006/index.json', + reuseExistingServer: false, + timeout: 30_000, + }, +}); diff --git a/packages/ui/tests/playwright/storybook-a11y.spec.ts b/packages/ui/tests/playwright/storybook-a11y.spec.ts new file mode 100644 index 0000000000..c3722718f4 --- /dev/null +++ b/packages/ui/tests/playwright/storybook-a11y.spec.ts @@ -0,0 +1,49 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect, test } from '@playwright/test'; + +const stories = [ + 'components-button--primary', + 'components-button--secondary', + 'components-button--loading', + 'components-button--danger', + 'components-statusbadge--running', + 'components-statusbadge--creating', + 'components-statusbadge--disconnected', + 'components-statusbadge--mobile-label', +] as const; + +const themes = ['sam', 'sam-light'] as const; + +for (const theme of themes) { + for (const story of stories) { + test(`${story} renders without overflow or serious axe violations in ${theme}`, async ({ + page, + }, testInfo) => { + const query = new URLSearchParams({ + id: story, + globals: `theme:${theme}`, + viewMode: 'story', + }); + await page.goto(`/iframe.html?${query.toString()}`); + await expect(page.locator('#storybook-root')).not.toBeEmpty(); + + const hasHorizontalOverflow = await page.evaluate( + () => document.documentElement.scrollWidth > document.documentElement.clientWidth + ); + expect(hasHorizontalOverflow).toBe(false); + + const axeResults = await new AxeBuilder({ page }).include('#storybook-root').analyze(); + const seriousViolations = axeResults.violations.filter( + (violation) => violation.impact === 'critical' || violation.impact === 'serious' + ); + expect(seriousViolations, JSON.stringify(seriousViolations, null, 2)).toEqual([]); + + const project = testInfo.project.name.toLowerCase().replace(/\W+/g, '-'); + await page.screenshot({ + path: `../../.codex/tmp/playwright-screenshots/storybook-${story}-${theme}-${project}.png`, + fullPage: true, + animations: 'disabled', + }); + }); + } +} diff --git a/packages/ui/tsconfig.storybook.json b/packages/ui/tsconfig.storybook.json new file mode 100644 index 0000000000..94f15df791 --- /dev/null +++ b/packages/ui/tsconfig.storybook.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "noEmit": true, + "rootDir": "." + }, + "include": [ + ".storybook/**/*.ts", + "src/**/*.stories.tsx", + "playwright.storybook.config.ts", + "tests/playwright/**/*.ts" + ], + "exclude": ["node_modules", "dist", "coverage", "storybook-static"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a70a642ad9..4082a3e57b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,12 +283,15 @@ importers: '@cloudflare/workers-types': specifier: 'catalog:' version: 5.20260707.1 + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.7(vitest@4.1.5) typescript: specifier: 'catalog:' version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) wrangler: specifier: 'catalog:' version: 4.118.0(@cloudflare/workers-types@5.20260707.1) @@ -459,9 +462,15 @@ importers: specifier: 11.14.0 version: 11.14.0 devDependencies: + '@axe-core/playwright': + specifier: 4.12.1 + version: 4.12.1(playwright-core@1.62.1) '@playwright/test': specifier: 1.62.1 version: 1.62.1 + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.7(vitest@4.1.5) esbuild: specifier: 0.28.1 version: 0.28.1 @@ -487,12 +496,15 @@ importers: '@types/node': specifier: 'catalog:' version: 22.19.7 + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.7(vitest@4.1.5) typescript: specifier: 'catalog:' version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@22.19.7)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@22.19.7)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@22.19.7)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@22.19.7)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) packages/acp-client: dependencies: @@ -560,6 +572,9 @@ importers: packages/cloud-init: devDependencies: + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.7(vitest@4.1.5) tsup: specifier: 8.5.1 version: 8.5.1(jiti@2.6.1)(postcss@8.5.16)(tsx@4.23.1)(typescript@5.9.3)(yaml@2.9.0) @@ -568,7 +583,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) yaml: specifier: 2.9.0 version: 2.9.0 @@ -685,11 +700,28 @@ importers: version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) packages/ui: - dependencies: - tailwindcss: - specifier: ^4.0.0 - version: 4.2.1 devDependencies: + '@axe-core/playwright': + specifier: 4.12.1 + version: 4.12.1(playwright-core@1.62.1) + '@playwright/test': + specifier: 1.62.1 + version: 1.62.1 + '@storybook/addon-a11y': + specifier: 10.5.7 + version: 10.5.7(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7)) + '@storybook/addon-docs': + specifier: 10.5.7 + version: 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@storybook/react': + specifier: 10.5.7 + version: 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(typescript@5.9.3) + '@storybook/react-vite': + specifier: 10.5.7 + version: 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(typescript@5.9.3)(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@tailwindcss/vite': + specifier: 4.2.3 + version: 4.2.3(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) '@testing-library/dom': specifier: 'catalog:' version: 10.4.1 @@ -708,6 +740,9 @@ importers: '@vitejs/plugin-react': specifier: 'catalog:' version: 5.2.0(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/coverage-v8': + specifier: 'catalog:' + version: 4.1.7(vitest@4.1.5) jsdom: specifier: 'catalog:' version: 29.1.1(@noble/hashes@2.0.1) @@ -723,12 +758,21 @@ importers: react-router: specifier: 'catalog:' version: 7.14.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + storybook: + specifier: 10.5.7 + version: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + tailwindcss: + specifier: 4.3.2 + version: 4.3.2 typescript: specifier: 'catalog:' version: 5.9.3 + vite: + specifier: 'catalog:' + version: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) vitest: specifier: 'catalog:' - version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + version: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) tools/og-image: dependencies: @@ -949,6 +993,11 @@ packages: resolution: {integrity: sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==} engines: {node: '>=18.0.0'} + '@axe-core/playwright@4.12.1': + resolution: {integrity: sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw==} + peerDependencies: + playwright-core: '>= 1.0.0' + '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -991,10 +1040,6 @@ packages: resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.29.7': resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} @@ -1450,12 +1495,27 @@ packages: '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + '@emnapi/core@1.11.2': + resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==} + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.11.1': resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + '@emnapi/runtime@1.11.2': + resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.8.1': resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} @@ -2589,6 +2649,15 @@ packages: resolution: {integrity: sha512-7kMz0BJpMvgAMkyglums7B2vtrn5g0a0am77JY0GjkZZNetOBCFn7AG7gKCwT0QPiXyxW7YIQSgtARknUEOcxQ==} engines: {node: '>=12'} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0': + resolution: {integrity: sha512-qvsTEwEFefhdirGOPnu9Wp6ChfIwy2dBCRuETU3uE+4cC+PFoxMSiiEhxk4lOluA34eARHA0OxqsEUYDqRMgeQ==} + peerDependencies: + typescript: '>= 4.3.x' + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -2641,6 +2710,12 @@ packages: '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + '@mermaid-js/parser@1.1.0': resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} @@ -2821,9 +2896,226 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + '@oxc-project/types@0.138.0': resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==} + cpu: [arm] + os: [android] + + '@oxc-resolver/binding-android-arm64@11.24.2': + resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==} + cpu: [arm64] + os: [android] + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==} + cpu: [arm64] + os: [darwin] + + '@oxc-resolver/binding-darwin-x64@11.24.2': + resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==} + cpu: [x64] + os: [darwin] + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==} + cpu: [x64] + os: [freebsd] + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==} + cpu: [arm] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==} + cpu: [arm64] + os: [linux] + + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==} + cpu: [ppc64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==} + cpu: [riscv64] + os: [linux] + + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==} + cpu: [s390x] + os: [linux] + + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==} + cpu: [x64] + os: [linux] + + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==} + cpu: [arm64] + os: [openharmony] + + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==} + cpu: [arm64] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==} + cpu: [x64] + os: [win32] + '@pagefind/darwin-arm64@1.5.2': resolution: {integrity: sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==} cpu: [arm64] @@ -3428,6 +3720,95 @@ packages: '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + '@storybook/addon-a11y@10.5.7': + resolution: {integrity: sha512-I30rsNz6aA3xg3811MEry40uJDHP3l5SOkfqtmNkp7y4NdqTDdKhmbhhDAZQX1WWEk6GeMBGr3AJ3TCz7r7JmQ==} + peerDependencies: + storybook: ^10.5.7 + + '@storybook/addon-docs@10.5.7': + resolution: {integrity: sha512-KNARJfjICaizinsR3INMEiipZm1ObYo+xw+E26gteu50Bcy2dIZUtk5uHY5XdtardU3AXX6yRXoBZ2HCY3lbHA==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.7 + peerDependenciesMeta: + '@types/react': + optional: true + + '@storybook/builder-vite@10.5.7': + resolution: {integrity: sha512-fShF/aQaITqcJuMCLr42BGNUAbhDi4IboqvlbZqXAwgrrTslnZEUnY8GcEcvpZmjl11VwlmazhMJdH50fIgBPg==} + peerDependencies: + storybook: ^10.5.7 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@storybook/csf-plugin@10.5.7': + resolution: {integrity: sha512-IaX8FlM0H36HNFhJ2+4L9bCldqfvHGqcLg841SJNyK/DhfMlM7JsvY/GDH2ZFuWrUf8FSOx96GRRnHq6XfRKag==} + peerDependencies: + esbuild: '*' + rollup: '*' + storybook: ^10.5.7 + vite: '*' + webpack: '*' + peerDependenciesMeta: + esbuild: + optional: true + rollup: + optional: true + vite: + optional: true + webpack: + optional: true + + '@storybook/global@5.0.0': + resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==} + + '@storybook/icons@2.1.0': + resolution: {integrity: sha512-Fxh9vYpX9bQqFeHRiY8h2ApeRGDzRSMLwJwNZ/AIRqnyOKHxRKL+yFe+ctEkVJmuptRE9u1Hrn8ZZNHyfDKKNg==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@storybook/react-dom-shim@10.5.7': + resolution: {integrity: sha512-lxOkyh+wu/MiBXvYQHjZfD+DRKOa4bHBzbuGuiHXnHXmdOcTRdcrQTsoeN2FPtfugmmOG66cZUEgDwNX+k5eRA==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.7 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@storybook/react-vite@10.5.7': + resolution: {integrity: sha512-eEo3eVa2pvqrzQukKxAzx7YvswDAA1s6k/y+tdMxmRvWyHX6QEOsb9Tda6wcVaa7c8BeJM7Ggq+289cRMTH6Iw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.7 + typescript: '>= 4.9.x' + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@storybook/react@10.5.7': + resolution: {integrity: sha512-uFvty2MMdFXzW5PcQe1JqDAZkz6cQq7q/9G/cbGVnBEvP6zsOVeL+bmrQ0/WBlFQN0Ko9+ZoCTvaQ9s65zBa5g==} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@types/react-dom': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + storybook: ^10.5.7 + typescript: '>= 4.9.x' + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + typescript: + optional: true + '@szmarczak/http-timer@4.0.6': resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} engines: {node: '>=10'} @@ -3757,6 +4138,9 @@ packages: resolution: {integrity: sha512-o7jqJM04gfaYrdCecCVMbZhNdG6T1MHg/oQoRFdERLV+4d+V7FijhiEAbFu0Usww84Yijk9yH58U4Jk4HbtzZw==} deprecated: This is a stub types definition. diff provides its own type definitions, so you do not need this installed. + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -3843,6 +4227,9 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@types/resolve@1.20.6': + resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -3999,6 +4386,9 @@ packages: '@vitest/browser': optional: true + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.5': resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} @@ -4013,6 +4403,9 @@ packages: vite: optional: true + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + '@vitest/pretty-format@4.1.5': resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} @@ -4025,15 +4418,24 @@ packages: '@vitest/snapshot@4.1.5': resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/spy@4.1.5': resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/utils@4.1.5': resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} '@vitest/utils@4.1.7': resolution: {integrity: sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==} + '@webcontainer/env@1.1.1': + resolution: {integrity: sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==} + '@workflow/serde@4.1.0': resolution: {integrity: sha512-pav4F2BoirECWR7Nf1TKt+2eETcBj7jj4cBefQ8VXQCA6NPkaKeLfj/zMgi+3zYV5ZIBT4GuUiphsj0/b9hPQQ==} @@ -4221,6 +4623,10 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + ast-v8-to-istanbul@1.0.2: resolution: {integrity: sha512-dKmJxJsGItLmc5CYZKuEjuG6GnBs6PG4gohMhyFOWKaNQoYCuRZJDECaBlHmcG0lv2wc2E0uU8lESmBEumC3DQ==} @@ -4262,6 +4668,10 @@ packages: resolution: {integrity: sha512-byD6KPdvo72y/wj2T/4zGEvvlis+PsZsn/yPS3pEO+sFpcrqRpX/TJCxvVaEsNeMrfQbCr7w163YqoD9IYwHXw==} engines: {node: '>=4'} + axe-core@4.12.1: + resolution: {integrity: sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==} + engines: {node: '>=4'} + axios@1.18.0: resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==} @@ -4435,6 +4845,10 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bundle-name@4.1.0: + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} + engines: {node: '>=18'} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -4489,6 +4903,10 @@ packages: ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} @@ -4509,6 +4927,10 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + chevrotain-allstar@0.4.1: resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==} peerDependencies: @@ -4999,6 +5421,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} engines: {node: '>=4.0.0'} @@ -5006,6 +5432,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} + engines: {node: '>=18'} + + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} + engines: {node: '>=18'} + defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -5014,6 +5448,10 @@ packages: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} + define-lazy-prop@3.0.0: + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} + engines: {node: '>=12'} + define-properties@1.2.1: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} @@ -5232,6 +5670,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + empathic@2.0.1: + resolution: {integrity: sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==} + engines: {node: '>=14'} + encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} @@ -5747,6 +6189,10 @@ packages: resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==} engines: {node: 20 || >=22} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me @@ -6543,6 +6989,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} @@ -6909,6 +7358,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} @@ -6940,11 +7393,6 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@3.3.16: - resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - nanoid@3.3.17: resolution: {integrity: sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -7115,6 +7563,10 @@ packages: oniguruma-to-es@4.3.6: resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} + open@10.2.0: + resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} + engines: {node: '>=18'} + openapi-types@12.1.3: resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} @@ -7126,6 +7578,13 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxc-parser@0.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + engines: {node: ^20.19.0 || >=22.12.0} + + oxc-resolver@11.24.2: + resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==} + p-cancelable@2.1.1: resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} engines: {node: '>=8'} @@ -7256,6 +7715,10 @@ packages: resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} @@ -7272,6 +7735,10 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + piccolore@0.1.3: resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} @@ -7499,6 +7966,15 @@ packages: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true + react-docgen-typescript@2.4.0: + resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==} + peerDependencies: + typescript: '>= 4.3.x' + + react-docgen@8.0.3: + resolution: {integrity: sha512-aEZ9qP+/M+58x2qgfSFEWH1BxLyHe5+qkLNJOZQb5iGS017jpbRnoKhNRrXPeA6RfBrZO5wZrT9DMC1UqE1f1w==} + engines: {node: ^20.9.0 || >=22} + react-dom@19.2.7: resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: @@ -7579,6 +8055,10 @@ packages: resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} engines: {node: '>= 20.19.0'} + recast@0.23.19: + resolution: {integrity: sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==} + engines: {node: '>= 4'} + recharts@3.10.0: resolution: {integrity: sha512-wulMvfncpIlmu2uFtRU/mE5/+NiVtASXkw2KdwJTdHs3WsASX0WxZlX+rpKgyn5BDbIhkPtCpUKkB9XNK5KE0w==} engines: {node: '>=18'} @@ -7762,6 +8242,10 @@ packages: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} + run-applescript@7.1.0: + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} + engines: {node: '>=18'} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -8019,6 +8503,21 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + storybook@10.5.7: + resolution: {integrity: sha512-oiKvWIwIoOhFP1i6dASYyMXwPHKEtVZMshqSB7EvIVYjWRh0l9H7gHEt1z4Gh2rLGFMekWdsm4s94rvwpR7gkg==} + hasBin: true + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + prettier: ^2 || ^3 + vite-plus: ^0.1.15 || ^0.2.0 + peerDependenciesMeta: + '@types/react': + optional: true + prettier: + optional: true + vite-plus: + optional: true + stream-replace-string@2.0.0: resolution: {integrity: sha512-TlnjJ1C0QrmxRNrON00JvaFFlNh5TTG00APw23j74ET7gkQpTASi6/L2fuiav8pzK715HXtUeClpBTw2NPSn6w==} @@ -8078,6 +8577,10 @@ packages: resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} engines: {node: '>=0.10.0'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + strip-final-newline@2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -8090,6 +8593,10 @@ packages: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -8132,9 +8639,6 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwindcss@4.2.1: - resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} - tailwindcss@4.2.3: resolution: {integrity: sha512-fA/NX5gMf0ooCLISgB0wScaWgaj6rjTN2SVAwleURjiya7ITNkV+VMmoHtKkldP6CIZoYCZyxb8zP/e2TWoEtQ==} @@ -8208,6 +8712,10 @@ packages: resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -8216,6 +8724,10 @@ packages: resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} engines: {node: '>=14.0.0'} + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + tldts-core@7.0.29: resolution: {integrity: sha512-W99NuU7b1DcG3uJ3v9k9VztCH3WialNbBkBft5wCs8V8mexu0XQqaZEYb9l9RNNzK8+3EJ9PKWB0/RUtTQ/o+Q==} @@ -8287,6 +8799,10 @@ packages: ts-morph@28.0.0: resolution: {integrity: sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==} + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -8450,6 +8966,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + unstorage@1.17.5: resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} peerDependencies: @@ -8739,6 +9259,9 @@ packages: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -8861,6 +9384,22 @@ packages: utf-8-validate: optional: true + ws@8.21.3: + resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + wsl-utils@0.1.0: + resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} + engines: {node: '>=18'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -9359,6 +9898,11 @@ snapshots: '@aws/lambda-invoke-store@0.3.0': {} + '@axe-core/playwright@4.12.1(playwright-core@1.62.1)': + dependencies: + axe-core: 4.12.1 + playwright-core: 1.62.1 + '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -9396,7 +9940,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 @@ -9414,7 +9958,7 @@ snapshots: '@babel/helper-module-imports@7.28.6': dependencies: '@babel/traverse': 7.29.0 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -9422,15 +9966,13 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-validator-identifier': 7.29.7 '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-string-parser@7.29.7': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -9442,20 +9984,19 @@ snapshots: '@babel/helpers@7.28.6': dependencies: '@babel/template': 7.28.6 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/parser@7.29.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/parser@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/parser@7.29.8': dependencies: '@babel/types': 7.29.8 - optional: true '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': dependencies: @@ -9471,26 +10012,26 @@ snapshots: '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.29.0 + '@babel/code-frame': 7.29.7 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.28.6 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3 transitivePeerDependencies: - supports-color '@babel/types@7.29.0': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@babel/types@7.29.7': dependencies: @@ -9501,7 +10042,6 @@ snapshots: dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - optional: true '@bcoe/v8-coverage@1.0.2': {} @@ -9873,16 +10413,43 @@ snapshots: tslib: 2.8.1 optional: true + '@emnapi/core@1.11.2': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.11.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 @@ -10578,6 +11145,14 @@ snapshots: '@isaacs/ttlcache@2.1.4': {} + '@joshwooding/vite-plugin-react-docgen-typescript@0.7.0(typescript@5.9.3)(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + glob: 13.0.6 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + optionalDependencies: + typescript: 5.9.3 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -10696,6 +11271,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.2.17 + react: 19.2.7 + '@mermaid-js/parser@1.1.0': dependencies: langium: 4.2.2 @@ -10729,6 +11310,20 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@tybys/wasm-util': 0.10.3 + optional: true + + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.3 + optional: true + '@noble/ciphers@2.1.1': {} '@noble/hashes@2.0.1': {} @@ -10896,60 +11491,187 @@ snapshots: transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.55.0(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/api-logs': 0.55.0 - '@types/shimmer': 1.2.0 - import-in-the-middle: 1.15.0 - require-in-the-middle: 7.5.2 - semver: 7.7.3 - shimmer: 1.2.1 - transitivePeerDependencies: - - supports-color + '@opentelemetry/instrumentation@0.55.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/api-logs': 0.55.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.15.0 + require-in-the-middle: 7.5.2 + semver: 7.7.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + + '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + + '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) + semver: 7.7.3 + + '@opentelemetry/semantic-conventions@1.27.0': {} + + '@opentelemetry/semantic-conventions@1.28.0': {} + + '@opentelemetry/semantic-conventions@1.40.0': {} + + '@oslojs/encoding@1.1.0': {} + + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true + + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + optional: true + + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + optional: true + + '@oxc-project/types@0.127.0': {} + + '@oxc-project/types@0.138.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.24.2': + optional: true + + '@oxc-resolver/binding-android-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-arm64@11.24.2': + optional: true + + '@oxc-resolver/binding-darwin-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-freebsd-x64@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2': + optional: true + + '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2': + optional: true - '@opentelemetry/propagator-b3@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@oxc-resolver/binding-linux-arm64-gnu@11.24.2': + optional: true - '@opentelemetry/propagator-jaeger@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) + '@oxc-resolver/binding-linux-arm64-musl@11.24.2': + optional: true - '@opentelemetry/resources@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2': + optional: true - '@opentelemetry/sdk-trace-base@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.28.0 + '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2': + optional: true - '@opentelemetry/sdk-trace-node@1.30.1(@opentelemetry/api@1.9.0)': - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/core': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-b3': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/propagator-jaeger': 1.30.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.30.1(@opentelemetry/api@1.9.0) - semver: 7.7.3 + '@oxc-resolver/binding-linux-riscv64-musl@11.24.2': + optional: true - '@opentelemetry/semantic-conventions@1.27.0': {} + '@oxc-resolver/binding-linux-s390x-gnu@11.24.2': + optional: true - '@opentelemetry/semantic-conventions@1.28.0': {} + '@oxc-resolver/binding-linux-x64-gnu@11.24.2': + optional: true - '@opentelemetry/semantic-conventions@1.40.0': {} + '@oxc-resolver/binding-linux-x64-musl@11.24.2': + optional: true - '@oslojs/encoding@1.1.0': {} + '@oxc-resolver/binding-openharmony-arm64@11.24.2': + optional: true - '@oxc-project/types@0.138.0': {} + '@oxc-resolver/binding-wasm32-wasi@11.24.2': + dependencies: + '@emnapi/core': 1.11.2 + '@emnapi/runtime': 1.11.2 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2) + optional: true + + '@oxc-resolver/binding-win32-arm64-msvc@11.24.2': + optional: true + + '@oxc-resolver/binding-win32-x64-msvc@11.24.2': + optional: true '@pagefind/darwin-arm64@1.5.2': optional: true @@ -11429,6 +12151,107 @@ snapshots: '@standard-schema/utils@0.3.0': {} + '@storybook/addon-a11y@10.5.7(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))': + dependencies: + '@storybook/global': 5.0.0 + axe-core: 4.11.2 + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + + '@storybook/addon-docs@10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) + '@storybook/csf-plugin': 10.5.7(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@storybook/icons': 2.1.0(react@19.2.7) + '@storybook/react-dom-shim': 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7)) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + ts-dedent: 2.2.0 + optionalDependencies: + '@types/react': 19.2.17 + transitivePeerDependencies: + - '@types/react-dom' + - esbuild + - rollup + - vite + - webpack + + '@storybook/builder-vite@10.5.7(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + '@storybook/csf-plugin': 10.5.7(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + ts-dedent: 2.2.0 + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + transitivePeerDependencies: + - esbuild + - rollup + - webpack + + '@storybook/csf-plugin@10.5.7(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + unplugin: 2.3.11 + optionalDependencies: + esbuild: 0.28.1 + rollup: 4.56.0 + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + + '@storybook/global@5.0.0': {} + + '@storybook/icons@2.1.0(react@19.2.7)': + dependencies: + react: 19.2.7 + + '@storybook/react-dom-shim@10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))': + dependencies: + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@storybook/react-vite@10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(esbuild@0.28.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(typescript@5.9.3)(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0))': + dependencies: + '@joshwooding/vite-plugin-react-docgen-typescript': 0.7.0(typescript@5.9.3)(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@rollup/pluginutils': 5.3.0(rollup@4.56.0) + '@storybook/builder-vite': 10.5.7(esbuild@0.28.1)(rollup@4.56.0)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@storybook/react': 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(typescript@5.9.3) + empathic: 2.0.1 + magic-string: 0.30.21 + react: 19.2.7 + react-docgen: 8.0.3 + react-dom: 19.2.7(react@19.2.7) + resolve: 1.22.11 + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + tsconfig-paths: 4.2.0 + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - esbuild + - rollup + - supports-color + - webpack + + '@storybook/react@10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7))(typescript@5.9.3)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/react-dom-shim': 10.5.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7)) + react: 19.2.7 + react-docgen: 8.0.3 + react-docgen-typescript: 2.4.0(typescript@5.9.3) + react-dom: 19.2.7(react@19.2.7) + storybook: 10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + '@szmarczak/http-timer@4.0.6': dependencies: defer-to-connect: 2.0.1 @@ -11590,16 +12413,16 @@ snapshots: '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@types/better-sqlite3@7.6.13': dependencies: @@ -11776,6 +12599,8 @@ snapshots: dependencies: diff: 9.0.0 + '@types/doctrine@0.0.9': {} + '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.8 @@ -11871,6 +12696,8 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/resolve@1.20.6': {} + '@types/responselike@1.0.3': dependencies: '@types/node': 25.9.1 @@ -12079,6 +12906,14 @@ snapshots: tinyrainbow: 3.1.0 vitest: 4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + '@vitest/expect@4.1.5': dependencies: '@standard-schema/spec': 1.1.0 @@ -12120,6 +12955,10 @@ snapshots: optionalDependencies: vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + '@vitest/pretty-format@4.1.5': dependencies: tinyrainbow: 3.1.0 @@ -12140,8 +12979,18 @@ snapshots: magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + '@vitest/spy@4.1.5': {} + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + '@vitest/utils@4.1.5': dependencies: '@vitest/pretty-format': 4.1.5 @@ -12154,6 +13003,8 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@webcontainer/env@1.1.1': {} + '@workflow/serde@4.1.0': {} '@xterm/addon-attach@0.12.0': {} @@ -12219,8 +13070,7 @@ snapshots: acorn@8.17.0: {} - acorn@8.18.0: - optional: true + acorn@8.18.0: {} agent-base@6.0.2: dependencies: @@ -12364,6 +13214,10 @@ snapshots: ast-types-flow@0.0.8: {} + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + ast-v8-to-istanbul@1.0.2: dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -12491,6 +13345,8 @@ snapshots: axe-core@4.11.2: {} + axe-core@4.12.1: {} + axios@1.18.0: dependencies: follow-redirects: 1.16.0 @@ -12730,6 +13586,10 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bundle-name@4.1.0: + dependencies: + run-applescript: 7.1.0 + bundle-require@5.1.0(esbuild@0.27.7): dependencies: esbuild: 0.27.7 @@ -12792,6 +13652,14 @@ snapshots: ccount@2.0.1: {} + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + chai@6.2.2: {} chalk@4.1.2: @@ -12807,6 +13675,8 @@ snapshots: character-reference-invalid@2.0.1: {} + check-error@2.1.3: {} + chevrotain-allstar@0.4.1(chevrotain@12.0.0): dependencies: chevrotain: 12.0.0 @@ -13294,10 +14164,19 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-extend@0.6.0: {} deep-is@0.1.4: {} + default-browser-id@5.0.1: {} + + default-browser@5.5.0: + dependencies: + bundle-name: 4.1.0 + default-browser-id: 5.0.1 + defer-to-connect@2.0.1: {} define-data-property@1.1.4: @@ -13306,6 +14185,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + define-lazy-prop@3.0.0: {} + define-properties@1.2.1: dependencies: define-data-property: 1.1.4 @@ -13423,6 +14304,8 @@ snapshots: emoji-regex@9.2.2: {} + empathic@2.0.1: {} + encodeurl@2.0.0: {} encoding@0.1.13: @@ -13546,7 +14429,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 es-shim-unscopables@1.1.0: dependencies: @@ -14180,7 +15063,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.2 + hasown: 2.0.4 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -14254,6 +15137,12 @@ snapshots: minipass: 7.1.2 path-scurry: 2.0.1 + glob@13.0.6: + dependencies: + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -14688,7 +15577,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.4 side-channel: 1.1.0 internmap@1.0.1: {} @@ -14739,7 +15628,7 @@ snapshots: is-core-module@2.16.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -15124,6 +16013,8 @@ snapshots: dependencies: js-tokens: 4.0.0 + loupe@3.2.1: {} + lowercase-keys@2.0.0: {} lru-cache@10.4.3: {} @@ -15795,6 +16686,8 @@ snapshots: minipass@7.1.2: {} + minipass@7.1.3: {} + minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -15824,8 +16717,6 @@ snapshots: nanoid@3.3.15: {} - nanoid@3.3.16: {} - nanoid@3.3.17: {} nanostores@1.3.0: {} @@ -16011,6 +16902,13 @@ snapshots: regex: 6.1.0 regex-recursion: 6.0.2 + open@10.2.0: + dependencies: + default-browser: 5.5.0 + define-lazy-prop: 3.0.0 + is-inside-container: 1.0.0 + wsl-utils: 0.1.0 + openapi-types@12.1.3: {} optionator@0.9.4: @@ -16028,6 +16926,53 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxc-parser@0.127.0: + dependencies: + '@oxc-project/types': 0.127.0 + optionalDependencies: + '@oxc-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.24.2: + optionalDependencies: + '@oxc-resolver/binding-android-arm-eabi': 11.24.2 + '@oxc-resolver/binding-android-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-arm64': 11.24.2 + '@oxc-resolver/binding-darwin-x64': 11.24.2 + '@oxc-resolver/binding-freebsd-x64': 11.24.2 + '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2 + '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-arm64-musl': 11.24.2 + '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2 + '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-gnu': 11.24.2 + '@oxc-resolver/binding-linux-x64-musl': 11.24.2 + '@oxc-resolver/binding-openharmony-arm64': 11.24.2 + '@oxc-resolver/binding-wasm32-wasi': 11.24.2 + '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2 + '@oxc-resolver/binding-win32-x64-msvc': 11.24.2 + p-cancelable@2.1.1: {} p-limit@3.1.0: @@ -16179,6 +17124,11 @@ snapshots: lru-cache: 11.3.5 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.3.5 + minipass: 7.1.3 + path-to-regexp@0.1.12: {} path-to-regexp@6.3.0: {} @@ -16189,6 +17139,8 @@ snapshots: pathe@2.0.3: {} + pathval@2.0.1: {} + piccolore@0.1.3: {} picocolors@1.1.1: {} @@ -16266,7 +17218,7 @@ snapshots: postcss@8.5.6: dependencies: - nanoid: 3.3.16 + nanoid: 3.3.17 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -16402,6 +17354,25 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 + react-docgen-typescript@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + react-docgen@8.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.8 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + '@types/doctrine': 0.0.9 + '@types/resolve': 1.20.6 + doctrine: 3.0.0 + resolve: 1.22.11 + strip-indent: 4.1.1 + transitivePeerDependencies: + - supports-color + react-dom@19.2.7(react@19.2.7): dependencies: react: 19.2.7 @@ -16485,6 +17456,14 @@ snapshots: readdirp@5.0.0: {} + recast@0.23.19: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + recharts@3.10.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@17.0.2)(react@19.2.7)(redux@5.0.1): dependencies: '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7) @@ -16818,6 +17797,8 @@ snapshots: transitivePeerDependencies: - supports-color + run-applescript@7.1.0: {} + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -17184,6 +18165,33 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + storybook@10.5.7(@types/react@19.2.17)(prettier@3.8.3)(react@19.2.7): + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.2.7) + '@testing-library/dom': 10.4.1 + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + '@vitest/expect': 3.2.4 + '@vitest/spy': 3.2.4 + '@webcontainer/env': 1.1.1 + esbuild: 0.28.1 + jsonc-parser: 3.3.1 + open: 10.2.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.24.2 + recast: 0.23.19 + semver: 7.8.5 + use-sync-external-store: 1.6.0(react@19.2.7) + ws: 8.21.3 + optionalDependencies: + '@types/react': 19.2.17 + prettier: 3.8.3 + transitivePeerDependencies: + - bufferutil + - react + - utf-8-validate + stream-replace-string@2.0.0: {} string-argv@0.3.2: {} @@ -17271,6 +18279,8 @@ snapshots: strip-bom-string@1.0.0: {} + strip-bom@3.0.0: {} + strip-final-newline@2.0.0: {} strip-final-newline@4.0.0: {} @@ -17279,6 +18289,8 @@ snapshots: dependencies: min-indent: 1.0.1 + strip-indent@4.1.1: {} + strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {} @@ -17323,8 +18335,6 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@4.2.1: {} - tailwindcss@4.2.3: {} tailwindcss@4.3.2: {} @@ -17401,11 +18411,15 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@2.0.0: {} + tinyrainbow@3.1.0: {} tinyrainbow@3.1.1: optional: true + tinyspy@4.0.4: {} + tldts-core@7.0.29: {} tldts@7.0.29: @@ -17463,6 +18477,12 @@ snapshots: '@ts-morph/common': 0.29.0 code-block-writer: 13.0.3 + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} tsup@8.5.1(jiti@2.6.1)(postcss@8.5.16)(tsx@4.23.1)(typescript@5.9.3)(yaml@2.9.0): @@ -17678,6 +18698,13 @@ snapshots: unpipe@1.0.0: {} + unplugin@2.3.11: + dependencies: + '@jridgewell/remapping': 2.3.5 + acorn: 8.18.0 + picomatch: 4.0.5 + webpack-virtual-modules: 0.6.2 + unstorage@1.17.5(aws4fetch@1.0.20): dependencies: anymatch: 3.1.3 @@ -17873,10 +18900,10 @@ snapshots: transitivePeerDependencies: - msw - vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@22.19.7)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@22.19.7)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@22.19.7)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -17893,20 +18920,20 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.1.3(@types/node@22.19.7)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 25.9.1 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + '@types/node': 22.19.7 + '@vitest/coverage-v8': 4.1.7(vitest@4.1.5) jsdom: 29.1.1(@noble/hashes@2.0.1) transitivePeerDependencies: - msw - vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -17923,20 +18950,20 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.1.3(@types/node@25.9.1)(esbuild@0.28.1)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 '@types/node': 25.9.1 - '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + '@vitest/coverage-v8': 4.1.7(vitest@4.1.5) jsdom: 29.1.1(@noble/hashes@2.0.1) transitivePeerDependencies: - msw - vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): + vitest@4.1.5(@opentelemetry/api@1.9.0)(@types/node@25.9.1)(@vitest/coverage-v8@4.1.7)(jsdom@29.1.1(@noble/hashes@2.0.1))(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.5 - '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) + '@vitest/mocker': 4.1.5(vite@8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.5 '@vitest/runner': 4.1.5 '@vitest/snapshot': 4.1.5 @@ -17953,7 +18980,7 @@ snapshots: tinyexec: 1.1.1 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.1.3(@types/node@25.9.1)(esbuild@0.19.12)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) + vite: 8.1.3(@types/node@25.9.1)(esbuild@0.27.7)(jiti@2.6.1)(terser@5.46.0)(tsx@4.23.1)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -18020,6 +19047,8 @@ snapshots: webidl-conversions@8.0.1: {} + webpack-virtual-modules@0.6.2: {} + whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1(@noble/hashes@2.0.1): @@ -18166,6 +19195,12 @@ snapshots: ws@8.21.0: {} + ws@8.21.3: {} + + wsl-utils@0.1.0: + dependencies: + is-wsl: 3.1.1 + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} diff --git a/scripts/quality/check-workspace-test-surfaces.test.ts b/scripts/quality/check-workspace-test-surfaces.test.ts new file mode 100644 index 0000000000..8ed1cafeac --- /dev/null +++ b/scripts/quality/check-workspace-test-surfaces.test.ts @@ -0,0 +1,102 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { + assertWorkspaceTestSurfaces, + findWorkspaceTestSurfaceFindings, +} from './check-workspace-test-surfaces'; + +const temporaryRoots: string[] = []; + +function createFixture( + packages: Array<{ + path: string; + name: string; + scripts?: Record; + testFile?: string; + }> +): string { + const root = mkdtempSync(join(tmpdir(), 'workspace-test-surfaces-')); + temporaryRoots.push(root); + writeFileSync(join(root, 'pnpm-workspace.yaml'), "packages:\n - 'packages/*'\n"); + + for (const workspace of packages) { + const workspaceRoot = join(root, workspace.path); + mkdirSync(workspaceRoot, { recursive: true }); + writeFileSync( + join(workspaceRoot, 'package.json'), + JSON.stringify({ name: workspace.name, scripts: workspace.scripts ?? {} }) + ); + if (workspace.testFile) { + const testPath = join(workspaceRoot, workspace.testFile); + mkdirSync(dirname(testPath), { recursive: true }); + writeFileSync(testPath, "import { it } from 'vitest';\nit('runs', () => {});\n"); + } + } + return root; +} + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +describe('workspace test surface guard', () => { + it('fails visibly when a tested workspace omits its coverage script', () => { + const root = createFixture([ + { + path: 'packages/covered', + name: '@fixture/covered', + scripts: { test: 'vitest run', 'test:coverage': 'vitest run --coverage' }, + testFile: 'tests/covered.test.ts', + }, + { + path: 'packages/omitted', + name: '@fixture/omitted', + scripts: { test: 'vitest run' }, + testFile: 'tests/omitted.test.ts', + }, + ]); + + expect(() => assertWorkspaceTestSurfaces(root)).toThrow( + '@fixture/omitted (packages/omitted): missing test:coverage' + ); + }); + + it('detects a package with test files even when both scripts were omitted', () => { + const root = createFixture([ + { + path: 'packages/no-scripts', + name: '@fixture/no-scripts', + testFile: '__tests__/component.spec.tsx', + }, + ]); + + expect(findWorkspaceTestSurfaceFindings(root)).toEqual([ + { + workspace: 'packages/no-scripts', + packageName: '@fixture/no-scripts', + missingScripts: ['test', 'test:coverage'], + }, + ]); + }); + + it('passes when every tested workspace exposes both commands', () => { + const root = createFixture([ + { + path: 'packages/complete', + name: '@fixture/complete', + scripts: { test: 'vitest run', 'test:coverage': 'vitest run --coverage' }, + testFile: 'tests/complete.test.ts', + }, + { path: 'packages/no-tests', name: '@fixture/no-tests' }, + ]); + + expect(() => assertWorkspaceTestSurfaces(root)).not.toThrow(); + }); + + it('passes against the checked-in workspace graph', () => { + expect(() => assertWorkspaceTestSurfaces(resolve(import.meta.dirname, '../..'))).not.toThrow(); + }); +}); diff --git a/scripts/quality/check-workspace-test-surfaces.ts b/scripts/quality/check-workspace-test-surfaces.ts new file mode 100644 index 0000000000..5287ae42ce --- /dev/null +++ b/scripts/quality/check-workspace-test-surfaces.ts @@ -0,0 +1,144 @@ +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { dirname, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +type PackageManifest = { + name?: string; + scripts?: Record; +}; + +export type WorkspaceTestSurfaceFinding = { + workspace: string; + packageName: string; + missingScripts: Array<'test' | 'test:coverage'>; +}; + +const TEST_FILE_PATTERN = /(?:^|\/)(?:[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)|[^/]+_test\.go)$/; +const IGNORED_DIRECTORIES = new Set([ + '.git', + '.turbo', + '.wrangler', + 'coverage', + 'dist', + 'node_modules', + 'storybook-static', +]); + +function parseWorkspacePatterns(workspaceSource: string): string[] { + const patterns: string[] = []; + let inPackages = false; + + for (const line of workspaceSource.split('\n')) { + if (/^packages:\s*$/.test(line)) { + inPackages = true; + continue; + } + if (inPackages && /^\S/.test(line) && line.trim() !== '') break; + if (!inPackages) continue; + + const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/); + if (match?.[1]) patterns.push(match[1]); + } + + if (patterns.length === 0) { + throw new Error('pnpm-workspace.yaml does not declare any package patterns'); + } + return patterns; +} + +function expandWorkspacePattern(root: string, pattern: string): string[] { + if (pattern.startsWith('!')) { + throw new Error(`unsupported negated workspace pattern: ${pattern}`); + } + if (!pattern.includes('*')) return [resolve(root, pattern)]; + if (!pattern.endsWith('/*') || pattern.slice(0, -2).includes('*')) { + throw new Error(`unsupported workspace pattern: ${pattern}`); + } + + const parent = resolve(root, pattern.slice(0, -2)); + if (!existsSync(parent)) return []; + return readdirSync(parent, { withFileTypes: true }) + .filter((entry) => entry.isDirectory()) + .map((entry) => resolve(parent, entry.name)); +} + +function containsTestFile(directory: string, current = directory): boolean { + if (!existsSync(current)) return false; + + for (const entry of readdirSync(current, { withFileTypes: true })) { + if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) continue; + const entryPath = resolve(current, entry.name); + if (entry.isDirectory() && containsTestFile(directory, entryPath)) return true; + if (entry.isFile() && TEST_FILE_PATTERN.test(relative(directory, entryPath))) return true; + } + return false; +} + +function readManifest(manifestPath: string): PackageManifest { + try { + return JSON.parse(readFileSync(manifestPath, 'utf8')) as PackageManifest; + } catch (error) { + throw new Error(`invalid workspace manifest ${manifestPath}: ${String(error)}`); + } +} + +export function findWorkspaceTestSurfaceFindings(root: string): WorkspaceTestSurfaceFinding[] { + const workspaceFile = resolve(root, 'pnpm-workspace.yaml'); + const workspacePatterns = parseWorkspacePatterns(readFileSync(workspaceFile, 'utf8')); + const workspaceDirectories = workspacePatterns.flatMap((pattern) => + expandWorkspacePattern(root, pattern) + ); + const findings: WorkspaceTestSurfaceFinding[] = []; + + for (const workspaceDirectory of workspaceDirectories) { + const manifestPath = resolve(workspaceDirectory, 'package.json'); + if (!existsSync(manifestPath)) continue; + + const manifest = readManifest(manifestPath); + const hasTests = containsTestFile(workspaceDirectory); + const hasTestScript = Boolean(manifest.scripts?.test?.trim()); + if (!hasTests && !hasTestScript) continue; + + const missingScripts: WorkspaceTestSurfaceFinding['missingScripts'] = []; + if (!hasTestScript) missingScripts.push('test'); + if (!manifest.scripts?.['test:coverage']?.trim()) missingScripts.push('test:coverage'); + if (missingScripts.length === 0) continue; + + findings.push({ + workspace: relative(root, workspaceDirectory), + packageName: manifest.name ?? relative(root, workspaceDirectory), + missingScripts, + }); + } + + return findings.sort((left, right) => left.workspace.localeCompare(right.workspace)); +} + +export function assertWorkspaceTestSurfaces(root: string): void { + const findings = findWorkspaceTestSurfaceFindings(root); + if (findings.length === 0) return; + + const details = findings.map( + (finding) => + `- ${finding.packageName} (${finding.workspace}): missing ${finding.missingScripts.join(', ')}` + ); + throw new Error( + ['Tested workspaces must expose runnable test and test:coverage scripts:', ...details].join( + '\n' + ) + ); +} + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); + +if (resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) { + try { + assertWorkspaceTestSurfaces(repositoryRoot); + console.log( + 'Workspace test surfaces: all tested pnpm workspaces expose test and test:coverage' + ); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} diff --git a/scripts/quality/ci-workspace-surfaces.test.ts b/scripts/quality/ci-workspace-surfaces.test.ts new file mode 100644 index 0000000000..084b97e165 --- /dev/null +++ b/scripts/quality/ci-workspace-surfaces.test.ts @@ -0,0 +1,46 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const CI_WORKFLOW_PATH = new URL('../../.github/workflows/ci.yml', import.meta.url); + +function jobBlock(workflow: string, jobName: string): string { + const pattern = new RegExp(String.raw`\n ${jobName}:\n[\s\S]*?(?=\n [a-zA-Z0-9_-]+:\n|\n*$)`); + const match = workflow.match(pattern); + + expect(match?.[0], `missing ${jobName} job`).toBeDefined(); + return match![0]; +} + +function expectBlockingWorkspaceSurfaces(workflow: string): void { + const job = jobBlock(workflow, 'workspace-quality-surfaces'); + const requiredCommands = [ + 'pnpm quality:workspace-test-surfaces', + 'pnpm --filter @simple-agent-manager/ui build-storybook', + 'pnpm --filter @simple-agent-manager/www build && pnpm --filter @simple-agent-manager/www check:links', + 'pnpm --filter @simple-agent-manager/ui test:storybook', + 'pnpm --filter @simple-agent-manager/www test:browser', + ]; + + for (const command of requiredCommands) { + expect(job, `missing blocking command: ${command}`).toContain(`run: ${command}`); + } + + expect(job).not.toContain('continue-on-error'); +} + +describe('CI workspace quality surface wiring', () => { + it('runs each deterministic workspace surface as a blocking command', () => { + expectBlockingWorkspaceSurfaces(readFileSync(CI_WORKFLOW_PATH, 'utf8')); + }); + + it('fails if a required workspace command is omitted from the job', () => { + const incompleteWorkflow = readFileSync(CI_WORKFLOW_PATH, 'utf8').replace( + ' run: pnpm --filter @simple-agent-manager/ui build-storybook\n', + '' + ); + + expect(() => expectBlockingWorkspaceSurfaces(incompleteWorkflow)).toThrow( + 'missing blocking command: pnpm --filter @simple-agent-manager/ui build-storybook' + ); + }); +}); diff --git a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md index eca1803674..5a961d60ae 100644 --- a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md +++ b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md @@ -25,14 +25,14 @@ This is a quality-gate repair only. It must not change runtime UI, API, CLI, clo ## Implementation checklist -- [ ] Add a fail-closed workspace quality validator that discovers tested pnpm workspaces and requires a real `test:coverage` command. -- [ ] Add fixture tests proving an omitted script/package fails and a complete workspace set passes. -- [ ] Add `test:coverage` and the V8 provider dependency to every currently omitted tested workspace. -- [ ] Install a single compatible Storybook toolchain for Vite 8/React 19, add development/production scripts, and typecheck stories/configuration. -- [ ] Add deterministic Storybook browser screenshots and axe assertions for both current shared component stories at mobile and desktop sizes. -- [ ] Include terminal `tests/**/*.tsx` in ESLint and add a dedicated no-emit TypeScript test project. -- [ ] Expose the public site Playwright suite as a package script and keep link checking build-backed. -- [ ] Add one blocking CI job for the workspace validator, Storybook production build/audit, public docs links, and public browser tests. +- [x] Add a fail-closed workspace quality validator that discovers tested pnpm workspaces and requires a real `test:coverage` command. +- [x] Add fixture tests proving an omitted script/package fails and a complete workspace set passes. +- [x] Add `test:coverage` and the V8 provider dependency to every currently omitted tested workspace. +- [x] Install a single compatible Storybook toolchain for Vite 8/React 19, add development/production scripts, and typecheck stories/configuration. +- [x] Add deterministic Storybook browser screenshots and axe assertions for both current shared component stories at mobile and desktop sizes. +- [x] Include terminal `tests/**/*.tsx` in ESLint and add a dedicated no-emit TypeScript test project. +- [x] Expose the public site Playwright suite as a package script and keep link checking build-backed. +- [x] Add one blocking CI job for the workspace validator, Storybook production build/audit, public docs links, and public browser tests. - [ ] Run affected tests/coverage, Storybook build/audit, public docs link/browser checks, tail-worker tests, terminal lint/typecheck, and the full root quality suite. - [ ] Complete all mandated local specialist reviews and address every material finding. - [ ] Open one non-draft PR against `main`, wait for every applicable GitHub check to turn green, and stop without staging, merge, or task archive. From 746b63bf108caaa11fdfcbbc0486d8679e893db7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 13:53:23 +0000 Subject: [PATCH 3/8] test: harden blocking quality audits --- .github/workflows/ci.yml | 6 +- apps/www/tests/tracker-source.test.ts | 67 +++++++++++++ apps/www/tests/tracker.test.ts | 8 +- packages/ui/.storybook/preview.css | 1 + packages/ui/.storybook/sam-tailwind-theme.css | 57 +++++++++++ .../tests/playwright/storybook-a11y.spec.ts | 96 ++++++++++++++++--- .../check-workspace-test-surfaces.test.ts | 22 ++++- .../quality/check-workspace-test-surfaces.ts | 32 ++++--- ...-08-blocking-workspace-quality-surfaces.md | 9 +- 9 files changed, 264 insertions(+), 34 deletions(-) create mode 100644 apps/www/tests/tracker-source.test.ts create mode 100644 packages/ui/.storybook/sam-tailwind-theme.css diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 369c22d550..1b188cf1a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -267,10 +267,8 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: workspace-browser-evidence - path: | - .codex/tmp/playwright-screenshots/ - apps/www/.codex/tmp/playwright-screenshots/ - if-no-files-found: ignore + path: .codex/tmp/playwright-screenshots/ + if-no-files-found: error retention-days: 7 playwright-visual: diff --git a/apps/www/tests/tracker-source.test.ts b/apps/www/tests/tracker-source.test.ts new file mode 100644 index 0000000000..2f1b13be4c --- /dev/null +++ b/apps/www/tests/tracker-source.test.ts @@ -0,0 +1,67 @@ +/** @vitest-environment jsdom */ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +async function readBeaconEvents(sendBeacon: ReturnType) { + const blob = sendBeacon.mock.calls.at(-1)?.[1] as Blob; + return JSON.parse(await blob.text()).events as Array>; +} + +async function installTracker(url: string, referrer = '') { + window.history.pushState({}, '', url); + Object.defineProperty(document, 'referrer', { value: referrer, configurable: true }); + + const script = document.createElement('script'); + script.setAttribute('data-api', 'https://api.example.com/api/t'); + Object.defineProperty(document, 'currentScript', { value: script, configurable: true }); + + const sendBeacon = vi.fn().mockReturnValue(true); + Object.defineProperty(navigator, 'sendBeacon', { value: sendBeacon, configurable: true }); + vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-4000-8000-000000000001'); + + vi.resetModules(); + await import('../src/scripts/tracker'); + return { sendBeacon }; +} + +describe('public website tracker source coverage', () => { + beforeEach(() => { + localStorage.clear(); + sessionStorage.clear(); + vi.restoreAllMocks(); + }); + + it('covers redaction and navigation behavior before browser-asset compilation', async () => { + const { sendBeacon } = await installTracker( + '/projects/01KZ941C7W5JRFDA9RDZASV8EE/repos/raphaeltm/simple-agent-manager/blob/apps/www/src/scripts/tracker.ts?utm_source=newsletter&utm_medium=email&utm_campaign=launch&token=secret#frag', + 'https://user:pass@example.com/oauth/callback/user@example.com?code=secret#frag' + ); + + expect(sendBeacon).toHaveBeenCalledTimes(1); + let [event] = await readBeaconEvents(sendBeacon); + expect(event).toMatchObject({ + event: 'page_view', + page: '/projects/[redacted]/repos/[redacted]/simple-agent-manager/blob/apps/www/src/scripts/[redacted]', + referrer: 'https://example.com/oauth/[redacted]/[redacted]', + host: 'localhost', + utmSource: 'newsletter', + utmMedium: 'email', + utmCampaign: 'launch', + }); + expect(JSON.stringify(event)).not.toContain('01KZ941C7W5JRFDA9RDZASV8EE'); + expect(JSON.stringify(event)).not.toContain('token=secret'); + expect(JSON.stringify(event)).not.toContain('#frag'); + expect(JSON.stringify(event)).not.toContain('user@example.com'); + + document.dispatchEvent(new Event('astro:page-load')); + expect(sendBeacon).toHaveBeenCalledTimes(1); + + window.history.pushState({}, '', '/invite/user@example.com/accept?code=secret#frag'); + document.dispatchEvent(new Event('astro:page-load')); + + expect(sendBeacon).toHaveBeenCalledTimes(2); + [event] = await readBeaconEvents(sendBeacon); + expect(event.page).toBe('/invite/[redacted]/accept'); + expect(JSON.stringify(event)).not.toContain('user@example.com'); + expect(JSON.stringify(event)).not.toContain('code=secret'); + }); +}); diff --git a/apps/www/tests/tracker.test.ts b/apps/www/tests/tracker.test.ts index 9ee9c84f4a..8cf4a9bfd5 100644 --- a/apps/www/tests/tracker.test.ts +++ b/apps/www/tests/tracker.test.ts @@ -1,4 +1,5 @@ /** @vitest-environment jsdom */ +import trackerScript from '../public/scripts/tracker.js?raw'; import { beforeEach, describe, expect, it, vi } from 'vitest'; async function readBeaconEvents(sendBeacon: ReturnType) { @@ -6,7 +7,7 @@ async function readBeaconEvents(sendBeacon: ReturnType) { return JSON.parse(await blob.text()).events as Array>; } -async function installTracker(url: string, referrer = '') { +function installTracker(url: string, referrer = '') { window.history.pushState({}, '', url); Object.defineProperty(document, 'referrer', { value: referrer, configurable: true }); @@ -18,8 +19,7 @@ async function installTracker(url: string, referrer = '') { Object.defineProperty(navigator, 'sendBeacon', { value: sendBeacon, configurable: true }); vi.spyOn(crypto, 'randomUUID').mockReturnValue('00000000-0000-4000-8000-000000000001'); - vi.resetModules(); - await import('../src/scripts/tracker'); + window.eval(trackerScript); return { sendBeacon }; } @@ -31,7 +31,7 @@ describe('public website tracker', () => { }); it('redacts sensitive URL data, preserves allowed metadata, and avoids duplicate page views', async () => { - const { sendBeacon } = await installTracker( + const { sendBeacon } = installTracker( '/projects/01KZ941C7W5JRFDA9RDZASV8EE/repos/raphaeltm/simple-agent-manager/blob/apps/www/src/scripts/tracker.ts?utm_source=newsletter&utm_medium=email&utm_campaign=launch&token=secret#frag', 'https://user:pass@example.com/oauth/callback/user@example.com?code=secret#frag' ); diff --git a/packages/ui/.storybook/preview.css b/packages/ui/.storybook/preview.css index 6addcceb16..1d2b9b3697 100644 --- a/packages/ui/.storybook/preview.css +++ b/packages/ui/.storybook/preview.css @@ -1,4 +1,5 @@ @import 'tailwindcss'; +@import './sam-tailwind-theme.css'; @import '../src/styles.css'; @source '../src/**/*.{ts,tsx}'; diff --git a/packages/ui/.storybook/sam-tailwind-theme.css b/packages/ui/.storybook/sam-tailwind-theme.css new file mode 100644 index 0000000000..efe862205b --- /dev/null +++ b/packages/ui/.storybook/sam-tailwind-theme.css @@ -0,0 +1,57 @@ +/* Keep Storybook's Tailwind utilities aligned with the SAM token mapping used by consumers. */ +@theme { + --color-canvas: var(--sam-color-bg-canvas); + --color-surface: var(--sam-color-bg-surface); + --color-surface-hover: var(--sam-color-bg-surface-hover); + --color-inset: var(--sam-color-bg-inset); + --color-overlay: var(--sam-color-bg-overlay); + --color-page: var(--sam-color-bg-page); + + --color-glass-chrome: var(--sam-glass-bg-chrome); + --color-glass-surface: var(--sam-glass-bg-surface); + --color-glass-modal: var(--sam-glass-bg-modal); + --color-glass-backdrop-dim: var(--sam-glass-backdrop-dim); + --color-glass-border: var(--sam-glass-border-color); + --color-glass-glow: var(--sam-glass-glow-color); + + --color-fg-primary: var(--sam-color-fg-primary); + --color-fg-muted: var(--sam-color-fg-muted); + --color-fg-on-accent: var(--sam-color-fg-on-accent); + + --color-accent: var(--sam-color-accent-primary); + --color-accent-hover: var(--sam-color-accent-primary-hover); + --color-success: var(--sam-color-success); + --color-warning: var(--sam-color-warning); + --color-danger: var(--sam-color-danger); + --color-info: var(--sam-color-info); + --color-purple: var(--sam-color-purple); + --color-focus-ring: var(--sam-color-focus-ring); + --color-border-default: var(--sam-color-border-default); + + --color-success-fg: var(--sam-color-success-fg); + --color-danger-fg: var(--sam-color-danger-fg); + --color-warning-fg: var(--sam-color-warning-fg); + --color-info-fg: var(--sam-color-info-fg); + --color-warning-surface: var(--sam-color-warning-surface); + + --color-accent-tint: var(--sam-color-accent-primary-tint); + --color-success-tint: var(--sam-color-success-tint); + --color-warning-tint: var(--sam-color-warning-tint); + --color-danger-tint: var(--sam-color-danger-tint); + --color-info-tint: var(--sam-color-info-tint); + + --shadow-sm: var(--sam-shadow-sm); + --shadow: var(--sam-shadow-default); + --shadow-md: var(--sam-shadow-md); + --shadow-lg: var(--sam-shadow-lg); + --shadow-xl: var(--sam-shadow-xl); + --shadow-dropdown: var(--sam-shadow-dropdown); + --shadow-overlay: var(--sam-shadow-overlay); + --shadow-tooltip: var(--sam-shadow-tooltip); + + --radius-sm: var(--sam-radius-sm); + --radius-md: var(--sam-radius-md); + --radius-lg: var(--sam-radius-lg); +} + +@custom-variant dark (&:where([data-ui-theme='sam'], [data-ui-theme='sam'] *)); diff --git a/packages/ui/tests/playwright/storybook-a11y.spec.ts b/packages/ui/tests/playwright/storybook-a11y.spec.ts index c3722718f4..e9612b8c96 100644 --- a/packages/ui/tests/playwright/storybook-a11y.spec.ts +++ b/packages/ui/tests/playwright/storybook-a11y.spec.ts @@ -1,22 +1,55 @@ +import { readFileSync } from 'node:fs'; + import AxeBuilder from '@axe-core/playwright'; import { expect, test } from '@playwright/test'; -const stories = [ - 'components-button--primary', - 'components-button--secondary', - 'components-button--loading', - 'components-button--danger', - 'components-statusbadge--running', - 'components-statusbadge--creating', - 'components-statusbadge--disconnected', - 'components-statusbadge--mobile-label', -] as const; +type StorybookIndex = { + entries: Record; +}; + +const storybookIndex = JSON.parse( + readFileSync(new URL('../../storybook-static/index.json', import.meta.url), 'utf8') +) as StorybookIndex; +const stories = Object.values(storybookIndex.entries) + .filter((entry) => entry.type === 'story') + .map((entry) => entry.id) + .sort(); + +if (stories.length === 0) throw new Error('Storybook index does not contain any story entries'); const themes = ['sam', 'sam-light'] as const; +type Theme = (typeof themes)[number]; + +// These existing dark-theme token pairs are below WCAG AA for normal-sized text. WP-065 +// cannot change runtime tokens, so keep the debt exact and visible: this test fails if an +// exception disappears, changes shape, or any unlisted serious violation is introduced. +const knownSeriousViolations: Partial> = { + 'sam/components-button--danger': ['color-contrast'], + 'sam/components-button--primary': ['color-contrast'], +}; + +const semanticButtonTokens: Partial> = { + 'components-button--primary': { + background: '--sam-color-accent-primary', + foreground: '--sam-color-fg-on-accent', + }, + 'components-button--loading': { + background: '--sam-color-accent-primary', + foreground: '--sam-color-fg-on-accent', + }, + 'components-button--danger': { + background: '--sam-color-danger', + foreground: '--sam-color-fg-on-accent', + }, + 'components-button--secondary': { + background: '--sam-color-bg-surface', + foreground: '--sam-color-fg-primary', + }, +}; for (const theme of themes) { for (const story of stories) { - test(`${story} renders without overflow or serious axe violations in ${theme}`, async ({ + test(`${story} renders without overflow or unexpected serious axe violations in ${theme}`, async ({ page, }, testInfo) => { const query = new URLSearchParams({ @@ -27,6 +60,30 @@ for (const theme of themes) { await page.goto(`/iframe.html?${query.toString()}`); await expect(page.locator('#storybook-root')).not.toBeEmpty(); + const semanticTokens = semanticButtonTokens[story]; + if (semanticTokens) { + const styles = await page.getByRole('button').evaluate((button, tokens) => { + const probe = document.createElement('span'); + probe.style.backgroundColor = `var(${tokens.background})`; + probe.style.color = `var(${tokens.foreground})`; + document.body.append(probe); + + const buttonStyles = getComputedStyle(button); + const probeStyles = getComputedStyle(probe); + const resolved = { + actualBackground: buttonStyles.backgroundColor, + actualForeground: buttonStyles.color, + expectedBackground: probeStyles.backgroundColor, + expectedForeground: probeStyles.color, + }; + probe.remove(); + return resolved; + }, semanticTokens); + + expect(styles.actualBackground).toBe(styles.expectedBackground); + expect(styles.actualForeground).toBe(styles.expectedForeground); + } + const hasHorizontalOverflow = await page.evaluate( () => document.documentElement.scrollWidth > document.documentElement.clientWidth ); @@ -36,7 +93,22 @@ for (const theme of themes) { const seriousViolations = axeResults.violations.filter( (violation) => violation.impact === 'critical' || violation.impact === 'serious' ); - expect(seriousViolations, JSON.stringify(seriousViolations, null, 2)).toEqual([]); + const exceptionKey = `${theme}/${story}` as const; + const expectedViolationIds = knownSeriousViolations[exceptionKey] ?? []; + expect( + seriousViolations.map((violation) => violation.id).sort(), + JSON.stringify(seriousViolations, null, 2) + ).toEqual([...expectedViolationIds].sort()); + + if (expectedViolationIds.length > 0) { + for (const violation of seriousViolations) { + expect(violation.nodes).toHaveLength(1); + expect(violation.nodes[0]?.target).toContain('.inline-flex'); + } + const description = `${exceptionKey}: ${expectedViolationIds.join(', ')}`; + testInfo.annotations.push({ type: 'known-accessibility-exception', description }); + console.warn(`[known-accessibility-exception] ${description}`); + } const project = testInfo.project.name.toLowerCase().replace(/\W+/g, '-'); await page.screenshot({ diff --git a/scripts/quality/check-workspace-test-surfaces.test.ts b/scripts/quality/check-workspace-test-surfaces.test.ts index 8ed1cafeac..db77489388 100644 --- a/scripts/quality/check-workspace-test-surfaces.test.ts +++ b/scripts/quality/check-workspace-test-surfaces.test.ts @@ -60,10 +60,28 @@ describe('workspace test surface guard', () => { ]); expect(() => assertWorkspaceTestSurfaces(root)).toThrow( - '@fixture/omitted (packages/omitted): missing test:coverage' + '@fixture/omitted (packages/omitted): missing or placeholder test:coverage' ); }); + it.each(['true', ':', 'exit 0', 'echo skipped'])( + 'rejects placeholder coverage command %s', + (command) => { + const root = createFixture([ + { + path: 'packages/placeholder', + name: '@fixture/placeholder', + scripts: { test: 'vitest run', 'test:coverage': command }, + testFile: 'tests/placeholder.test.ts', + }, + ]); + + expect(() => assertWorkspaceTestSurfaces(root)).toThrow( + '@fixture/placeholder (packages/placeholder): missing or placeholder test:coverage' + ); + } + ); + it('detects a package with test files even when both scripts were omitted', () => { const root = createFixture([ { @@ -77,7 +95,7 @@ describe('workspace test surface guard', () => { { workspace: 'packages/no-scripts', packageName: '@fixture/no-scripts', - missingScripts: ['test', 'test:coverage'], + invalidScripts: ['test', 'test:coverage'], }, ]); }); diff --git a/scripts/quality/check-workspace-test-surfaces.ts b/scripts/quality/check-workspace-test-surfaces.ts index 5287ae42ce..07c30bb39d 100644 --- a/scripts/quality/check-workspace-test-surfaces.ts +++ b/scripts/quality/check-workspace-test-surfaces.ts @@ -10,7 +10,7 @@ type PackageManifest = { export type WorkspaceTestSurfaceFinding = { workspace: string; packageName: string; - missingScripts: Array<'test' | 'test:coverage'>; + invalidScripts: Array<'test' | 'test:coverage'>; }; const TEST_FILE_PATTERN = /(?:^|\/)(?:[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)|[^/]+_test\.go)$/; @@ -24,6 +24,14 @@ const IGNORED_DIRECTORIES = new Set([ 'storybook-static', ]); +function isRunnableScript(command: string | undefined): boolean { + const normalized = command?.trim(); + if (!normalized) return false; + if (/^(?:true|:|exit\s+0)$/i.test(normalized)) return false; + if (/^(?:echo|printf)\b/i.test(normalized) && !/(?:&&|\|\||;)/.test(normalized)) return false; + return true; +} + function parseWorkspacePatterns(workspaceSource: string): string[] { const patterns: string[] = []; let inPackages = false; @@ -96,18 +104,19 @@ export function findWorkspaceTestSurfaceFindings(root: string): WorkspaceTestSur const manifest = readManifest(manifestPath); const hasTests = containsTestFile(workspaceDirectory); - const hasTestScript = Boolean(manifest.scripts?.test?.trim()); + const hasTestScript = isRunnableScript(manifest.scripts?.test); if (!hasTests && !hasTestScript) continue; - const missingScripts: WorkspaceTestSurfaceFinding['missingScripts'] = []; - if (!hasTestScript) missingScripts.push('test'); - if (!manifest.scripts?.['test:coverage']?.trim()) missingScripts.push('test:coverage'); - if (missingScripts.length === 0) continue; + const invalidScripts: WorkspaceTestSurfaceFinding['invalidScripts'] = []; + if (!hasTestScript) invalidScripts.push('test'); + if (!isRunnableScript(manifest.scripts?.['test:coverage'])) + invalidScripts.push('test:coverage'); + if (invalidScripts.length === 0) continue; findings.push({ workspace: relative(root, workspaceDirectory), packageName: manifest.name ?? relative(root, workspaceDirectory), - missingScripts, + invalidScripts, }); } @@ -120,12 +129,13 @@ export function assertWorkspaceTestSurfaces(root: string): void { const details = findings.map( (finding) => - `- ${finding.packageName} (${finding.workspace}): missing ${finding.missingScripts.join(', ')}` + `- ${finding.packageName} (${finding.workspace}): missing or placeholder ${finding.invalidScripts.join(', ')}` ); throw new Error( - ['Tested workspaces must expose runnable test and test:coverage scripts:', ...details].join( - '\n' - ) + [ + 'Tested workspaces must expose non-placeholder test and test:coverage scripts:', + ...details, + ].join('\n') ); } diff --git a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md index 5a961d60ae..21d5734417 100644 --- a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md +++ b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md @@ -33,7 +33,7 @@ This is a quality-gate repair only. It must not change runtime UI, API, CLI, clo - [x] Include terminal `tests/**/*.tsx` in ESLint and add a dedicated no-emit TypeScript test project. - [x] Expose the public site Playwright suite as a package script and keep link checking build-backed. - [x] Add one blocking CI job for the workspace validator, Storybook production build/audit, public docs links, and public browser tests. -- [ ] Run affected tests/coverage, Storybook build/audit, public docs link/browser checks, tail-worker tests, terminal lint/typecheck, and the full root quality suite. +- [x] Run affected tests/coverage, Storybook build/audit, public docs link/browser checks, tail-worker tests, terminal lint/typecheck, and the full root quality suite. - [ ] Complete all mandated local specialist reviews and address every material finding. - [ ] Open one non-draft PR against `main`, wait for every applicable GitHub check to turn green, and stop without staging, merge, or task archive. @@ -42,6 +42,7 @@ This is a quality-gate repair only. It must not change runtime UI, API, CLI, clo - Every pnpm workspace that has a test script also has a non-placeholder `test:coverage` script; future omissions fail a checked-in behavioral fixture and blocking CI. - Root `pnpm test:coverage` executes coverage commands for `apps/tail-worker`, `apps/www`, `packages/cloud-init`, `packages/ui`, and `infra` instead of Turbo `NONEXISTENT` tasks. - `packages/ui` installs from the frozen lockfile, typechecks its Storybook config/stories, builds static Storybook output, and passes mobile/desktop screenshot plus serious axe checks. +- The Storybook axe gate explicitly expects the two existing dark-theme `color-contrast` findings for primary and danger buttons. They remain visible in test output and are exact assertions rather than skips: the gate fails when either finding changes/disappears or any unlisted serious violation appears. Changing runtime color tokens belongs to a separate UI fix because this packet is non-breaking. - A lint-invalid or type-invalid terminal `.test.tsx` file is within the configured lint/typecheck inputs; the existing terminal suite remains green. - `apps/www` internal docs links and its local production-preview Playwright flow are blocking CI checks. - `apps/tail-worker` tests execute through root coverage and pass directly. @@ -58,6 +59,12 @@ Quality surfaces existed in source but were unreachable from the commands and CI Turbo intentionally tolerates missing workspace tasks. The repository had no invariant checker distinguishing a workspace with no relevant surface from a tested workspace that accidentally omitted the required script. Standalone Storybook, link, browser, and static-test-input configurations were added without a durable CI wiring contract. +### Timeline + +- Before 2026-08-08, the workspace omissions accumulated across separate changes; the exact introducing commits are not recoverable from the accepted audit identifiers, so this record does not infer them. +- The accepted `6050d049…`, `253325b4…`, R9-007, and R10-009 audits identified the Storybook, terminal TSX, public-site/tail-worker, and Turbo coverage roots. +- On 2026-08-08, WP-065 reproduced the silent green baseline, added fail-closed package-graph and CI fixtures, exposed the missing package commands, and made deterministic Storybook/public browser surfaces blocking. + ### Why it was not caught CI asserted only that aggregate commands exited successfully. It did not test the package-graph completeness invariant or mutate a representative fixture to prove an omitted package makes the gate fail. From 59c51ec411b3a11b9d49962dcf7cb6e1d6d9f4a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 14:08:15 +0000 Subject: [PATCH 4/8] test: close browser quality gate bypasses --- .github/workflows/ci.yml | 3 + apps/www/playwright.config.ts | 1 + apps/www/tests/playwright/fixtures.ts | 26 ++++++++ .../playwright/public-surface-a11y.spec.ts | 9 ++- .../self-host-wizard-secrets.spec.ts | 2 +- package.json | 1 + .../quality/check-browser-evidence.test.ts | 54 +++++++++++++++ scripts/quality/check-browser-evidence.ts | 65 +++++++++++++++++++ .../check-workspace-test-surfaces.test.ts | 44 +++++++++++-- .../quality/check-workspace-test-surfaces.ts | 62 +++++++++++++++--- scripts/quality/ci-workspace-surfaces.test.ts | 3 + 11 files changed, 255 insertions(+), 15 deletions(-) create mode 100644 apps/www/tests/playwright/fixtures.ts create mode 100644 scripts/quality/check-browser-evidence.test.ts create mode 100644 scripts/quality/check-browser-evidence.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b188cf1a8..a33da9fd06 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -262,6 +262,9 @@ jobs: - name: Test public site in desktop and mobile browsers run: pnpm --filter @simple-agent-manager/www test:browser + - name: Require complete Storybook and public browser evidence + run: pnpm quality:browser-evidence + - name: Upload workspace browser evidence if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 diff --git a/apps/www/playwright.config.ts b/apps/www/playwright.config.ts index a98f6b2e9d..65fb292006 100644 --- a/apps/www/playwright.config.ts +++ b/apps/www/playwright.config.ts @@ -22,6 +22,7 @@ export default defineConfig({ ? undefined : { command: 'pnpm build && pnpm preview --host 127.0.0.1 --port 4321', + env: { PUBLIC_BASE_DOMAIN: 'localhost' }, url: 'http://127.0.0.1:4321/self-host/', reuseExistingServer: false, timeout: 120_000, diff --git a/apps/www/tests/playwright/fixtures.ts b/apps/www/tests/playwright/fixtures.ts new file mode 100644 index 0000000000..5ffc65fdc1 --- /dev/null +++ b/apps/www/tests/playwright/fixtures.ts @@ -0,0 +1,26 @@ +import { expect, test as base } from '@playwright/test'; + +const PRODUCTION_ANALYTICS_ORIGIN = 'https://api.simple-agent-manager.org'; + +export const test = base.extend<{ productionAnalyticsIsolation: void }>({ + productionAnalyticsIsolation: [ + async ({ page }, use) => { + const productionAnalyticsRequests: string[] = []; + await page.route(`${PRODUCTION_ANALYTICS_ORIGIN}/api/t*`, async (route) => { + productionAnalyticsRequests.push(route.request().url()); + await route.abort('blockedbyclient'); + }); + + await use(); + + expect( + productionAnalyticsRequests, + 'browser quality tests must not write synthetic analytics to production' + ).toEqual([]); + }, + { auto: true }, + ], +}); + +export { expect }; +export type { Page } from '@playwright/test'; diff --git a/apps/www/tests/playwright/public-surface-a11y.spec.ts b/apps/www/tests/playwright/public-surface-a11y.spec.ts index 4060d113d5..e6fafb283c 100644 --- a/apps/www/tests/playwright/public-surface-a11y.spec.ts +++ b/apps/www/tests/playwright/public-surface-a11y.spec.ts @@ -1,9 +1,16 @@ import AxeBuilder from '@axe-core/playwright'; -import { expect, test } from '@playwright/test'; + +import { expect, test } from './fixtures'; test('self-host surface has no overflow or serious axe violations', async ({ page }, testInfo) => { await page.goto('/self-host/'); await expect(page.getByRole('heading', { name: 'Deploy your own SAM instance' })).toBeVisible(); + if (!process.env.PLAYWRIGHT_BASE_URL) { + await expect(page.locator('script[data-api]')).toHaveAttribute( + 'data-api', + 'https://api.localhost/api/t' + ); + } const hasHorizontalOverflow = await page.evaluate( () => document.documentElement.scrollWidth > document.documentElement.clientWidth diff --git a/apps/www/tests/playwright/self-host-wizard-secrets.spec.ts b/apps/www/tests/playwright/self-host-wizard-secrets.spec.ts index 725db18532..9c6b6bdc2c 100644 --- a/apps/www/tests/playwright/self-host-wizard-secrets.spec.ts +++ b/apps/www/tests/playwright/self-host-wizard-secrets.spec.ts @@ -1,4 +1,4 @@ -import { expect, test, type Page } from '@playwright/test'; +import { expect, test, type Page } from './fixtures'; const STORAGE_KEY = 'sam-self-host-wizard-v1'; const LEGACY_WEBHOOK_CANARY = 'legacy-webhook-secret-canary-0123456789abcdef'; diff --git a/package.json b/package.json index 0d2765126e..adde5e145b 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "quality:observability-noise": "tsx scripts/quality/check-observability-noise.ts", "quality:dependency-governance": "npx vitest run --config scripts/quality/vitest.config.ts scripts/quality/dependency-governance.test.ts", "quality:workspace-test-surfaces": "tsx scripts/quality/check-workspace-test-surfaces.ts", + "quality:browser-evidence": "tsx scripts/quality/check-browser-evidence.ts", "test:infra": "pnpm --filter @simple-agent-manager/infra test", "prepare": "husky", "quality:migration-ordering": "tsx scripts/quality/check-migration-ordering.ts", diff --git a/scripts/quality/check-browser-evidence.test.ts b/scripts/quality/check-browser-evidence.test.ts new file mode 100644 index 0000000000..01634b050a --- /dev/null +++ b/scripts/quality/check-browser-evidence.test.ts @@ -0,0 +1,54 @@ +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; +import { afterEach, describe, expect, it } from 'vitest'; + +import { assertBrowserEvidence, expectedBrowserEvidence } from './check-browser-evidence'; + +const temporaryRoots: string[] = []; + +function createFixture(): string { + const root = mkdtempSync(join(tmpdir(), 'browser-evidence-')); + temporaryRoots.push(root); + const indexPath = join(root, 'packages/ui/storybook-static/index.json'); + mkdirSync(dirname(indexPath), { recursive: true }); + writeFileSync( + indexPath, + JSON.stringify({ + entries: { + 'components-example--primary': { + id: 'components-example--primary', + type: 'story', + }, + }, + }) + ); + return root; +} + +afterEach(() => { + for (const root of temporaryRoots.splice(0)) rmSync(root, { recursive: true, force: true }); +}); + +describe('browser evidence manifest', () => { + it('fails when either Storybook or public-site project evidence is absent', () => { + const root = createFixture(); + for (const path of expectedBrowserEvidence(root)) { + if (path.endsWith('www-self-host-mobile-chrome.png')) continue; + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, 'fixture'); + } + + expect(() => assertBrowserEvidence(root)).toThrow('www-self-host-mobile-chrome.png'); + }); + + it('passes only when every story/theme/project and public-site screenshot exists', () => { + const root = createFixture(); + for (const path of expectedBrowserEvidence(root)) { + mkdirSync(dirname(path), { recursive: true }); + writeFileSync(path, 'fixture'); + } + + expect(() => assertBrowserEvidence(root)).not.toThrow(); + }); +}); diff --git a/scripts/quality/check-browser-evidence.ts b/scripts/quality/check-browser-evidence.ts new file mode 100644 index 0000000000..5b9622c77f --- /dev/null +++ b/scripts/quality/check-browser-evidence.ts @@ -0,0 +1,65 @@ +import { existsSync, readFileSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +type StorybookIndex = { + entries: Record; +}; + +const THEMES = ['sam', 'sam-light'] as const; +const PROJECTS = ['desktop-chrome', 'mobile-chrome'] as const; + +export function expectedBrowserEvidence(root: string): string[] { + const indexPath = resolve(root, 'packages/ui/storybook-static/index.json'); + if (!existsSync(indexPath)) { + throw new Error(`Browser evidence check failed: missing Storybook index ${indexPath}`); + } + + const index = JSON.parse(readFileSync(indexPath, 'utf8')) as StorybookIndex; + const stories = Object.values(index.entries) + .filter((entry) => entry.type === 'story') + .map((entry) => entry.id) + .sort(); + if (stories.length === 0) { + throw new Error('Browser evidence check failed: Storybook index has no stories'); + } + + const storybookEvidence = stories.flatMap((story) => + THEMES.flatMap((theme) => + PROJECTS.map((project) => + resolve( + root, + `.codex/tmp/playwright-screenshots/storybook-${story}-${theme}-${project}.png` + ) + ) + ) + ); + const publicSiteEvidence = PROJECTS.map((project) => + resolve(root, `.codex/tmp/playwright-screenshots/www-self-host-${project}.png`) + ); + return [...storybookEvidence, ...publicSiteEvidence]; +} + +export function assertBrowserEvidence(root: string): void { + const missing = expectedBrowserEvidence(root).filter((path) => !existsSync(path)); + if (missing.length === 0) return; + + throw new Error( + [ + 'Browser evidence check failed: required screenshots are missing:', + ...missing.map((p) => `- ${p}`), + ].join('\n') + ); +} + +const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); + +if (resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) { + try { + assertBrowserEvidence(repositoryRoot); + console.log('Browser evidence: Storybook and public-site screenshots exist for every project'); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} diff --git a/scripts/quality/check-workspace-test-surfaces.test.ts b/scripts/quality/check-workspace-test-surfaces.test.ts index db77489388..885f40ef11 100644 --- a/scripts/quality/check-workspace-test-surfaces.test.ts +++ b/scripts/quality/check-workspace-test-surfaces.test.ts @@ -6,9 +6,11 @@ import { afterEach, describe, expect, it } from 'vitest'; import { assertWorkspaceTestSurfaces, findWorkspaceTestSurfaceFindings, + type RequiredWorkspaceScripts, } from './check-workspace-test-surfaces'; const temporaryRoots: string[] = []; +const NO_SPECIALIZED_REQUIREMENTS: RequiredWorkspaceScripts[] = []; function createFixture( packages: Array<{ @@ -59,7 +61,7 @@ describe('workspace test surface guard', () => { }, ]); - expect(() => assertWorkspaceTestSurfaces(root)).toThrow( + expect(() => assertWorkspaceTestSurfaces(root, NO_SPECIALIZED_REQUIREMENTS)).toThrow( '@fixture/omitted (packages/omitted): missing or placeholder test:coverage' ); }); @@ -76,7 +78,7 @@ describe('workspace test surface guard', () => { }, ]); - expect(() => assertWorkspaceTestSurfaces(root)).toThrow( + expect(() => assertWorkspaceTestSurfaces(root, NO_SPECIALIZED_REQUIREMENTS)).toThrow( '@fixture/placeholder (packages/placeholder): missing or placeholder test:coverage' ); } @@ -91,7 +93,7 @@ describe('workspace test surface guard', () => { }, ]); - expect(findWorkspaceTestSurfaceFindings(root)).toEqual([ + expect(findWorkspaceTestSurfaceFindings(root, NO_SPECIALIZED_REQUIREMENTS)).toEqual([ { workspace: 'packages/no-scripts', packageName: '@fixture/no-scripts', @@ -111,7 +113,41 @@ describe('workspace test surface guard', () => { { path: 'packages/no-tests', name: '@fixture/no-tests' }, ]); - expect(() => assertWorkspaceTestSurfaces(root)).not.toThrow(); + expect(() => assertWorkspaceTestSurfaces(root, NO_SPECIALIZED_REQUIREMENTS)).not.toThrow(); + }); + + it('fails when a specialized browser package omits a required script', () => { + const root = createFixture([ + { + path: 'packages/specialized', + name: '@fixture/specialized', + scripts: { 'test:browser': 'playwright test' }, + }, + ]); + + expect(() => + assertWorkspaceTestSurfaces(root, [ + { + workspace: 'packages/specialized', + packageName: '@fixture/specialized', + scripts: ['build-browser', 'test:browser'], + }, + ]) + ).toThrow('@fixture/specialized (packages/specialized): missing or placeholder build-browser'); + }); + + it('fails when a required specialized package is absent', () => { + const root = createFixture([]); + + expect(() => + assertWorkspaceTestSurfaces(root, [ + { + workspace: 'packages/absent', + packageName: '@fixture/absent', + scripts: ['test:browser'], + }, + ]) + ).toThrow('@fixture/absent (packages/absent): missing or placeholder test:browser'); }); it('passes against the checked-in workspace graph', () => { diff --git a/scripts/quality/check-workspace-test-surfaces.ts b/scripts/quality/check-workspace-test-surfaces.ts index 07c30bb39d..2b562e7cd4 100644 --- a/scripts/quality/check-workspace-test-surfaces.ts +++ b/scripts/quality/check-workspace-test-surfaces.ts @@ -10,9 +10,28 @@ type PackageManifest = { export type WorkspaceTestSurfaceFinding = { workspace: string; packageName: string; - invalidScripts: Array<'test' | 'test:coverage'>; + invalidScripts: string[]; }; +export type RequiredWorkspaceScripts = { + workspace: string; + packageName: string; + scripts: string[]; +}; + +export const REQUIRED_WORKSPACE_SCRIPTS: RequiredWorkspaceScripts[] = [ + { + workspace: 'packages/ui', + packageName: '@simple-agent-manager/ui', + scripts: ['build-storybook', 'test:storybook'], + }, + { + workspace: 'apps/www', + packageName: '@simple-agent-manager/www', + scripts: ['check:links', 'test:browser'], + }, +]; + const TEST_FILE_PATTERN = /(?:^|\/)(?:[^/]+\.(?:test|spec)\.(?:[cm]?[jt]sx?)|[^/]+_test\.go)$/; const IGNORED_DIRECTORIES = new Set([ '.git', @@ -90,7 +109,10 @@ function readManifest(manifestPath: string): PackageManifest { } } -export function findWorkspaceTestSurfaceFindings(root: string): WorkspaceTestSurfaceFinding[] { +export function findWorkspaceTestSurfaceFindings( + root: string, + requiredWorkspaceScripts: RequiredWorkspaceScripts[] = REQUIRED_WORKSPACE_SCRIPTS +): WorkspaceTestSurfaceFinding[] { const workspaceFile = resolve(root, 'pnpm-workspace.yaml'); const workspacePatterns = parseWorkspacePatterns(readFileSync(workspaceFile, 'utf8')); const workspaceDirectories = workspacePatterns.flatMap((pattern) => @@ -120,11 +142,36 @@ export function findWorkspaceTestSurfaceFindings(root: string): WorkspaceTestSur }); } + for (const requirement of requiredWorkspaceScripts) { + const manifestPath = resolve(root, requirement.workspace, 'package.json'); + const manifest = existsSync(manifestPath) ? readManifest(manifestPath) : undefined; + const invalidScripts = requirement.scripts.filter( + (script) => !isRunnableScript(manifest?.scripts?.[script]) + ); + if (invalidScripts.length === 0) continue; + + const existing = findings.find((finding) => finding.workspace === requirement.workspace); + if (existing) { + existing.invalidScripts.push( + ...invalidScripts.filter((script) => !existing.invalidScripts.includes(script)) + ); + } else { + findings.push({ + workspace: requirement.workspace, + packageName: manifest?.name ?? requirement.packageName, + invalidScripts, + }); + } + } + return findings.sort((left, right) => left.workspace.localeCompare(right.workspace)); } -export function assertWorkspaceTestSurfaces(root: string): void { - const findings = findWorkspaceTestSurfaceFindings(root); +export function assertWorkspaceTestSurfaces( + root: string, + requiredWorkspaceScripts: RequiredWorkspaceScripts[] = REQUIRED_WORKSPACE_SCRIPTS +): void { + const findings = findWorkspaceTestSurfaceFindings(root, requiredWorkspaceScripts); if (findings.length === 0) return; const details = findings.map( @@ -132,10 +179,7 @@ export function assertWorkspaceTestSurfaces(root: string): void { `- ${finding.packageName} (${finding.workspace}): missing or placeholder ${finding.invalidScripts.join(', ')}` ); throw new Error( - [ - 'Tested workspaces must expose non-placeholder test and test:coverage scripts:', - ...details, - ].join('\n') + ['Workspaces must expose every required non-placeholder quality script:', ...details].join('\n') ); } @@ -145,7 +189,7 @@ if (resolve(process.argv[1] ?? '') === fileURLToPath(import.meta.url)) { try { assertWorkspaceTestSurfaces(repositoryRoot); console.log( - 'Workspace test surfaces: all tested pnpm workspaces expose test and test:coverage' + 'Workspace quality surfaces: all tested workspaces and specialized browser packages expose required scripts' ); } catch (error) { console.error(error instanceof Error ? error.message : String(error)); diff --git a/scripts/quality/ci-workspace-surfaces.test.ts b/scripts/quality/ci-workspace-surfaces.test.ts index 084b97e165..fc12332c92 100644 --- a/scripts/quality/ci-workspace-surfaces.test.ts +++ b/scripts/quality/ci-workspace-surfaces.test.ts @@ -19,6 +19,7 @@ function expectBlockingWorkspaceSurfaces(workflow: string): void { 'pnpm --filter @simple-agent-manager/www build && pnpm --filter @simple-agent-manager/www check:links', 'pnpm --filter @simple-agent-manager/ui test:storybook', 'pnpm --filter @simple-agent-manager/www test:browser', + 'pnpm quality:browser-evidence', ]; for (const command of requiredCommands) { @@ -26,6 +27,8 @@ function expectBlockingWorkspaceSurfaces(workflow: string): void { } expect(job).not.toContain('continue-on-error'); + expect(job).toContain('path: .codex/tmp/playwright-screenshots/'); + expect(job).toContain('if-no-files-found: error'); } describe('CI workspace quality surface wiring', () => { From a99fa21586a18387199376a37501ef2302b44a69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 14:11:29 +0000 Subject: [PATCH 5/8] test: bind specialized gates to workspace identity --- .../check-workspace-test-surfaces.test.ts | 51 +++++++++++++++++-- .../quality/check-workspace-test-surfaces.ts | 16 ++++-- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/scripts/quality/check-workspace-test-surfaces.test.ts b/scripts/quality/check-workspace-test-surfaces.test.ts index 885f40ef11..f02e32fce4 100644 --- a/scripts/quality/check-workspace-test-surfaces.test.ts +++ b/scripts/quality/check-workspace-test-surfaces.test.ts @@ -62,7 +62,7 @@ describe('workspace test surface guard', () => { ]); expect(() => assertWorkspaceTestSurfaces(root, NO_SPECIALIZED_REQUIREMENTS)).toThrow( - '@fixture/omitted (packages/omitted): missing or placeholder test:coverage' + '@fixture/omitted (packages/omitted): missing or invalid test:coverage' ); }); @@ -79,7 +79,7 @@ describe('workspace test surface guard', () => { ]); expect(() => assertWorkspaceTestSurfaces(root, NO_SPECIALIZED_REQUIREMENTS)).toThrow( - '@fixture/placeholder (packages/placeholder): missing or placeholder test:coverage' + '@fixture/placeholder (packages/placeholder): missing or invalid test:coverage' ); } ); @@ -133,7 +133,7 @@ describe('workspace test surface guard', () => { scripts: ['build-browser', 'test:browser'], }, ]) - ).toThrow('@fixture/specialized (packages/specialized): missing or placeholder build-browser'); + ).toThrow('@fixture/specialized (packages/specialized): missing or invalid build-browser'); }); it('fails when a required specialized package is absent', () => { @@ -147,7 +147,50 @@ describe('workspace test surface guard', () => { scripts: ['test:browser'], }, ]) - ).toThrow('@fixture/absent (packages/absent): missing or placeholder test:browser'); + ).toThrow('missing or invalid pnpm workspace membership'); + }); + + it('fails when a specialized package path leaves the pnpm workspace graph', () => { + const root = createFixture([]); + const workspaceRoot = join(root, 'apps/www'); + mkdirSync(workspaceRoot, { recursive: true }); + writeFileSync( + join(workspaceRoot, 'package.json'), + JSON.stringify({ + name: '@fixture/www', + scripts: { 'test:browser': 'playwright test' }, + }) + ); + + expect(() => + assertWorkspaceTestSurfaces(root, [ + { + workspace: 'apps/www', + packageName: '@fixture/www', + scripts: ['test:browser'], + }, + ]) + ).toThrow('@fixture/www (apps/www): missing or invalid pnpm workspace membership'); + }); + + it('fails when a specialized package manifest has the wrong package name', () => { + const root = createFixture([ + { + path: 'packages/specialized', + name: '@fixture/renamed', + scripts: { 'test:browser': 'playwright test' }, + }, + ]); + + expect(() => + assertWorkspaceTestSurfaces(root, [ + { + workspace: 'packages/specialized', + packageName: '@fixture/expected', + scripts: ['test:browser'], + }, + ]) + ).toThrow('missing or invalid package name @fixture/expected'); }); it('passes against the checked-in workspace graph', () => { diff --git a/scripts/quality/check-workspace-test-surfaces.ts b/scripts/quality/check-workspace-test-surfaces.ts index 2b562e7cd4..8c11b28c2b 100644 --- a/scripts/quality/check-workspace-test-surfaces.ts +++ b/scripts/quality/check-workspace-test-surfaces.ts @@ -143,10 +143,18 @@ export function findWorkspaceTestSurfaceFindings( } for (const requirement of requiredWorkspaceScripts) { - const manifestPath = resolve(root, requirement.workspace, 'package.json'); + const requiredWorkspaceDirectory = resolve(root, requirement.workspace); + const manifestPath = resolve(requiredWorkspaceDirectory, 'package.json'); const manifest = existsSync(manifestPath) ? readManifest(manifestPath) : undefined; - const invalidScripts = requirement.scripts.filter( - (script) => !isRunnableScript(manifest?.scripts?.[script]) + const invalidScripts: string[] = []; + if (!workspaceDirectories.includes(requiredWorkspaceDirectory)) { + invalidScripts.push('pnpm workspace membership'); + } + if (manifest?.name !== requirement.packageName) { + invalidScripts.push(`package name ${requirement.packageName}`); + } + invalidScripts.push( + ...requirement.scripts.filter((script) => !isRunnableScript(manifest?.scripts?.[script])) ); if (invalidScripts.length === 0) continue; @@ -176,7 +184,7 @@ export function assertWorkspaceTestSurfaces( const details = findings.map( (finding) => - `- ${finding.packageName} (${finding.workspace}): missing or placeholder ${finding.invalidScripts.join(', ')}` + `- ${finding.packageName} (${finding.workspace}): missing or invalid ${finding.invalidScripts.join(', ')}` ); throw new Error( ['Workspaces must expose every required non-placeholder quality script:', ...details].join('\n') From 2980f1bd80d51c9b1c8ec418f774e07626b9d27d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 14:12:54 +0000 Subject: [PATCH 6/8] docs: record WP-065 validation evidence --- ...-08-blocking-workspace-quality-surfaces.md | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md index 21d5734417..ca13e543ae 100644 --- a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md +++ b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md @@ -34,9 +34,36 @@ This is a quality-gate repair only. It must not change runtime UI, API, CLI, clo - [x] Expose the public site Playwright suite as a package script and keep link checking build-backed. - [x] Add one blocking CI job for the workspace validator, Storybook production build/audit, public docs links, and public browser tests. - [x] Run affected tests/coverage, Storybook build/audit, public docs link/browser checks, tail-worker tests, terminal lint/typecheck, and the full root quality suite. -- [ ] Complete all mandated local specialist reviews and address every material finding. +- [x] Complete all mandated local specialist reviews and address every material finding. - [ ] Open one non-draft PR against `main`, wait for every applicable GitHub check to turn green, and stop without staging, merge, or task archive. +## Changed contracts + +- `quality:workspace-test-surfaces` discovers tested workspaces and rejects missing/placeholder `test` or `test:coverage` scripts. It also binds the specialized Storybook/www gates to pnpm workspace membership, exact package identity, and runnable script names so exact-name filters cannot silently select nothing. +- `apps/tail-worker`, `apps/www`, `packages/cloud-init`, `packages/ui`, and `infra` now expose real coverage commands, making all 20 root Turbo coverage tasks executable. `tools/og-image` remains the explicit narrow exception because it has no tests. +- `packages/terminal` lint includes `.test.tsx`, and its typecheck includes the dedicated no-emit test project. +- `packages/ui` owns installable Storybook build/dev/browser scripts, production Vite/Tailwind configuration, dynamic built-story discovery, semantic token assertions, desktop/mobile screenshots, and axe checks in both themes. +- `apps/www` owns separate unit/coverage/browser commands. Browser tests build with a loopback analytics domain and automatically block/fail on any canonical production analytics request. +- CI runs the repository guard, Storybook production build/audit, public docs build/link check, and public browser suite as a blocking job. A post-run evidence manifest requires every built Storybook story/theme/project screenshot and both public-site project screenshots before artifact upload. + +## Validation evidence + +- Baseline mutation proof: the unmodified UI Storybook command exited zero despite no script, and the Turbo dry run returned five tested workspaces as `NONEXISTENT`; the checked-in omission fixtures fail for missing package membership, package identity, required scripts, placeholder scripts, and incomplete browser evidence. +- Affected coverage: tail-worker 30 tests, cloud-init 190, infra 56, UI 87, terminal 99, and www 2 emitted/source tracker tests; www source coverage is 85.13% statements and 91.04% lines. +- Browser/public surfaces: Storybook production build passed; 32/32 Storybook desktop/mobile/theme audits passed; www reported 0 broken links across 26 docs pages and 14/14 browser tests passed; the complete browser-evidence manifest passed. +- Static/full gates: terminal lint/typecheck passed; `pnpm lint`, `pnpm typecheck`, `pnpm test:coverage` (20/20 Turbo tasks), and `pnpm build` passed; `pnpm quality:scripts:test` passed 20 files / 224 tests. +- Release constraints: latest `origin/main` remains `8eed3b740`; shared staging was not mutated, no merge occurred, the task remains active, and the user-owned `.codex/config.toml` was never staged or edited. + +## Specialist review outcomes + +- `test-engineer`: PASS after placeholder rejection, built-story discovery, and emitted/source tracker coverage were separated. +- `ui-ux-specialist`: PASS after faithful semantic Tailwind mapping, computed token assertions, complete screenshots, and fail-on-missing artifact handling; independently rebuilt and passed 32/32 audits. +- `doc-sync-validator`: PASS after adding this post-mortem timeline; no public docs or `CLAUDE.md` change is required for internal developer/CI commands. +- `constitution-validator`: PASS; no runtime/deployment hardcoded values or constitution violations. +- `security-auditor`: PASS after isolating browser analytics to loopback and adding a fail-closed production-request blocker. +- Independent defensive regression reviewer: PASS after specialized gates were bound to exact workspace/package/script identity and the complete evidence manifest was added. +- Every material finding was addressed; no finding was deferred. + ## Acceptance criteria - Every pnpm workspace that has a test script also has a non-placeholder `test:coverage` script; future omissions fail a checked-in behavioral fixture and blocking CI. From e6a5ef65976057994f4bbe60d8aae6bd67c02ad6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 14:17:35 +0000 Subject: [PATCH 7/8] docs: record completion review --- tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md | 1 + 1 file changed, 1 insertion(+) diff --git a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md index ca13e543ae..b0b8fe2806 100644 --- a/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md +++ b/tasks/active/2026-08-08-blocking-workspace-quality-surfaces.md @@ -62,6 +62,7 @@ This is a quality-gate repair only. It must not change runtime UI, API, CLI, clo - `constitution-validator`: PASS; no runtime/deployment hardcoded values or constitution violations. - `security-auditor`: PASS after isolating browser analytics to loopback and adding a fail-closed production-request blocker. - Independent defensive regression reviewer: PASS after specialized gates were bound to exact workspace/package/script identity and the complete evidence manifest was added. +- `task-completion-validator`: PASS; every research finding, checklist item, and implementation acceptance criterion maps to substantive diff/test evidence, with the PR/check wait intentionally remaining as the sole release item. - Every material finding was addressed; no finding was deferred. ## Acceptance criteria From 0e19f7bae34d6074bc9c9415e52b7c0f97608334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Titsworth-Morin?= Date: Sat, 8 Aug 2026 14:28:17 +0000 Subject: [PATCH 8/8] fix: satisfy quality gate static analysis --- .github/workflows/ci.yml | 4 +- scripts/quality/check-browser-evidence.ts | 2 +- .../quality/check-workspace-test-surfaces.ts | 130 +++++++++++------- 3 files changed, 87 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a33da9fd06..8946f12f7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -241,7 +241,7 @@ jobs: cache: 'pnpm' - name: Install dependencies - run: pnpm install --frozen-lockfile + run: pnpm install --frozen-lockfile --ignore-scripts - name: Require test coverage scripts for every tested workspace run: pnpm quality:workspace-test-surfaces @@ -254,7 +254,7 @@ jobs: - name: Install Chromium working-directory: packages/ui - run: pnpm exec playwright install --with-deps chromium + run: ./node_modules/.bin/playwright install --with-deps chromium - name: Audit Storybook in desktop and mobile browsers run: pnpm --filter @simple-agent-manager/ui test:storybook diff --git a/scripts/quality/check-browser-evidence.ts b/scripts/quality/check-browser-evidence.ts index 5b9622c77f..5bd96bb38e 100644 --- a/scripts/quality/check-browser-evidence.ts +++ b/scripts/quality/check-browser-evidence.ts @@ -19,7 +19,7 @@ export function expectedBrowserEvidence(root: string): string[] { const stories = Object.values(index.entries) .filter((entry) => entry.type === 'story') .map((entry) => entry.id) - .sort(); + .sort((left, right) => left.localeCompare(right)); if (stories.length === 0) { throw new Error('Browser evidence check failed: Storybook index has no stories'); } diff --git a/scripts/quality/check-workspace-test-surfaces.ts b/scripts/quality/check-workspace-test-surfaces.ts index 8c11b28c2b..2abf8c29ee 100644 --- a/scripts/quality/check-workspace-test-surfaces.ts +++ b/scripts/quality/check-workspace-test-surfaces.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import { existsSync, readdirSync, readFileSync } from 'node:fs'; import { dirname, relative, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -63,8 +63,8 @@ function parseWorkspacePatterns(workspaceSource: string): string[] { if (inPackages && /^\S/.test(line) && line.trim() !== '') break; if (!inPackages) continue; - const match = line.match(/^\s+-\s+['"]?([^'"]+)['"]?\s*$/); - if (match?.[1]) patterns.push(match[1]); + const entry = parseWorkspaceEntry(line); + if (entry) patterns.push(entry); } if (patterns.length === 0) { @@ -73,6 +73,18 @@ function parseWorkspacePatterns(workspaceSource: string): string[] { return patterns; } +function parseWorkspaceEntry(line: string): string | undefined { + const trimmed = line.trim(); + if (!trimmed.startsWith('-')) return undefined; + + const value = trimmed.slice(1).trim(); + const quote = value.at(0); + if ((quote === "'" || quote === '"') && value.endsWith(quote)) { + return value.slice(1, -1); + } + return value || undefined; +} + function expandWorkspacePattern(root: string, pattern: string): string[] { if (pattern.startsWith('!')) { throw new Error(`unsupported negated workspace pattern: ${pattern}`); @@ -109,15 +121,10 @@ function readManifest(manifestPath: string): PackageManifest { } } -export function findWorkspaceTestSurfaceFindings( +function findTestedWorkspaceFindings( root: string, - requiredWorkspaceScripts: RequiredWorkspaceScripts[] = REQUIRED_WORKSPACE_SCRIPTS + workspaceDirectories: string[] ): WorkspaceTestSurfaceFinding[] { - const workspaceFile = resolve(root, 'pnpm-workspace.yaml'); - const workspacePatterns = parseWorkspacePatterns(readFileSync(workspaceFile, 'utf8')); - const workspaceDirectories = workspacePatterns.flatMap((pattern) => - expandWorkspacePattern(root, pattern) - ); const findings: WorkspaceTestSurfaceFinding[] = []; for (const workspaceDirectory of workspaceDirectories) { @@ -129,49 +136,80 @@ export function findWorkspaceTestSurfaceFindings( const hasTestScript = isRunnableScript(manifest.scripts?.test); if (!hasTests && !hasTestScript) continue; - const invalidScripts: WorkspaceTestSurfaceFinding['invalidScripts'] = []; - if (!hasTestScript) invalidScripts.push('test'); - if (!isRunnableScript(manifest.scripts?.['test:coverage'])) - invalidScripts.push('test:coverage'); - if (invalidScripts.length === 0) continue; - - findings.push({ - workspace: relative(root, workspaceDirectory), - packageName: manifest.name ?? relative(root, workspaceDirectory), - invalidScripts, - }); - } - - for (const requirement of requiredWorkspaceScripts) { - const requiredWorkspaceDirectory = resolve(root, requirement.workspace); - const manifestPath = resolve(requiredWorkspaceDirectory, 'package.json'); - const manifest = existsSync(manifestPath) ? readManifest(manifestPath) : undefined; - const invalidScripts: string[] = []; - if (!workspaceDirectories.includes(requiredWorkspaceDirectory)) { - invalidScripts.push('pnpm workspace membership'); - } - if (manifest?.name !== requirement.packageName) { - invalidScripts.push(`package name ${requirement.packageName}`); - } - invalidScripts.push( - ...requirement.scripts.filter((script) => !isRunnableScript(manifest?.scripts?.[script])) + const invalidScripts = ['test', 'test:coverage'].filter((script) => + script === 'test' ? !hasTestScript : !isRunnableScript(manifest.scripts?.[script]) ); - if (invalidScripts.length === 0) continue; - - const existing = findings.find((finding) => finding.workspace === requirement.workspace); - if (existing) { - existing.invalidScripts.push( - ...invalidScripts.filter((script) => !existing.invalidScripts.includes(script)) - ); - } else { + if (invalidScripts.length > 0) { findings.push({ - workspace: requirement.workspace, - packageName: manifest?.name ?? requirement.packageName, + workspace: relative(root, workspaceDirectory), + packageName: manifest.name ?? relative(root, workspaceDirectory), invalidScripts, }); } } + return findings; +} + +function findRequiredWorkspaceFinding( + root: string, + workspaceDirectories: string[], + requirement: RequiredWorkspaceScripts +): WorkspaceTestSurfaceFinding | undefined { + const workspaceDirectory = resolve(root, requirement.workspace); + const manifestPath = resolve(workspaceDirectory, 'package.json'); + const manifest = existsSync(manifestPath) ? readManifest(manifestPath) : undefined; + const invalidScripts = requirement.scripts.filter( + (script) => !isRunnableScript(manifest?.scripts?.[script]) + ); + + if (!workspaceDirectories.includes(workspaceDirectory)) { + invalidScripts.unshift('pnpm workspace membership'); + } + if (manifest?.name !== requirement.packageName) { + invalidScripts.push(`package name ${requirement.packageName}`); + } + if (invalidScripts.length === 0) return undefined; + + return { + workspace: requirement.workspace, + packageName: manifest?.name ?? requirement.packageName, + invalidScripts, + }; +} + +function mergeFinding( + findings: WorkspaceTestSurfaceFinding[], + candidate: WorkspaceTestSurfaceFinding +): void { + const existing = findings.find((finding) => finding.workspace === candidate.workspace); + if (!existing) { + findings.push(candidate); + return; + } + + const additions = candidate.invalidScripts.filter( + (script) => !existing.invalidScripts.includes(script) + ); + existing.invalidScripts.push(...additions); +} + +export function findWorkspaceTestSurfaceFindings( + root: string, + requiredWorkspaceScripts: RequiredWorkspaceScripts[] = REQUIRED_WORKSPACE_SCRIPTS +): WorkspaceTestSurfaceFinding[] { + const workspaceFile = resolve(root, 'pnpm-workspace.yaml'); + const workspacePatterns = parseWorkspacePatterns(readFileSync(workspaceFile, 'utf8')); + const workspaceDirectories = workspacePatterns.flatMap((pattern) => + expandWorkspacePattern(root, pattern) + ); + const findings = findTestedWorkspaceFindings(root, workspaceDirectories); + + for (const requirement of requiredWorkspaceScripts) { + const finding = findRequiredWorkspaceFinding(root, workspaceDirectories, requirement); + if (finding) mergeFinding(findings, finding); + } + return findings.sort((left, right) => left.workspace.localeCompare(right.workspace)); }