From df168ba3205e48174ce9ab5df2168bff3a2f4d9d Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 23 Sep 2026 09:36:50 -0700 Subject: [PATCH 1/3] Add Argos visual tests alongside Chromatic Every Storybook story runs in Chromium and WebKit through the Storybook Vitest addon and uploads to Argos as one build per browser; the built Storybook is deployed to Argos too. Same triggers, draft gate, and fork gate as Chromatic, which stays in place. The preview's snapshot freezes now also apply under Argos, and ARGOS_TOKEN joins the audited repo-level secret inventory. Co-Authored-By: Claude Opus 5.5 (1M context) --- .config/tend.yaml | 2 + .github/workflows/argos.yml | 84 +++++ .gitignore | 3 +- AGENTS.md | 2 +- TESTING_AND_MODIFICATION_GUIDE.md | 4 +- docs/specs/security-ci.md | 9 +- docs/specs/security-ci.rationale.md | 2 +- docs/specs/security.md | 4 +- lib/.storybook/main.ts | 2 + lib/.storybook/preview.ts | 11 +- lib/.storybook/vitest.setup.ts | 4 + lib/package.json | 7 + lib/tsconfig.node.json | 2 +- lib/vitest.argos.config.ts | 58 ++++ pnpm-lock.yaml | 485 +++++++++++++++++++++++++++- scripts/spec-word-budgets.json | 4 +- 16 files changed, 656 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/argos.yml create mode 100644 lib/.storybook/vitest.setup.ts create mode 100644 lib/vitest.argos.config.ts diff --git a/.config/tend.yaml b/.config/tend.yaml index 9d09e0872..07e749574 100644 --- a/.config/tend.yaml +++ b/.config/tend.yaml @@ -3,9 +3,11 @@ bot_name: dormouse-bot secrets: allowed: - CHROMATIC_PROJECT_TOKEN + - ARGOS_TOKEN workflows: ci-fix: watched_workflows: - CI - Chromatic + - Argos diff --git a/.github/workflows/argos.yml b/.github/workflows/argos.yml new file mode 100644 index 000000000..a36136c6f --- /dev/null +++ b/.github/workflows/argos.yml @@ -0,0 +1,84 @@ +name: Argos + +# Runs beside Chromatic while Argos is evaluated as its replacement: same +# stories, same triggers, same draft and fork gates. Argos renders in this job +# (Playwright, via the Storybook Vitest addon) rather than in its own cloud, so +# a play-function exception fails the run here instead of passing silently. +on: + push: + branches: + - main + # The same inputs as `chromatic.yml`, for the same reasons given there: + # the lockfile and the three source-aliased packages are push-only. + paths: + - 'lib/**' + - 'docs/stories/**' + - 'pnpm-lock.yaml' + - 'dor/**' + - 'remote-lib-common/**' + - 'dor-lib-common/**' + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + paths: + - 'lib/**' + - 'docs/stories/**' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + # Keyed by SHA on push so main never cancels: those builds are the baselines. + group: argos-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + argos: + name: Argos Visual Tests + # Drafts do not spend screenshots; fork PRs get no ARGOS_TOKEN. See the + # matching gate in `chromatic.yml`. + if: >- + github.event_name != 'pull_request' + || (github.event.pull_request.draft == false + && github.event.pull_request.head.repo.full_name == github.repository) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: package.json + + - name: Setup pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + + - name: Install dependencies + # Frozen, so the lockfile pins what runs in a job holding ARGOS_TOKEN. + run: pnpm install --frozen-lockfile + working-directory: lib + + - name: Install browsers + run: pnpm exec playwright install --with-deps chromium webkit + working-directory: lib + + - name: Capture and upload screenshots + # One Argos build per browser: `storybook-chromium`, `storybook-webkit`. + run: pnpm test:argos + working-directory: lib + env: + ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} + + - name: Build Storybook + run: pnpm build-storybook + working-directory: lib + + - name: Deploy Storybook + # main's deploy is the production one, the standing link to the Storybook. + run: pnpm exec argos deploy ./storybook-static ${{ github.ref == 'refs/heads/main' && '--prod' || '' }} + working-directory: lib + env: + ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} diff --git a/.gitignore b/.gitignore index 0e5d50116..458e4d630 100644 --- a/.gitignore +++ b/.gitignore @@ -67,12 +67,13 @@ hosted/.wrangler/ hosted/.pgstencil/ hosted/dist-worker/ -# Storybook / Chromatic +# Storybook / Chromatic / Argos storybook-static/ build-storybook.log chromatic.log chromatic-build-*.xml chromatic-diagnostics.json +lib/screenshots/ # symlinked skill files .agents/skills/ diff --git a/AGENTS.md b/AGENTS.md index 96916bc8f..185c98750 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ pnpm build # build lib, vscode extension, Pocket, and website The harness opens its browser pane and prints its URL and browser command (`docs/specs/standalone.md` → "Standalone browser-dev harness"). -**Open every PR as a draft.** Chromatic bills per snapshot and skips drafts, so +**Open every PR as a draft.** Chromatic and Argos bill per snapshot and skip drafts, so marking a PR ready for review is what spends them. ## Worktrees diff --git a/TESTING_AND_MODIFICATION_GUIDE.md b/TESTING_AND_MODIFICATION_GUIDE.md index 9563650b0..48226bf60 100644 --- a/TESTING_AND_MODIFICATION_GUIDE.md +++ b/TESTING_AND_MODIFICATION_GUIDE.md @@ -53,8 +53,8 @@ needed. If a PR is squash-merged by mistake, PR N+1 will show PR N's changes again until `main` is merged into its branch (that merge resolves cleanly, since both sides carry identical content). The repo's default is a merge commit; keep it. -Keep every PR a draft until it is actually up for review (Chromatic bills on -ready-for-review). +Keep every PR a draft until it is actually up for review (Chromatic and Argos +bill on ready-for-review). ## 2. Setup in a worktree diff --git a/docs/specs/security-ci.md b/docs/specs/security-ci.md index 6744139c9..81d7c956d 100644 --- a/docs/specs/security-ci.md +++ b/docs/specs/security-ci.md @@ -20,9 +20,10 @@ This repository runs the [tend](https://github.com/max-sixty/tend) agent harness | Secret | What a compromise buys | What bounds it | | --- | --- | --- | -| `TEND_BOT_TOKEN` (worst case) | full `repo` + `workflow` write *as a trusted collaborator*: issue/PR spam, force-pushing or deleting feature branches, persistent compromise by authoring new workflows — also how `CHROMATIC_PROJECT_TOKEN` is reached | cannot itself merge to `main`, push tags, or reach env-scoped secrets; new workflows are caught by `.github/workflows/workflow-audit.yaml`; the trusted identity can still social-engineer an admin toward a `main` merge | +| `TEND_BOT_TOKEN` (worst case) | full `repo` + `workflow` write *as a trusted collaborator*: issue/PR spam, force-pushing or deleting feature branches, persistent compromise by authoring new workflows — also how `CHROMATIC_PROJECT_TOKEN` and `ARGOS_TOKEN` are reached | cannot itself merge to `main`, push tags, or reach env-scoped secrets; new workflows are caught by `.github/workflows/workflow-audit.yaml`; the trusted identity can still social-engineer an admin toward a `main` merge | | `CLAUDE_CODE_OAUTH_TOKEN` | Anthropic API-credit abuse | the bot account's spend limit | | `CHROMATIC_PROJECT_TOKEN` | corrupted snapshot testing | rotation; abuse is visible in Chromatic's own dashboard | +| `ARGOS_TOKEN` | corrupted snapshot testing, a replaced Storybook deploy | rotation; abuse is visible in Argos's own dashboard | **Prompt-injection through user-supplied content.** The harness reads PR descriptions, code diffs, issue text, comments, and CI logs — all attacker-influenceable. **Assume a malicious prompt can push a workflow that sends a repo-level secret to an external URL**: admin-gated release paths stay sealed, but a workflow on a bot-pushed feature branch still executes with repo-level secrets in scope. @@ -34,7 +35,7 @@ This repository runs the [tend](https://github.com/max-sixty/tend) agent harness **The notifications poll widens its input.** `tend-notifications.yaml` alone takes its subjects from the bot account's own unread feed rather than from an event payload or a fixed scheduled sweep, and its inlined pre-check `PUT /repos/diffplug/dormouse/subscription -F subscribed=true` runs every `*/15` cycle to keep that feed wide. `dormouse-bot` watches all repository activity; its prompt decides whether to respond (rationale). What still bounds the bot on an undispatched thread is `author_association` tiering and the admin gate on `main`. **Never expect unwatching by hand to stick**: the PUT is idempotent and repeated every cycle, so the lever is `tend-notifications.yaml`, not the Unwatch button. -**Reachable repo-level secrets.** `CHROMATIC_PROJECT_TOKEN` is reachable by any workflow the bot can author: `.github/workflows/chromatic.yml` is `pull_request`-triggered, and GitHub environment policies cannot distinguish a bot from a human contributor at the ref level. **Accepted, with rotation as the mitigation** — the token is scoped to a single project and easy to rotate. `OVSX_PAT` and `VSCE_PAT` are protected: they live only in the `vscode-extension-publish` environment, whose deployment-branch-policy admits only `v*` tags, and tag creation is admin-only. +**Reachable repo-level secrets.** `CHROMATIC_PROJECT_TOKEN` and `ARGOS_TOKEN` are reachable by any workflow the bot can author: `.github/workflows/chromatic.yml` and `.github/workflows/argos.yml` are `pull_request`-triggered, and GitHub environment policies cannot distinguish a bot from a human contributor at the ref level. **Accepted, with rotation as the mitigation** — each token is scoped to a single project and easy to rotate. `OVSX_PAT` and `VSCE_PAT` are protected: they live only in the `vscode-extension-publish` environment, whose deployment-branch-policy admits only `v*` tags, and tag creation is admin-only. **Inert secret plumbing.** Every generated `tend-*.yaml` passes `anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}` to `max-sixty/tend/claude`; no such secret exists at repo or org level, so it resolves to the empty string and the harness authenticates with `CLAUDE_CODE_OAUTH_TOKEN` instead. **Never create an `ANTHROPIC_API_KEY` secret**: the input cannot be deleted locally, so the `FAIL IF` below is what makes adding one a deliberate, documented expansion of the bot's reach (rationale). @@ -64,7 +65,7 @@ This repository runs the [tend](https://github.com/max-sixty/tend) agent harness - **FAIL IF** `dormouse-bot` holds `maintain` or `admin` on this repository. `GET /collaborators/dormouse-bot/permission` spells `push` as `write` in both `permission` and `role_name`, so check that neither of those two roles appears rather than string-comparing against `push`. - **FAIL IF** any GitHub environment except `hosted-preview` admits a ref that is not admin-gated by the `Tag operations` or `Merge access` rulesets. Hosted environments follow "Hosted Deployments" below. Today: `vscode-extension-publish` and `release-attest` (`v*` tag, admin-only via `Tag operations`); `security-audit` (`main` admin-only via `Merge access`, plus `v*` tag); `tend` (`main` only, admin-only via `Merge access`). - **FAIL IF** the secret inventory departs from this placement (rationale). One pass over `actions/secrets`, `actions/organization-secrets`, and each environment's secret listing answers every line: - - `CHROMATIC_PROJECT_TOKEN` — repo level, the only secret there; accepted with rotation (see "Reachable repo-level secrets"). + - `CHROMATIC_PROJECT_TOKEN`, `ARGOS_TOKEN` — repo level, the only secrets there; accepted with rotation (see "Reachable repo-level secrets"). - `AUDIT_PAT` — in `security-audit`, absent at repo level. - `TEND_BOT_TOKEN` — in `tend`, absent at repo level. - `CLAUDE_CODE_OAUTH_TOKEN` — in **both** `tend` and `security-audit`, absent at repo level. Environments do not inherit each other's secrets, so a rotation must set both. @@ -72,7 +73,7 @@ This repository runs the [tend](https://github.com/max-sixty/tend) agent harness - `ANTHROPIC_API_KEY` — absent at repo *and* org level, for as long as `tend-*.yaml` passes `anthropic_api_key` to `max-sixty/tend/claude` (see "Inert secret plumbing"). - `release-attest`'s own secret listing is empty **and** it declares no environment variables — an empty environment is what keeps `id-token: write` the only credential `release.yml`'s two build jobs can reach. - No org-level secret visible to this repository at all (see "Org-level secrets"). -- **FAIL IF** `CHROMATIC_PROJECT_TOKEN` is missing from `secrets.allowed` in `.config/tend.yaml` (rationale). +- **FAIL IF** `CHROMATIC_PROJECT_TOKEN` or `ARGOS_TOKEN` is missing from `secrets.allowed` in `.config/tend.yaml` (rationale). - **FAIL IF** `.github/workflows/workflow-audit.yaml` is missing, disabled, or has not produced a successful run in the last 48 hours. Treat one skipped run as a signal rather than as slack in the window (rationale). - **FAIL IF** Renovate's `github-actions` manager can update `.github/workflows/tend-*.yaml`; the tend generator owns every dependency pin (rationale). - **FAIL IF** any `tend-*.yaml` pins `max-sixty/tend` below `0.1.19`, the release that pins instruction files by glob at any depth (rationale). diff --git a/docs/specs/security-ci.rationale.md b/docs/specs/security-ci.rationale.md index e014bd84b..89d7376df 100644 --- a/docs/specs/security-ci.rationale.md +++ b/docs/specs/security-ci.rationale.md @@ -44,7 +44,7 @@ **Why the secret inventory is placement-checked.** Env-scoping is what stops a workflow pushed to an excluded branch from reading a secret, so a repo-level copy of an environment secret reopens exactly what the environment gate closes. The `release-attest` environment exists only to bound the ref a provenance OIDC token can be minted from. -**Why `CHROMATIC_PROJECT_TOKEN` is listed in `secrets.allowed`.** The entry is an explicit acknowledgment that the bot can read that token. +**Why `CHROMATIC_PROJECT_TOKEN` and `ARGOS_TOKEN` are listed in `secrets.allowed`.** Each entry is an explicit acknowledgment that the bot can read that token. **Why 48 hours is thinner than it reads.** `workflow-audit` runs at 07:13 UTC and the security audit at 04:21, so the steady state is ~21.5h and a single skipped run lands at ~45.5h — inside tolerance by under three hours, which is why one skipped run is a signal rather than noise. diff --git a/docs/specs/security.md b/docs/specs/security.md index 9095ddfcf..39f8a34c2 100644 --- a/docs/specs/security.md +++ b/docs/specs/security.md @@ -93,8 +93,8 @@ run this knows what they are taking on. - **The bot's upstream is pinned by tag, not commit**, so a hostile upstream could change what the bot runs without a diff here. Accepted: the trust equals what the harness already holds ([Automated Maintainer](./security-ci.md#automated-maintainer-tend)). -- **The Chromatic token is reachable by any workflow the bot can author.** - Accepted with rotation; abuse is visible in Chromatic's dashboard +- **The Chromatic and Argos tokens are reachable by any workflow the bot can author.** + Accepted with rotation; abuse is visible in each service's dashboard ([Automated Maintainer](./security-ci.md#automated-maintainer-tend)). ## Known gaps diff --git a/lib/.storybook/main.ts b/lib/.storybook/main.ts index 0a9c9ad16..09afe9b75 100644 --- a/lib/.storybook/main.ts +++ b/lib/.storybook/main.ts @@ -14,6 +14,8 @@ const config: StorybookConfig = { // a story since Storybook 7). stories: ['../src/**/*.stories.@(ts|tsx)', '../../docs/stories/**/*.mdx'], addons: [ + // Runs the stories as Vitest tests for Argos (`lib/vitest.argos.config.ts`). + '@storybook/addon-vitest', { name: '@storybook/addon-docs', // MDX is CommonMark only out of the box, so a GFM table renders as its own diff --git a/lib/.storybook/preview.ts b/lib/.storybook/preview.ts index b156eabec..2911d6d81 100644 --- a/lib/.storybook/preview.ts +++ b/lib/.storybook/preview.ts @@ -81,7 +81,10 @@ const FRAME_FALLBACK_MS = 100; // Initialize fake platform once at module scope const fakePlatform = initPlatform('fake'); -// Pin animations at T=0 for deterministic Chromatic snapshots. +/** Set only by `lib/vitest.argos.config.ts`; absent in the real Storybook. */ +declare const __ARGOS_SNAPSHOT__: boolean | undefined; + +// Pin animations at T=0 for deterministic Chromatic and Argos snapshots. // // Ask `isChromatic()`, never the user agent: Chromatic only rewrites the UA on // its Chrome runner, and identifies every other browser (Safari, Firefox, Edge) @@ -89,7 +92,7 @@ const fakePlatform = initPlatform('fake'); // every guard below OFF in Safari — an alarm pulsing on a 650ms infinite loop, a // blinking cursor, terminals on WebGL, and mid-tween pane geometry — which is // what made the Safari snapshots unstable while Chrome's stayed clean. -if (isChromatic()) { +if (isChromatic() || (typeof __ARGOS_SNAPSHOT__ !== 'undefined' && __ARGOS_SNAPSHOT__)) { cfg.marchingAnts.paused = true; cfg.alert.ringingPaused = true; // A blinking cursor is captured on-or-off depending on the frame; freeze it to a @@ -219,6 +222,10 @@ function resolveStorybookTheme(requestedThemeName: string | undefined) { const preview: Preview = { parameters: { layout: 'fullscreen', + // Argos otherwise shrinks the body to fit-content at 2x zoom, collapsing every + // fullscreen layout into a sliver; capture the page at the viewport width, + // as Chromatic does. + argos: { fitToContent: false }, }, globalTypes: { theme: { diff --git a/lib/.storybook/vitest.setup.ts b/lib/.storybook/vitest.setup.ts new file mode 100644 index 000000000..e7cff6f09 --- /dev/null +++ b/lib/.storybook/vitest.setup.ts @@ -0,0 +1,4 @@ +import { setProjectAnnotations } from '@storybook/react-vite'; +import * as previewAnnotations from './preview'; + +setProjectAnnotations([previewAnnotations]); diff --git a/lib/package.json b/lib/package.json index 59f1c2b88..eca6c1ab0 100644 --- a/lib/package.json +++ b/lib/package.json @@ -16,6 +16,7 @@ "test:watch": "vitest", "storybook": "storybook dev -p 6006 --no-open --ci", "build-storybook": "storybook build", + "test:argos": "vitest run --config vitest.argos.config.ts", "typecheck": "tsc -b" }, "dependencies": { @@ -42,7 +43,10 @@ "yaml": "^2.9.0" }, "devDependencies": { + "@argos-ci/cli": "^6.9.4", + "@argos-ci/storybook": "^6.4.0", "@storybook/addon-docs": "^10.4.0", + "@storybook/addon-vitest": "^10.6.0", "@storybook/react": "^10.4.0", "@storybook/react-vite": "^10.4.0", "@tailwindcss/vite": "^4.3.0", @@ -50,8 +54,11 @@ "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.2", + "@vitest/browser": "^4.1.11", + "@vitest/browser-playwright": "^4.1.11", "chromatic": "^17.0.0", "fake-indexeddb": "^6.2.5", + "playwright": "^1.63.0", "remark-gfm": "^4.0.1", "storybook": "^10.4.0", "tailwindcss": "^4.3.0", diff --git a/lib/tsconfig.node.json b/lib/tsconfig.node.json index 5ddb1ec04..1a6690976 100644 --- a/lib/tsconfig.node.json +++ b/lib/tsconfig.node.json @@ -14,5 +14,5 @@ "noUnusedParameters": true, "noFallthroughCasesInSwitch": true }, - "include": ["vite.config.ts", "vite.pocket.config.ts", "vite.sw.config.ts"] + "include": ["vite.config.ts", "vite.pocket.config.ts", "vite.sw.config.ts", "vitest.argos.config.ts"] } diff --git a/lib/vitest.argos.config.ts b/lib/vitest.argos.config.ts new file mode 100644 index 000000000..ec4fac2fb --- /dev/null +++ b/lib/vitest.argos.config.ts @@ -0,0 +1,58 @@ +// Argos visual tests: every Storybook story runs in a real browser through the +// Storybook Vitest addon, and `@argos-ci/storybook` screenshots it after its +// `play` settles. Separate from `vite.config.ts` so `pnpm test` never starts a +// browser. `pnpm test:argos` writes to `./screenshots/`; it uploads only on CI. +import path from 'path'; +import { defineConfig, mergeConfig, type TestProjectInlineConfiguration } from 'vitest/config'; +import { playwright } from '@vitest/browser-playwright'; +import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'; +import { argosVitestPlugin } from '@argos-ci/storybook/vitest-plugin'; +import viteConfig from './vite.config'; + +const configDir = path.join(import.meta.dirname, '.storybook'); + +// Argos names a screenshot by story id alone, so each browser writes its own +// directory and uploads it as its own build; a shared root would let one +// browser's files overwrite the other's. Chromium and WebKit mirror Chromatic's +// Chrome and Safari. +function storybookProject(browser: 'chromium' | 'webkit'): TestProjectInlineConfiguration { + return { + extends: true, + plugins: [ + storybookTest({ configDir }), + argosVitestPlugin({ + uploadToArgos: !!process.env.CI, + buildName: `storybook-${browser}`, + root: `./screenshots/${browser}`, + }), + ], + test: { + name: `storybook-${browser}`, + browser: { + enabled: true, + headless: true, + provider: playwright( + browser === 'chromium' + ? // Glyphs render identically on macOS and the Linux runner. + { launchOptions: { args: ['--disable-lcd-text', '--font-render-hinting=none'] } } + : {}, + ), + instances: [{ browser }], + // Chromatic's default capture width; Vitest's default is a 414px phone. + viewport: { width: 1200, height: 900 }, + }, + setupFiles: ['.storybook/vitest.setup.ts'], + }, + }; +} + +export default mergeConfig( + viteConfig, + defineConfig({ + // Read by `lib/.storybook/preview.ts` to apply the same snapshot freezes as + // `isChromatic()`. A compile-time flag because the preview's freezes run at + // import, before any setup-file code could set a global. + define: { __ARGOS_SNAPSHOT__: 'true' }, + test: { projects: [storybookProject('chromium'), storybookProject('webkit')] }, + }), +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb0e67624..556c94d27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,7 +98,7 @@ importers: version: 2.1.1(hono@4.13.8) '@pgstencil/auth': specifier: file:../vendor/pgstencil-auth-0.1.0.tgz - version: file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))) + version: file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) hono: specifier: ^4.13.8 version: 4.13.8 @@ -144,7 +144,7 @@ importers: version: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^4.1.6 - version: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + version: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) wrangler: specifier: 4.135.0 version: 4.135.0(@types/node@24.13.6) @@ -215,9 +215,18 @@ importers: specifier: ^2.9.0 version: 2.9.1 devDependencies: + '@argos-ci/cli': + specifier: ^6.9.4 + version: 6.9.4(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/storybook': + specifier: ^6.4.0 + version: 6.4.0(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(playwright@1.63.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(supports-color@10.2.2)(vitest@4.1.11) '@storybook/addon-docs': specifier: ^10.4.0 version: 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0)) + '@storybook/addon-vitest': + specifier: ^10.6.0 + version: 10.6.0(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(@vitest/runner@4.1.11)(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(vitest@4.1.11) '@storybook/react': specifier: ^10.4.0 version: 10.6.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(supports-color@10.2.2)(typescript@6.0.3) @@ -239,12 +248,21 @@ importers: '@vitejs/plugin-react': specifier: ^6.0.2 version: 6.1.1(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@vitest/browser': + specifier: ^4.1.11 + version: 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + '@vitest/browser-playwright': + specifier: ^4.1.11 + version: 4.1.11(playwright@1.63.0)(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) chromatic: specifier: ^17.0.0 version: 17.8.0 fake-indexeddb: specifier: ^6.2.5 version: 6.2.5 + playwright: + specifier: ^1.63.0 + version: 1.63.0 remark-gfm: specifier: ^4.0.1 version: 4.0.1(supports-color@10.2.2) @@ -262,7 +280,7 @@ importers: version: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^4.1.6 - version: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + version: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) relay: dependencies: @@ -385,7 +403,7 @@ importers: version: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^4.1.6 - version: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + version: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) standalone/sidecar: dependencies: @@ -465,7 +483,7 @@ importers: version: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^4.1.6 - version: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + version: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) website: dependencies: @@ -517,13 +535,53 @@ importers: version: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) vitest: specifier: ^4.1.6 - version: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + version: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) packages: '@adobe/css-tools@4.5.0': resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@argos-ci/api-client@0.31.0': + resolution: {integrity: sha512-7EdoiHRc4BvuDxp6/8CbkehCMu5bFYEJPjssYFvOR5FqCq50hT9+GxDuQ4pULpbXUZOVl1vhZ4b8ed4fe7rVCQ==} + engines: {node: '>=22.0.0'} + + '@argos-ci/browser@6.4.5': + resolution: {integrity: sha512-8qWtsOXG752VTAI6iPeHZyMKLSqN9jqdAqZZMk0Q4+j5WSPX76K5a3feV8nBlSqpr1O4RP2/GWYVoxkGdzJr3w==} + engines: {node: '>=22.0.0'} + + '@argos-ci/cli@6.9.4': + resolution: {integrity: sha512-/DJMQUB7Z+Zmg7YTcYYqSR5hQTZex66WJbunFH3MQ4bFWlmgtk64YgBE/TYkxBgtCEPRNsbEV6PbVpRAvFM3ag==} + engines: {node: '>=22.0.0'} + hasBin: true + + '@argos-ci/core@6.8.5': + resolution: {integrity: sha512-6JeGMOY2uSj3eZxYl1P+EdLuM84VJ/qBaDAKDRrSup039CGzHDEU/eEZwwRDiGhChNQ8UPDOWRTyd+0qvtzHgQ==} + engines: {node: '>=22.0.0'} + + '@argos-ci/playwright@7.6.0': + resolution: {integrity: sha512-us/eBIHM9uSBR+Wf3Z8LIq3BFb3MTxsJBXy6F1C+WWOW4Y4VzuED3RZoDfpc0WUYibO7lg6xBJJuqkY8uoXabA==} + engines: {node: '>=22.0.0'} + + '@argos-ci/storybook@6.4.0': + resolution: {integrity: sha512-Y2KifwiNZ5oJFKT9OadSerLYQGTT60/ZhRM+Pi8ZnQMG3HNIHu4/PDGB8B5wOETGzUMKKftgw8EPf7YXhqi3iA==} + engines: {node: '>=22.0.0'} + peerDependencies: + storybook: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0-0 + + '@argos-ci/util@4.2.0': + resolution: {integrity: sha512-HZCNwbJqoaL3pwhnEXH+HJYUPeOMv7rWyCrQaT6IcKZCrdYGOFkG72uXbDvoa84y/FvVSWPV2ja2Ryv0I8ViUg==} + engines: {node: '>=22.0.0'} + + '@argos-ci/vitest@0.7.0': + resolution: {integrity: sha512-GzT05iF4KZZ0rRwcZcOPjwZ31Bi1Bmdfi1hsqMNsvN1QTjJ9lmEpJrNGSJD3BOjDU+pc9Oi82/PN0gCAYuJCoQ==} + engines: {node: '>=22.0.0'} + peerDependencies: + '@vitest/browser': '>=4' + '@vitest/browser-playwright': '>=4' + playwright: '>=1' + vitest: '>=4' + '@asamuzakjp/css-color@5.1.11': resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} @@ -745,6 +803,9 @@ packages: '@better-fetch/fetch@1.3.1': resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} + '@blazediff/core@1.9.1': + resolution: {integrity: sha512-ehg3jIkYKulZh+8om/O25vkvSsXXwC+skXmyA87FFx6A/45eqOkZsBltMw/TVteb0mloiGT8oGRTcjRAz66zaA==} + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true @@ -2405,6 +2466,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@polka/url@1.0.0-next.29': + resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} + '@poppinss/colors@4.1.6': resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} @@ -2795,6 +2859,24 @@ packages: '@types/react': optional: true + '@storybook/addon-vitest@10.6.0': + resolution: {integrity: sha512-GY2SwSXLNgyMqjQ59J7YTkEQTHdEbBMggOvbbuhuPUBhWo97OGSDCmn1UW3nXDZvkoWyWhF9T9628m1uei7FbA==} + peerDependencies: + '@vitest/browser': ^3.0.0 || ^4.0.0 + '@vitest/browser-playwright': ^4.0.0 + '@vitest/runner': ^3.0.0 || ^4.0.0 + storybook: ^10.6.0 + vitest: ^3.0.0 || ^4.0.0 + peerDependenciesMeta: + '@vitest/browser': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/runner': + optional: true + vitest: + optional: true + '@storybook/builder-vite@10.6.0': resolution: {integrity: sha512-7zt8tYB6dnSO8fbjUz91MHexGDpf9HrLlA3FAmlVb+2ZL3eWUs499L3I6BDUymcWu7oW5WCBJlB8ERj8ZLLzig==} peerDependencies: @@ -3162,6 +3244,10 @@ packages: resolution: {integrity: sha512-jIXhD0eWQ1JA6ln/5Dltyx22UxWNrw0hZmhy2rlv6m6KgF7kplHx3g0fzi09lNmTJQRR91OlemYp3xFnvDK9og==} engines: {node: '>=20.0.0'} + '@vercel/detect-agent@1.2.5': + resolution: {integrity: sha512-0krENrjuitlW8s6TJu0MlqCevyCU7K7JK63jZAf7xZ6n17tx+vUEwzHT3sTxawtwZxaW21hu+oFUpoOrm49FsQ==} + engines: {node: '>=14'} + '@vitejs/plugin-react@6.1.1': resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -3178,6 +3264,17 @@ packages: oxc-transform-react: optional: true + '@vitest/browser-playwright@4.1.11': + resolution: {integrity: sha512-riLBxPqwnJ0lWs2DN2WeUfYeKLoAjbP2Xx8cLQdSddzMi20sksIa6K2mPz79DyMZKKVKH2ksOC2yJvtNcZg8cg==} + peerDependencies: + playwright: '*' + vitest: 4.1.11 + + '@vitest/browser@4.1.11': + resolution: {integrity: sha512-bwMovvAeuTFOK5kIFevw4VEf+1gVEICv4SYK4k3knJOxl6b1zEWud8mYKD73e1B0odAn174h1MofURy2TPWf3w==} + peerDependencies: + vitest: 4.1.11 + '@vitest/expect@3.2.4': resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} @@ -3654,6 +3751,10 @@ packages: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + chalk@6.0.0: + resolution: {integrity: sha512-2uNTXIuTTxk7ciZgAU1BQcgnchcG0xXnrs6jzkQfj9SsRa9M2s5zE8WT96hS6KmG4MzWHSrvH43DF1m4XRkrFg==} + engines: {node: '>=22'} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} @@ -3700,6 +3801,14 @@ packages: ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-spinners@3.4.0: + resolution: {integrity: sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==} + engines: {node: '>=18.20'} + cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -3739,6 +3848,10 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} + commander@15.0.0: + resolution: {integrity: sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg==} + engines: {node: '>=22.12.0'} + commander@6.2.1: resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} engines: {node: '>= 6'} @@ -3757,6 +3870,10 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + convict@6.2.5: + resolution: {integrity: sha512-JtXpxqDqJ8P0UwEHwhxLzCIXQy97vlYBZR222Sbzb1q1Erex9ASrztJ29SyhWFQjod1AeFBaPzEEC8YvtZMIYg==} + engines: {node: '>=6'} + cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} @@ -4310,15 +4427,27 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} + engines: {node: '>=20'} + is-inside-container@1.0.0: resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} engines: {node: '>=14.16'} hasBin: true + is-interactive@2.0.0: + resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} + engines: {node: '>=12'} + is-it-type@5.1.3: resolution: {integrity: sha512-AX2uU0HW+TxagTgQXOJY7+2fbFHemC7YFBwN1XqD8qQMKdtfbOC8OC3fUb4s5NU59a3662Dzwto8tWDdZYRXxg==} engines: {node: '>=12'} + is-network-error@1.3.2: + resolution: {integrity: sha512-PhBY86zaxNZUuWP6h13Vu5oFe0XY6/UlKzQnYFELzGVHygP3MxmvTfYSG7GN3aIab/iWudSMgjSnG9Dq+nHrgA==} + engines: {node: '>=16'} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -4334,6 +4463,10 @@ packages: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + is-wsl@3.1.1: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} @@ -4590,6 +4723,9 @@ packages: lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -4617,6 +4753,10 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@7.0.1: + resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==} + engines: {node: '>=18'} + long@5.3.2: resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==} @@ -4804,10 +4944,18 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + mime@1.6.0: resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} engines: {node: '>=4'} @@ -4863,6 +5011,10 @@ packages: engines: {node: '>=10'} hasBin: true + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -5091,13 +5243,31 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} + open@11.0.4: + resolution: {integrity: sha512-++Zlftm0kVLPmzC06t6epuWmcRMDbI4z5P3NNX979WA/k23+NtSOynEGzsVfZwguKw2mi5umVgnBlJQMwRz4Pg==} + engines: {node: '>=20'} + + openapi-fetch@0.17.0: + resolution: {integrity: sha512-PsbZR1wAPcG91eEthKhN+Zn92FMHxv+/faECIwjXdxfTODGSGegYv0sc1Olz+HYPvKOuoXfp+0pA2XVt2cI0Ig==} + + openapi-typescript-helpers@0.1.0: + resolution: {integrity: sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw==} + openid-client@6.8.8: resolution: {integrity: sha512-ZsucJA5Ad04Uv7YN4ql+s4GXNmb9uAYQwyJTsJx7CH/MX3JZioLE2pVKi1SC+YrbSLA4Px3Gi30dMjgOZtb6pA==} + ora@9.4.1: + resolution: {integrity: sha512-6VlU9MLXbjVQD04AZCMX28hVtA5bUoadvUqO76MUCVA0ilwJbMiHsITRPfyVm6p/BC0Av/BXMujx39WCe1LEqw==} + engines: {node: '>=20'} + ovsx@1.2.0: resolution: {integrity: sha512-12mauBqsLehlJ0cRiroQz4uKYVjblVS5OobgSJ278CdCtkDfRQD+SI3NQ00GNalDqzp5PDN11yx84miFUojeMw==} engines: {node: '>=22.0.0'} @@ -5118,6 +5288,10 @@ packages: resolution: {integrity: sha512-VaWRu2i4FJNRtiRWCuuQRgfQ1B7a6+gMSrO+3j0EQi/k0ULfS9kosRxGoiqwzIjZTDI02tGfk5mXXltLg6QtfQ==} engines: {node: '>=18'} + p-retry@8.0.1: + resolution: {integrity: sha512-NAigyu8hfvJe7MsS18mnc4is0MZtau3QMdBklaJKK23oBQb6occO3UPM3vHmTckW8KhfyjMZaUOqdTFEYliHgg==} + engines: {node: '>=22'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -5232,6 +5406,11 @@ packages: engines: {node: '>=20'} hasBin: true + playwright@1.63.0: + resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==} + engines: {node: '>=20'} + hasBin: true + pluralize@2.0.0: resolution: {integrity: sha512-TqNZzQCD4S42De9IfnnBvILN7HAW7riLqsCyp8lgjXeysyPlX5HhqKAcJHHHb9XskE4/a+7VGC9zzx8Ls0jOAw==} @@ -5243,6 +5422,10 @@ packages: resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} engines: {node: '>=12.13.0'} + pngjs@7.0.0: + resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} + engines: {node: '>=14.19.0'} + postcss@8.5.28: resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} engines: {node: ^10 || ^12 || >=14} @@ -5263,6 +5446,14 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + + powershell-utils@0.2.1: + resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==} + engines: {node: '>=20'} + prebuild-install@7.1.3: resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} engines: {node: '>=10'} @@ -5412,6 +5603,10 @@ packages: engines: {node: '>= 0.4'} hasBin: true + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -5557,6 +5752,10 @@ packages: simplex-noise@4.0.3: resolution: {integrity: sha512-qSE2I4AngLQG7BXqoZj51jokT4WUXe8mOBrvfOXpci8+6Yu44+/dD5zqDpOx3Ux792eamTd2lLcI8jqFntk/lg==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} + engines: {node: '>=18'} + slash@5.1.0: resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} engines: {node: '>=14.16'} @@ -5605,6 +5804,10 @@ packages: std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stdin-discarder@0.3.2: + resolution: {integrity: sha512-eCPu1qRxPVkl5605OTWF8Wz40b4Mf45NY5LQmVPQ599knfs5QhASUm9GbJ5BDMDOXgrnh0wyEdvzmL//YMlw0A==} + engines: {node: '>=18'} + storybook@10.6.0: resolution: {integrity: sha512-H48X6AURAFTpfN2zEux6vNesee5Oq8Gxj1IanYmblPZ218sfKSeyyerF1NC7GD7BrruoC8L4/cU0zII4y+d6zw==} hasBin: true @@ -5639,6 +5842,10 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} + engines: {node: '>=20'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -5806,6 +6013,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tough-cookie@6.0.2: resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} @@ -6174,6 +6385,10 @@ packages: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} + wsl-utils@1.0.0: + resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} + engines: {node: '>=20'} + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -6208,6 +6423,10 @@ packages: engines: {node: '>= 14.6'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -6239,6 +6458,10 @@ packages: resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} engines: {node: '>=18'} + yoctocolors@2.2.0: + resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} + engines: {node: '>=18'} + youch-core@0.3.3: resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} @@ -6259,6 +6482,85 @@ snapshots: '@adobe/css-tools@4.5.0': {} + '@argos-ci/api-client@0.31.0(supports-color@10.2.2)': + dependencies: + debug: 4.4.3(supports-color@10.2.2) + openapi-fetch: 0.17.0 + p-retry: 8.0.1 + transitivePeerDependencies: + - supports-color + + '@argos-ci/browser@6.4.5': {} + + '@argos-ci/cli@6.9.4(@types/node@24.13.6)(supports-color@10.2.2)': + dependencies: + '@argos-ci/api-client': 0.31.0(supports-color@10.2.2) + '@argos-ci/core': 6.8.5(@types/node@24.13.6)(supports-color@10.2.2) + '@vercel/detect-agent': 1.2.5 + commander: 15.0.0 + open: 11.0.4 + ora: 9.4.1 + transitivePeerDependencies: + - '@types/node' + - supports-color + + '@argos-ci/core@6.8.5(@types/node@24.13.6)(supports-color@10.2.2)': + dependencies: + '@argos-ci/api-client': 0.31.0(supports-color@10.2.2) + '@argos-ci/util': 4.2.0 + convict: 6.2.5 + debug: 4.4.3(supports-color@10.2.2) + fast-glob: 3.3.3 + mime-types: 3.0.2 + p-retry: 8.0.1 + sharp: 0.35.4(@types/node@24.13.6) + tmp: 0.2.7 + transitivePeerDependencies: + - '@types/node' + - supports-color + + '@argos-ci/playwright@7.6.0(@types/node@24.13.6)(supports-color@10.2.2)': + dependencies: + '@argos-ci/browser': 6.4.5 + '@argos-ci/core': 6.8.5(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/util': 4.2.0 + chalk: 6.0.0 + debug: 4.4.3(supports-color@10.2.2) + transitivePeerDependencies: + - '@types/node' + - supports-color + + '@argos-ci/storybook@6.4.0(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(playwright@1.63.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(supports-color@10.2.2)(vitest@4.1.11)': + dependencies: + '@argos-ci/core': 6.8.5(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/playwright': 7.6.0(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/util': 4.2.0 + '@argos-ci/vitest': 0.7.0(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(playwright@1.63.0)(supports-color@10.2.2)(vitest@4.1.11) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0) + transitivePeerDependencies: + - '@types/node' + - '@vitest/browser' + - '@vitest/browser-playwright' + - playwright + - supports-color + - vitest + + '@argos-ci/util@4.2.0': {} + + '@argos-ci/vitest@0.7.0(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(playwright@1.63.0)(supports-color@10.2.2)(vitest@4.1.11)': + dependencies: + '@argos-ci/browser': 6.4.5 + '@argos-ci/core': 6.8.5(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/playwright': 7.6.0(@types/node@24.13.6)(supports-color@10.2.2) + '@argos-ci/util': 4.2.0 + '@vitest/browser': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + '@vitest/browser-playwright': 4.1.11(playwright@1.63.0)(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + playwright: 1.63.0 + vitest: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + transitivePeerDependencies: + - '@types/node' + - supports-color + '@asamuzakjp/css-color@5.1.11': dependencies: '@asamuzakjp/generational-cache': 1.0.1 @@ -6526,6 +6828,8 @@ snapshots: '@better-fetch/fetch@1.3.1': {} + '@blazediff/core@1.9.1': {} + '@bramus/specificity@2.4.2': dependencies: css-tree: 3.2.1 @@ -7634,9 +7938,9 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.21.2': optional: true - '@pgstencil/auth@file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)))': + '@pgstencil/auth@file:vendor/pgstencil-auth-0.1.0.tgz(hono@4.13.8)(kysely@0.29.5)(pg@8.23.0)(pgstencil@file:vendor/pgstencil-0.1.0.tgz(kysely@0.29.5)(supports-color@10.2.2))(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11)': dependencies: - better-auth: 1.7.3(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))) + better-auth: 1.7.3(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11) hono: 4.13.8 jose: 6.2.12 kysely: 0.29.5 @@ -7673,6 +7977,8 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@polka/url@1.0.0-next.29': {} + '@poppinss/colors@4.1.6': dependencies: kleur: 4.1.5 @@ -7977,6 +8283,19 @@ snapshots: transitivePeerDependencies: - '@types/react-dom' + '@storybook/addon-vitest@10.6.0(@vitest/browser-playwright@4.1.11)(@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11))(@vitest/runner@4.1.11)(react@19.3.0)(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(vitest@4.1.11)': + dependencies: + '@storybook/global': 5.0.0 + '@storybook/icons': 2.1.0(react@19.3.0) + storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0) + optionalDependencies: + '@vitest/browser': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + '@vitest/browser-playwright': 4.1.11(playwright@1.63.0)(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + '@vitest/runner': 4.1.11 + vitest: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + transitivePeerDependencies: + - react + '@storybook/builder-vite@10.6.0(storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))': dependencies: storybook: 10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0) @@ -8336,11 +8655,43 @@ snapshots: transitivePeerDependencies: - supports-color + '@vercel/detect-agent@1.2.5': {} + '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))': dependencies: '@rolldown/pluginutils': 1.0.1 vite: 8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + '@vitest/browser-playwright@4.1.11(playwright@1.63.0)(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11)': + dependencies: + '@vitest/browser': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) + '@vitest/mocker': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + playwright: 1.63.0 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + + '@vitest/browser@4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11)': + dependencies: + '@blazediff/core': 1.9.1 + '@vitest/mocker': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pngjs: 7.0.0 + sirv: 3.0.2 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + ws: 8.21.3 + transitivePeerDependencies: + - bufferutil + - msw + - utf-8-validate + - vite + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -8684,7 +9035,7 @@ snapshots: dependencies: tweetnacl: 0.14.5 - better-auth@1.7.3(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))): + better-auth@1.7.3(pg@8.23.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@4.1.11): dependencies: '@better-auth/core': 1.7.3(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.5)(nanostores@1.5.3) '@better-auth/drizzle-adapter': 1.7.3(@better-auth/core@1.7.3(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.5)(nanostores@1.5.3))(@better-auth/utils@0.4.2) @@ -8707,7 +9058,7 @@ snapshots: pg: 8.23.0 react: 19.3.0 react-dom: 19.3.0(react@19.3.0) - vitest: 4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + vitest: 4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) transitivePeerDependencies: - '@cloudflare/workers-types' - '@opentelemetry/api' @@ -8819,6 +9170,8 @@ snapshots: chalk@5.6.2: {} + chalk@6.0.0: {} + character-entities@2.0.2: {} chardet@2.2.0: {} @@ -8862,6 +9215,12 @@ snapshots: ci-info@2.0.0: {} + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-spinners@3.4.0: {} + cli-width@4.1.0: {} cliui@8.0.1: @@ -8894,6 +9253,8 @@ snapshots: commander@14.0.3: {} + commander@15.0.0: {} + commander@6.2.1: {} compress-commons@6.0.2: @@ -8920,6 +9281,11 @@ snapshots: convert-source-map@2.0.0: {} + convict@6.2.5: + dependencies: + lodash.clonedeep: 4.5.0 + yargs-parser: 20.2.9 + cookie-es@3.1.1: {} cookie@1.1.1: {} @@ -9500,14 +9866,20 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-in-ssh@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 + is-interactive@2.0.0: {} + is-it-type@5.1.3: dependencies: globalthis: 1.0.4 + is-network-error@1.3.2: {} + is-number@7.0.0: {} is-plain-obj@4.1.0: {} @@ -9516,6 +9888,8 @@ snapshots: is-stream@2.0.1: {} + is-unicode-supported@2.1.0: {} + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -9738,6 +10112,8 @@ snapshots: lodash.camelcase@4.3.0: {} + lodash.clonedeep@4.5.0: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -9756,6 +10132,11 @@ snapshots: lodash@4.18.1: {} + log-symbols@7.0.1: + dependencies: + is-unicode-supported: 2.1.0 + yoctocolors: 2.2.0 + long@5.3.2: {} longest-streak@3.1.0: {} @@ -10146,10 +10527,16 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + mime@1.6.0: {} mimic-function@5.0.1: {} @@ -10206,6 +10593,8 @@ snapshots: mkdirp@3.0.1: {} + mrmime@2.0.1: {} + ms@2.1.3: {} mute-stream@0.0.8: {} @@ -10306,6 +10695,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + open@10.2.0: dependencies: default-browser: 5.5.1 @@ -10313,11 +10706,37 @@ snapshots: is-inside-container: 1.0.0 wsl-utils: 0.1.0 + open@11.0.4: + dependencies: + default-browser: 5.5.1 + define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 + is-inside-container: 1.0.0 + powershell-utils: 0.2.1 + wsl-utils: 1.0.0 + + openapi-fetch@0.17.0: + dependencies: + openapi-typescript-helpers: 0.1.0 + + openapi-typescript-helpers@0.1.0: {} + openid-client@6.8.8: dependencies: jose: 6.2.12 oauth4webapi: 3.8.8 + ora@9.4.1: + dependencies: + chalk: 5.6.2 + cli-cursor: 5.0.0 + cli-spinners: 3.4.0 + is-interactive: 2.0.0 + is-unicode-supported: 2.1.0 + log-symbols: 7.0.1 + stdin-discarder: 0.3.2 + string-width: 8.2.2 + ovsx@1.2.0(@types/node@24.13.6)(debug@4.4.3(supports-color@10.2.2))(supports-color@10.2.2): dependencies: '@inquirer/prompts': 7.10.1(@types/node@24.13.6) @@ -10386,6 +10805,10 @@ snapshots: p-map@7.0.7: {} + p-retry@8.0.1: + dependencies: + is-network-error: 1.3.2 + package-json-from-dist@1.0.1: {} parse-json@8.3.0: @@ -10504,12 +10927,18 @@ snapshots: playwright-core@1.63.0: {} + playwright@1.63.0: + dependencies: + playwright-core: 1.63.0 + pluralize@2.0.0: {} pluralize@8.0.0: {} pngjs@6.0.0: {} + pngjs@7.0.0: {} + postcss@8.5.28: dependencies: nanoid: 3.3.18 @@ -10526,6 +10955,10 @@ snapshots: dependencies: xtend: 4.0.2 + powershell-utils@0.1.0: {} + + powershell-utils@0.2.1: {} + prebuild-install@7.1.3: dependencies: detect-libc: 2.1.2 @@ -10745,6 +11178,11 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + retry@0.12.0: {} reusify@1.1.0: {} @@ -11002,6 +11440,12 @@ snapshots: simplex-noise@4.0.3: {} + sirv@3.0.2: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + slash@5.1.0: {} slice-ansi@4.0.0: @@ -11049,6 +11493,8 @@ snapshots: std-env@4.2.0: {} + stdin-discarder@0.3.2: {} + storybook@10.6.0(@types/react@19.3.0)(prettier@3.9.6)(react@19.3.0): dependencies: '@storybook/global': 5.0.0 @@ -11105,6 +11551,11 @@ snapshots: get-east-asian-width: 1.6.0 strip-ansi: 7.2.0 + string-width@8.2.2: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -11303,6 +11754,8 @@ snapshots: dependencies: is-number: 7.0.0 + totalist@3.0.1: {} + tough-cookie@6.0.2: dependencies: tldts: 7.4.8 @@ -11476,7 +11929,7 @@ snapshots: tsx: 4.23.15 yaml: 2.9.1 - vitest@4.1.11(@types/node@24.13.6)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)): + vitest@4.1.11(@types/node@24.13.6)(@vitest/browser-playwright@4.1.11)(jsdom@29.1.1(@noble/hashes@2.4.0))(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)): dependencies: '@vitest/expect': 4.1.11 '@vitest/mocker': 4.1.11(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) @@ -11500,6 +11953,7 @@ snapshots: why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.13.6 + '@vitest/browser-playwright': 4.1.11(playwright@1.63.0)(vite@8.3.0(@types/node@24.13.6)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(vitest@4.1.11) jsdom: 29.1.1(@noble/hashes@2.4.0) transitivePeerDependencies: - msw @@ -11616,6 +12070,11 @@ snapshots: dependencies: is-wsl: 3.1.1 + wsl-utils@1.0.0: + dependencies: + is-wsl: 3.1.1 + powershell-utils: 0.1.0 + xml-name-validator@5.0.0: {} xml2js@0.5.0: @@ -11637,6 +12096,8 @@ snapshots: yaml@2.9.1: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} yargs-parser@22.0.0: {} @@ -11676,6 +12137,8 @@ snapshots: yoctocolors-cjs@2.1.3: {} + yoctocolors@2.2.0: {} + youch-core@0.3.3: dependencies: '@poppinss/exception': 1.2.3 diff --git a/scripts/spec-word-budgets.json b/scripts/spec-word-budgets.json index 555b27d60..dc7fd2928 100644 --- a/scripts/spec-word-budgets.json +++ b/scripts/spec-word-budgets.json @@ -19,12 +19,12 @@ "docs/specs/remote-api.md": 4700, "docs/specs/remote-security-model.md": 4750, "docs/specs/security-audit.md": 1950, - "docs/specs/security-ci.md": 2700, + "docs/specs/security-ci.md": 2750, "docs/specs/security-hosted.md": 600, "docs/specs/security-local.md": 3150, "docs/specs/security-remote.md": 5750, "docs/specs/security-supply-chain.md": 1150, - "docs/specs/security.md": 1900, + "docs/specs/security.md": 1950, "docs/specs/shortcuts.md": 1150, "docs/specs/standalone.md": 11500, "docs/specs/terminal-context.md": 1050, From 2221f3bb989304988baa58859a18192a05b071ba Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 23 Sep 2026 09:36:51 -0700 Subject: [PATCH 2/3] Regenerate tend workflows at 0.2.15 for the Argos watch Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/tend-ci-fix.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tend-ci-fix.yaml b/.github/workflows/tend-ci-fix.yaml index a9613d5f5..7e1050877 100644 --- a/.github/workflows/tend-ci-fix.yaml +++ b/.github/workflows/tend-ci-fix.yaml @@ -10,7 +10,7 @@ name: tend-ci-fix on: workflow_run: - workflows: ["CI", "Chromatic"] + workflows: ["CI", "Chromatic", "Argos"] types: [completed] branches: ["main"] From 4c12c9940c6c9930b735638cb658391964b6e9a9 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 23 Sep 2026 10:03:51 -0700 Subject: [PATCH 3/3] Simplify the Argos setup and answer review - Drop `.storybook/vitest.setup.ts`: since Storybook 10.3 the Vitest addon applies the preview's annotations, `beforeAll` included, on its own. - Name the freeze gate `visualSnapshot` and point the specs at visual snapshots rather than at Chromatic alone. - Workflow: a 30-minute cap, publish the Storybook even when a capture fails, drop the full-history checkout Argos does not need, and keep the Chromatic cross-reference in one header comment. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/argos.yml | 16 +++++++++------- docs/specs/alert.rationale.md | 2 +- docs/specs/layout.md | 10 +++++----- docs/specs/tiling-engine.md | 2 +- lib/.storybook/preview.ts | 27 +++++++++++++++------------ lib/.storybook/vitest.setup.ts | 4 ---- lib/vitest.argos.config.ts | 4 ++-- 7 files changed, 33 insertions(+), 32 deletions(-) delete mode 100644 lib/.storybook/vitest.setup.ts diff --git a/.github/workflows/argos.yml b/.github/workflows/argos.yml index a36136c6f..be9ca9f49 100644 --- a/.github/workflows/argos.yml +++ b/.github/workflows/argos.yml @@ -1,15 +1,14 @@ name: Argos # Runs beside Chromatic while Argos is evaluated as its replacement: same -# stories, same triggers, same draft and fork gates. Argos renders in this job +# stories, and the same triggers and draft and fork gates as `chromatic.yml`, +# whose comments give the reasons. Argos renders in this job # (Playwright, via the Storybook Vitest addon) rather than in its own cloud, so # a play-function exception fails the run here instead of passing silently. on: push: branches: - main - # The same inputs as `chromatic.yml`, for the same reasons given there: - # the lockfile and the three source-aliased packages are push-only. paths: - 'lib/**' - 'docs/stories/**' @@ -35,18 +34,17 @@ concurrency: jobs: argos: name: Argos Visual Tests - # Drafts do not spend screenshots; fork PRs get no ARGOS_TOKEN. See the - # matching gate in `chromatic.yml`. if: >- github.event_name != 'pull_request' || (github.event.pull_request.draft == false && github.event.pull_request.head.repo.full_name == github.repository) runs-on: ubuntu-latest + # Unlike Chromatic's, this job does the rendering, and `--with-deps` runs an + # unbounded apt-get; without a cap a stalled mirror holds the runner 6h. + timeout-minutes: 30 steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - fetch-depth: 0 - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 @@ -72,11 +70,15 @@ jobs: env: ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }} + # Publish even when a story fails capture, as Chromatic does, so one throwing + # `play` on main does not freeze the standing Storybook link. - name: Build Storybook + if: ${{ !cancelled() }} run: pnpm build-storybook working-directory: lib - name: Deploy Storybook + if: ${{ !cancelled() }} # main's deploy is the production one, the standing link to the Storybook. run: pnpm exec argos deploy ./storybook-static ${{ github.ref == 'refs/heads/main' && '--prod' || '' }} working-directory: lib diff --git a/docs/specs/alert.rationale.md b/docs/specs/alert.rationale.md index e8ed388e0..f089f620b 100644 --- a/docs/specs/alert.rationale.md +++ b/docs/specs/alert.rationale.md @@ -106,7 +106,7 @@ Guarding only completion leaves a stale `start` free to replace the active utter **Why `SPEAKING` may pulse unbounded and `SPOKEN` may not.** An utterance is seconds long and stops on its own, so the pulse it carries is self-bounding. `SPOKEN` persists until the ring is attended, so animating it would be exactly the per-Session animation with no end that bounding the burst exists to remove. -**Why `cfg.alert.ringingPaused` suppresses the pulse.** It is the Chromatic freeze that pins the alarm; even a bounded animation could otherwise snapshot at an arbitrary phase during its first 2.6 seconds. +**Why `cfg.alert.ringingPaused` suppresses the pulse.** It is the visual-snapshot freeze that pins the alarm; even a bounded animation could otherwise snapshot at an arbitrary phase during its first 2.6 seconds. **Why the unlabelled treatment pulses once per episode.** An infinite per-Session animation is expensive, and the whole-Pane treatment covers far more surface than the retired bell icon did. With four focused panes wearing an infinite animation, three minutes cost 6.89 MB of embedder memory, 1,127 style recalculations, and 3.99 seconds of renderer CPU; pausing only those animations in the same loaded document reduced that to 0.13 MB, two recalculations, and 0.025 seconds. After bounding the burst, two consecutive three-minute windows each had zero live animations, one recalculation, under 0.40 MB of non-cumulative embedder drift, and at most 0.024 seconds of renderer CPU (measured in Chrome 150, 2026-09). A handful of cycles preserves the entry cue without leaving an animation running for the lifetime of an unattended alert. The episode — not a track latch — is the key because the episode is the summons the sinks already work from: a second track latching inside one enriches an alarm the user was already shown, and re-flashing the whole Pane for it would read as a new alarm. Running the burst off `episode.startedAt` rather than from mount makes the CSS clock a property of the episode, so minimize → reattach or a Workspace switch lands past an expired burst instead of replaying it. A Session BEL-ing in a loop still cannot restart the burst, because a track that is already latched does not re-latch. diff --git a/docs/specs/layout.md b/docs/specs/layout.md index a140081a6..7b720bf5e 100644 --- a/docs/specs/layout.md +++ b/docs/specs/layout.md @@ -331,7 +331,7 @@ A fixed-positioned element on top of the Lath host, covering the active element' - **Exactly one pane or door is active at a time**, drawn by one SVG renderer (`SelectionRing`, `variant: 'ants' | 'solid'`). - **Passthrough:** `variant='solid'` — a 1px solid SVG stroke, centerline `strokeWidth/2` inside the div edge for panes and doors alike, no glow (rationale). - **Command:** `variant='ants'` — marching-ants border (`cfg.marchingAnts`: 10px segment, 60% dash, 0.4s cycle, 2px stroke). **March for as long as command mode lasts** (test: `marches for as long as command mode lasts, across selection changes` in `lib/src/components/wall/WorkspaceSelectionOverlay.test.tsx`; rationale). **Never restart or retime the march for travel** — only the dash resizes, refitted to the moving perimeter so segments stay even — and draw the smear separately ([Ring travel](#ring-travel)). **While unfocused, pause it and apply `saturate(0.3)` to the ring.** -- **Never pause the ants in a focused window except during Workspace title editing**, resuming when editing ends without changing mode, **under reduced motion**, which holds a still dashed ring, **or under `cfg.marchingAnts.paused`** (Chromatic sets it in `lib/.storybook/preview.ts`). Pinned by `pauses while a workspace is renamed, then resumes marching` and `holds the ants still under reduced motion` in `lib/src/components/wall/WorkspaceSelectionOverlay.test.tsx`. +- **Never pause the ants in a focused window except during Workspace title editing**, resuming when editing ends without changing mode, **under reduced motion**, which holds a still dashed ring, **or under `cfg.marchingAnts.paused`** (visual snapshots set it in `lib/.storybook/preview.ts`). Pinned by `pauses while a workspace is renamed, then resumes marching` and `holds the ants still under reduced motion` in `lib/src/components/wall/WorkspaceSelectionOverlay.test.tsx`. - Border radius follows DESIGN.md's Concentric-Corners Rule: the pane ring's radius is the pane radius plus the inflate (`PANE_SELECTION_RING_RADIUS_PX`), with the marching-ants path inset so its stroke centerline sits on the same gutter midline; doors sit at zero offset and keep `0.5rem 0.5rem 0 0`. - Color is the resolved `--color-focus-ring`, **re-read whenever `document.body`'s class/style changes**, because the dynamic palette publishes it there (`useFocusRingColor`). - `z-index: SELECTION_RING_Z_INDEX` (50), `pointer-events: none`. Under `WorkspaceWindow` it renders into `document.body`, outside the Workspace's transform and stacking context. @@ -346,8 +346,8 @@ Per-frame writes are **imperative**: `SelectionRing` gives the overlay refs to i - **Identity change → tween.** A measurement whose identity (`${selectedType}:${selectedId}`) differs from the one on screen glides from the current interpolated position to the new target, **clock restarted**, so arrow-key spam stays responsive. - **Same identity → snap 1:1.** A same-identity re-measure with no tween in flight (sash drag, window resize, a settled leaf's store commit) writes the new rect directly, tracking the geometry exactly instead of easing behind it. - **In-flight retarget.** A same-identity re-measure *during* a tween retargets the destination **without resetting the clock**, so the ring converges on a moving target (select-a-neighbor-during-kill) and still lands on the original completion instant. -- **Snap gate.** `motionIsInstant()` — `!cfg.layout.animate` (Chromatic) or `prefersReducedMotion()` — settles the ring instantly; it is the same predicate the Lath animator's duration uses, so ring and leaves agree. **A ring appearing with nothing on screen also snaps**: there is no `from` to glide from. -- **The unfocus-saturate fade is the one CSS transition** (`filter ${FOCUS_MOTION_MS}ms`, set inline by `SelectionRing.tsx`); neither the snap gate nor reduced motion touches it. Under Chromatic it snapshots already finished (pinned in `lib/.storybook/preview.ts`). +- **Snap gate.** `motionIsInstant()` — `!cfg.layout.animate` (visual snapshots) or `prefersReducedMotion()` — settles the ring instantly; it is the same predicate the Lath animator's duration uses, so ring and leaves agree. **A ring appearing with nothing on screen also snaps**: there is no `from` to glide from. +- **The unfocus-saturate fade is the one CSS transition** (`filter ${FOCUS_MOTION_MS}ms`, set inline by `SelectionRing.tsx`); neither the snap gate nor reduced motion touches it. Visual snapshots capture it already finished (pinned in `lib/.storybook/preview.ts`). - Pane↔door selection morphs the corner radii (12px all-round ⇄ `8,8,0,0`) and stroke inset through the same tween, so the shape lerps instead of popping. - **Must continue from the last painted frame across Workspace activation**, not the incoming Wall's stale frame. Hidden Walls neither animate nor publish ring geometry. Tabs use Door geometry; `+` uses its button rectangle and 4px corners. Pinned by `carries the last visible ring across Walls instead of their stale pane positions` in `lib/src/components/wall/WorkspaceSelectionOverlay.test.tsx`. @@ -421,7 +421,7 @@ The name `` is replaced by an `InlineEditInput` (shared with the browser U The field is **controlled by its own draft state**, seeded at mount and untouched by later prop changes, and **the `select()` ref callback has a stable identity** so it runs exactly once (rationale). **Mounting is the reset** — the editor exists only during a rename, so each one starts from the current label. Clipboard chords inside the field are the wall's job on hosts whose webview has no native Edit menu (`docs/specs/mouse-and-clipboard.md` §8.9). -Submitted values are rejected when empty or when they fail the `setTerminalUserTitle` validation that also guards title seeding (`docs/specs/terminal-state.md` → Supported OSC Inputs). `` is the default panel placeholder but is otherwise allowed as a user pin. **On rejection the input still closes** — it is not a blocking dialog — and a warning popover anchored under it names the offending value, dismissing on the next pointerdown, scroll, resize, `Escape`, or after `cfg.overlays.warningAutoDismissMs` (3s; 0 under Chromatic, pinned in `lib/.storybook/preview.ts`). +Submitted values are rejected when empty or when they fail the `setTerminalUserTitle` validation that also guards title seeding (`docs/specs/terminal-state.md` → Supported OSC Inputs). `` is the default panel placeholder but is otherwise allowed as a user pin. **On rejection the input still closes** — it is not a blocking dialog — and a warning popover anchored under it names the offending value, dismissing on the next pointerdown, scroll, resize, `Escape`, or after `cfg.overlays.warningAutoDismissMs` (3s; 0 under visual snapshots, pinned in `lib/.storybook/preview.ts`). Source of truth: `lib/src/components/wall/IllegalRenameWarning.tsx`, `lib/src/components/wall/use-dismiss-overlay.ts`. @@ -450,7 +450,7 @@ On cold restore, a terminal pane with a host-captured recovery invocation runs i ### Renderer -**Must use `@xterm/addon-webgl` for mounted terminals when available**, falling back to xterm's DOM renderer on unsupported WebGL, activation failure, or context-budget eviction. `cfg.terminal.webglRenderer` disables WebGL and is off under Chromatic. ImageAddon owns its separate canvas layers (`docs/specs/terminal-escapes.md` → "Inline graphics"). (rationale) +**Must use `@xterm/addon-webgl` for mounted terminals when available**, falling back to xterm's DOM renderer on unsupported WebGL, activation failure, or context-budget eviction. `cfg.terminal.webglRenderer` disables WebGL and is off under visual snapshots. ImageAddon owns its separate canvas layers (`docs/specs/terminal-escapes.md` → "Inline graphics"). (rationale) - **Must acquire GPU resources at mount, never at Session creation**, and keep a successfully activated renderer when context capture or explicit loss is unavailable. (rationale) - **Must dispose the addon on unmount/minimize, Workspace deactivation, helper parking, and Session disposal**, then explicitly lose its context when captured and supported. Report addon or extension failures without aborting teardown. Minimize preserves the xterm, grid, buffers, PTY, and other addons. diff --git a/docs/specs/tiling-engine.md b/docs/specs/tiling-engine.md index 22af4aa0f..96ff3c167 100644 --- a/docs/specs/tiling-engine.md +++ b/docs/specs/tiling-engine.md @@ -180,7 +180,7 @@ Source of truth: `BODY_COMPONENTS` / `TAB_COMPONENTS` / `OVERLAY_COMPONENTS` in - **A `retarget` mid-flight starts every leaf from its current interpolated frame** — interruptible by construction, no in-progress guards. `snap: true` starts leaves already settled (sash-drag commits, container resizes: hand-placed geometry must not tween), as does a retarget whose from/to frames already match. - **Enter hints are derived by the store's mutators, not passed in.** `addLeaf` / `restoreLeaf` / `insertLeaf` set the hint to the *opposite* of the edge they commit (`oppositeEdge`, so a pane placed right grows from its left boundary; a reattach hint therefore comes from the door token's edge), `consumeEnterHints` drains it at the next retarget, and the leaf's frames begin collapsed against that boundary at opacity 0. **Precedence**: a re-admitted parked leaf's held rect (an `EnterFrom` rect at full opacity) beats an explicit `setEnterHint`, which beats a derived hint (rationale). The only `setEnterHint` user is the auto-spawn refill (`'top-left'`, the killed last pane having shrunk toward the bottom-right). - **Exit is two-phase.** The Wall's `lath.markDying(id, { shrinkTowardBottomRight })` freezes and fades the leaf in place — the last-pane kill shrinking toward its bottom-right corner — with its terminal DOM still mounted; a `setTimeout(lath.exitMs)` then runs `disposeSession` and commits `store.removeLeaf`, and survivors tween into the reclaimed space on the resulting retarget. The finalizer bails if the leaf is already gone (superseded by a replace) and **forgets the surface ref only after the removal** (rationale). `isDying` makes a second kill a no-op. Dying leaves get `pointer-events: none`; a dying zoomed leaf keeps its elevated inset geometry and layer while LathHost applies the animator's opacity. -- **Ownership split**: the core animator is pure and owns the dying state; the *engine* owns the animator instance, `exitMs`, and the frame/wake signals (`markDying` fades without a store commit, so it wakes the tick loop itself); the *store* owns the enter-hint map; *LathHost* drives a rAF tick while unsettled and applies `framesAt` **imperatively** to the leaf divs (left/top/width/height/opacity/z-index/box-shadow/pointer-events). **Reduced motion is the same code path**, not a branch — `durationMs` 0 under `motionIsInstant()`, which Chromatic's `animate: false` in `lib/.storybook/preview.ts` also sets. **A no-deps layout effect re-asserts the current frames after any unrelated React commit**, so a mid-tween re-render cannot snap styles back to target (rationale). **There is no CSS entrance/exit path.** +- **Ownership split**: the core animator is pure and owns the dying state; the *engine* owns the animator instance, `exitMs`, and the frame/wake signals (`markDying` fades without a store commit, so it wakes the tick loop itself); the *store* owns the enter-hint map; *LathHost* drives a rAF tick while unsettled and applies `framesAt` **imperatively** to the leaf divs (left/top/width/height/opacity/z-index/box-shadow/pointer-events). **Reduced motion is the same code path**, not a branch — `durationMs` 0 under `motionIsInstant()`, which the visual-snapshot `animate: false` in `lib/.storybook/preview.ts` also sets. **A no-deps layout effect re-asserts the current frames after any unrelated React commit**, so a mid-tween re-render cannot snap styles back to target (rationale). **There is no CSS entrance/exit path.** Source of truth: `createAnimator` in `lib/src/lib/lath/animator.ts`; the animator ownership in `lib/src/components/wall/lath-wall-engine.ts`; the enter-hint derivation in `lib/src/components/wall/lath-wall-store.ts`; the frame-application effects in `lib/src/components/wall/LathHost.tsx`. diff --git a/lib/.storybook/preview.ts b/lib/.storybook/preview.ts index 2911d6d81..3592dfcd6 100644 --- a/lib/.storybook/preview.ts +++ b/lib/.storybook/preview.ts @@ -81,18 +81,21 @@ const FRAME_FALLBACK_MS = 100; // Initialize fake platform once at module scope const fakePlatform = initPlatform('fake'); -/** Set only by `lib/vitest.argos.config.ts`; absent in the real Storybook. */ -declare const __ARGOS_SNAPSHOT__: boolean | undefined; - -// Pin animations at T=0 for deterministic Chromatic and Argos snapshots. -// -// Ask `isChromatic()`, never the user agent: Chromatic only rewrites the UA on -// its Chrome runner, and identifies every other browser (Safari, Firefox, Edge) -// with a `chromatic=true` query parameter instead. A UA sniff therefore left -// every guard below OFF in Safari — an alarm pulsing on a 650ms infinite loop, a -// blinking cursor, terminals on WebGL, and mid-tween pane geometry — which is -// what made the Safari snapshots unstable while Chrome's stayed clean. -if (isChromatic() || (typeof __ARGOS_SNAPSHOT__ !== 'undefined' && __ARGOS_SNAPSHOT__)) { +/** Defined only by `lib/vitest.argos.config.ts`; absent in the real Storybook. */ +declare const __ARGOS_SNAPSHOT__: true | undefined; + +/** A visual-snapshot run — Chromatic or Argos — that must render deterministically. + * + * Ask `isChromatic()`, never the user agent: Chromatic only rewrites the UA on + * its Chrome runner, and identifies every other browser (Safari, Firefox, Edge) + * with a `chromatic=true` query parameter instead. A UA sniff therefore left + * every guard below OFF in Safari — an alarm pulsing on a 650ms infinite loop, a + * blinking cursor, terminals on WebGL, and mid-tween pane geometry — which is + * what made the Safari snapshots unstable while Chrome's stayed clean. */ +const visualSnapshot = isChromatic() || typeof __ARGOS_SNAPSHOT__ !== 'undefined'; + +// Pin animations at T=0 for deterministic snapshots. +if (visualSnapshot) { cfg.marchingAnts.paused = true; cfg.alert.ringingPaused = true; // A blinking cursor is captured on-or-off depending on the frame; freeze it to a diff --git a/lib/.storybook/vitest.setup.ts b/lib/.storybook/vitest.setup.ts deleted file mode 100644 index e7cff6f09..000000000 --- a/lib/.storybook/vitest.setup.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { setProjectAnnotations } from '@storybook/react-vite'; -import * as previewAnnotations from './preview'; - -setProjectAnnotations([previewAnnotations]); diff --git a/lib/vitest.argos.config.ts b/lib/vitest.argos.config.ts index ec4fac2fb..8c7454a1e 100644 --- a/lib/vitest.argos.config.ts +++ b/lib/vitest.argos.config.ts @@ -1,6 +1,7 @@ // Argos visual tests: every Storybook story runs in a real browser through the // Storybook Vitest addon, and `@argos-ci/storybook` screenshots it after its -// `play` settles. Separate from `vite.config.ts` so `pnpm test` never starts a +// `play` settles; the addon applies the preview's annotations, `beforeAll` +// included, itself (Storybook 10.3+), so no setup file. Separate from `vite.config.ts` so `pnpm test` never starts a // browser. `pnpm test:argos` writes to `./screenshots/`; it uploads only on CI. import path from 'path'; import { defineConfig, mergeConfig, type TestProjectInlineConfiguration } from 'vitest/config'; @@ -41,7 +42,6 @@ function storybookProject(browser: 'chromium' | 'webkit'): TestProjectInlineConf // Chromatic's default capture width; Vitest's default is a 414px phone. viewport: { width: 1200, height: 900 }, }, - setupFiles: ['.storybook/vitest.setup.ts'], }, }; }