diff --git a/.prettierrc b/.agents/skills/archify/.oxfmtrc.json similarity index 51% rename from .prettierrc rename to .agents/skills/archify/.oxfmtrc.json index 806e3c5f..f69698a4 100644 --- a/.prettierrc +++ b/.agents/skills/archify/.oxfmtrc.json @@ -3,5 +3,5 @@ "singleQuote": true, "trailingComma": "none", "printWidth": 120, - "tailwindFunction": ["clsx"] + "ignorePatterns": ["renderers/shared/generated-validators.mjs", "examples/*.html"] } diff --git a/.agents/skills/archify/LICENSE b/.agents/skills/archify/LICENSE new file mode 100644 index 00000000..4c27b715 --- /dev/null +++ b/.agents/skills/archify/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 tt-a1i (Archify) +Copyright (c) 2025 Cocoon AI (original "architecture-diagram-generator") + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/.agents/skills/archify/SKILL.md b/.agents/skills/archify/SKILL.md new file mode 100644 index 00000000..bdf96d08 --- /dev/null +++ b/.agents/skills/archify/SKILL.md @@ -0,0 +1,310 @@ +--- +name: archify +description: Create professional architecture, workflow, sequence, data-flow, and lifecycle/state diagrams as standalone HTML files with SVG graphics, a built-in dark/light theme toggle, and one-click export to PNG / JPEG / WebP / SVG. Accepts plain-language descriptions or pasted Mermaid code (flowchart, sequenceDiagram, stateDiagram) and lays the diagram out from scratch in archify style. Use when the user asks for system architecture diagrams, infrastructure diagrams, cloud architecture visualizations, security diagrams, network topology, technical workflows, approval flows, runbooks, CI/CD flows, process diagrams, API call sequences, request lifecycles, data pipelines, ETL/ELT maps, PII boundaries, data lineage, state machines, lifecycle diagrams, status transitions, or asks to convert/beautify a Mermaid diagram. +license: MIT +metadata: + version: '2.11' + author: tt-a1i + based_on: Cocoon-AI/architecture-diagram-generator (MIT, v1.0) +--- + +# Archify Skill + +Create professional technical diagrams as self-contained HTML files with inline SVG, a theme toggle, and a built-in image/SVG export menu. + +Every diagram ships with a **dark/light theme toggle** (persists in `localStorage`, respects `prefers-color-scheme`), an **export menu** (copy PNG to clipboard; download PNG/JPEG/WebP rasterized natively at up to 4× resolution; download a **dual-theme SVG** that follows the embedding host's `prefers-color-scheme` — ideal for GitHub READMEs), and a **CSS-variable color system** that keeps both themes consistent. + +## Setup + +No dependency installation is required. The distributed skill includes standalone validators compiled from all five JSON Schemas, so schema and layout validation work immediately after installation. The generated HTML and the renderer runtime are both dependency-free. + +Run `node bin/archify.mjs doctor` to verify an installation. Run `node bin/archify.mjs demo [output-directory]` to generate a ready-to-open example before creating the first custom diagram. + +If you have no shell access at all (e.g. the skill was added as project knowledge), fall back to architecture mode for every request: hand-place SVG into `assets/template.html` following the Design System below, and run the self-review checklist before delivering. + +## Choosing a Diagram Type + +| Type | Use for | How | +| -------------- | --------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | +| `architecture` | System components, cloud resources, services, security boundaries, infrastructure | `renderers/architecture/render-architecture.mjs` + JSON (or hand-place SVG when renderers can't run) | +| `workflow` | Technical flows, approval gates, tool calls, runbooks, CI/CD, incident response | `renderers/workflow/render-workflow.mjs` + JSON | +| `sequence` | API call chains, request lifecycles, cache fallback, async traces, return paths | `renderers/sequence/render-sequence.mjs` + JSON | +| `dataflow` | Pipelines, ETL/ELT, PII isolation, lineage, warehouse sync, consumers | `renderers/dataflow/render-dataflow.mjs` + JSON | +| `lifecycle` | State machines, status transitions, wait states, retries, terminal states | `renderers/lifecycle/render-lifecycle.mjs` + JSON | + +Trigger phrases: "architecture/system/cloud diagram" → `architecture` (unless clearly process-oriented). "workflow/flow/process/runbook/approval/CI-CD/incident" → `workflow`. "sequence/interaction/call chain/who calls whom" → `sequence`. "data flow/pipeline/ETL/lineage/PII/governance" → `dataflow`. "state/status/lifecycle/state machine/retry/terminal" → `lifecycle`. + +## Mermaid as an Input Dialect + +When the user pastes Mermaid code, do NOT try to render or parse it mechanically — read it for structure and **lay out from scratch** in the matching archify mode: + +| Mermaid | Archify mode | Mapping | +| --------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `flowchart` / `graph` | `workflow` (or `architecture` if it's a component map) | `subgraph` → lane or region boundary; node shape `{}` (diamond) → decision/security node; `-->` labels → edge labels (use sparingly); `classDef`/`style` → nearest semantic type | +| `sequenceDiagram` | `sequence` | `participant` → participants (pick semantic `type` from the name); `->>` → message, `-->>` → `return` variant; `Note` → message `note`; `rect` blocks → segments | +| `stateDiagram` | `lifecycle` | states → states (pick `start`/`active`/`waiting`/`success`/`failure` from names); `[*]` start/end → `start` type / `terminal` lane; transition labels → event-like labels | + +Drop Mermaid styling; keep only the topology and meaning. You choose grouping, lane order, and what deserves emphasis — that judgment is the product. + +## Layout principles (read before placing) + +Archify's readability comes from **spatial narrative**, not from drawing every dependency as an arrow. Before you write coordinates or edge lists, plan one clear story: + +1. **One main path** — left → right (architecture) or lane → column (workflow). The reader should trace the happy path without crossing lines. +2. **Few labeled edges** — label only cross-boundary or non-obvious transitions on the main path. Adjacent steps stay unlabeled. +3. **Short side branches** — permissions, storage, bots, CI: connect **up or down** from the nearest node on the main path. Never route a secondary edge diagonally across unrelated components. +4. **Cards for detail** — policies, tech stack notes, and "also connects to X" belong in summary cards, not as extra arrows. +5. **Mode fit** — process / approval / tool-call stories → `workflow` or `sequence`. Component maps with ≤12 nodes → `architecture`. If the diagram needs 20+ edges, remove edges until the main path is obvious. + +The bundled `examples/web-app.architecture.json` demonstrates this pattern end to end. + +When validation fails on label overlap, read the **Suggested fix** lines (coordinates / `labelAt` / `labelDy`) and apply them directly — do not guess offsets blindly. + +## Renderer Modes (architecture / workflow / sequence / dataflow / lifecycle) + +All five modes follow the same loop: + +1. **Read first**: the schema (`schemas/.schema.json`) and the complete worked example (`examples/*.{architecture,workflow,sequence,dataflow,lifecycle}.json`) — copy its patterns instead of guessing field shapes. +2. Write `..json`. +3. Render: `node bin/archify.mjs render .json .html` (paths relative to this skill's folder). +4. Validate the generated artifact: `node bin/archify.mjs validate .json --json`, or check an existing HTML file with `node bin/archify.mjs check .html`. This catches malformed SVG output, non-finite SVG values, two-point diagonal arrows, and arrows crossing the legend. +5. If either step fails, the error names the JSON path or the fix (thresholds, valid ranges, which knob to change). Fix the JSON and re-run; never edit the renderer. + +Schema violations exit non-zero with path-prefixed messages like `/nodes/3 (id/label: "router") must NOT have additional properties`. The renderers additionally fail fast on layout problems: node/state overlap (including cross-lane), labels colliding with nodes or other labels, labels wider than their node, out-of-range columns/rows, too-short edges, workflow edges crossing unrelated nodes, and legends outside the viewBox. CJK text is measured at double width automatically. + +Set `meta.animation: "trace"` only when the user asks for motion or a presentation/demo view. It adds lightweight SVG/CSS trace animation to renderer-marked arrows and nodes, respects `prefers-reduced-motion`, and leaves the default static output unchanged. + +### Workflow + +```json +{ + "schema_version": 1, + "diagram_type": "workflow", + "meta": { "title": "Release Workflow", "subtitle": "PR to production", "output": "release.html" }, + "lanes": [ + { "id": "dev", "label": "Developer" }, + { "id": "ci", "label": "CI" }, + { "id": "exceptions", "label": "Exception Handling", "variant": "exception" } + ], + "phases": [{ "id": "intake", "label": "Intake", "fromCol": 0, "toCol": 1 }], + "groups": [ + { "id": "checks", "label": "Parallel checks", "lane": "ci", "fromCol": 1, "toCol": 3, "variant": "emphasis" } + ], + "mainPath": ["pr", "build"], + "nodes": [ + { "id": "pr", "lane": "dev", "col": 0, "type": "frontend", "label": "Open PR", "sublabel": "feature branch" }, + { + "id": "build", + "lane": "ci", + "col": 1, + "type": "backend", + "label": "Build", + "sublabel": "lint + test", + "tag": "blocking" + } + ], + "edges": [ + { + "from": "pr", + "to": "build", + "label": "webhook", + "variant": "emphasis", + "fromSide": "bottom", + "toSide": "top", + "route": "drop" + } + ], + "cards": [] +} +``` + +**Layout budget**: 6 columns (`col` 0–5) at fixed x positions `[88, 220, 300, 430, 500, 625]` — columns 1↔2 and 3↔4 are only 70–80px apart, so default-width (92px) nodes in those adjacent columns of the same lane overlap; skip a column or shrink `width`. Lane content width is 640px. Omit `meta.viewBox` — the renderer sizes height to the lane count automatically. Use `phases` for top-of-diagram story beats, `groups` to frame parallel work or a branch inside one lane, and `lane.variant: "exception"` for error/retry/fallback lanes. `mainPath` is optional but recommended: list the happy-path node ids in order so the renderer can catch missing edges or accidental backward movement. Edge routes: `straight`, `drop` (bend between lanes; `bias` 0–1 picks where), `outside-right`, `return-left`, `bottom-channel`, `up-channel`, or explicit `via` points. Keep adjacent-step edges unlabeled; reserve labels for cross-lane transitions, approvals, async traces, and returns. + +### Sequence + +```json +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { "title": "Cache Miss Request", "subtitle": "auth and cache fallback", "output": "cache-miss.html" }, + "participants": [ + { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "React UI" }, + { "id": "api", "type": "backend", "label": "API", "sublabel": "handler" } + ], + "segments": [{ "from": 160, "to": 320, "label": "01 / AUTH" }], + "messages": [ + { "from": "web", "to": "api", "y": 200, "label": "GET /data", "variant": "emphasis" }, + { "from": "api", "to": "web", "y": 290, "label": "200 JSON", "variant": "return" } + ], + "activations": [{ "participant": "api", "from": 190, "to": 300, "type": "backend" }], + "cards": [] +} +``` + +**Layout budget**: participants sit at x = 62 + index×108, so a 920-wide viewBox fits at most 8. Message `y` must stay within `[160, viewBox_height − 83]`; messages that share horizontal space need ≥28px vertical separation; arrows need ≥60px horizontal span. `segments[].from/to` and `activations[].from/to` are **y pixel coordinates**, not participant ids. A taller `meta.viewBox` (default `[920, 760]`) buys more timeline room. Keep labels short: "GET /path", "verify JWT", "cache miss", "200 JSON". + +### Dataflow + +```json +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { "title": "Product Analytics", "subtitle": "events to consumers", "output": "analytics.html" }, + "stages": [{ "label": "Sources" }, { "label": "Ingest" }, { "label": "Store" }], + "nodes": [ + { "id": "web", "type": "frontend", "label": "Web App", "stage": 0, "row": 0, "sublabel": "clickstream" }, + { "id": "kafka", "type": "messagebus", "label": "Kafka", "stage": 1, "row": 0, "tag": "accepted events" } + ], + "flows": [{ "from": "web", "to": "kafka", "label": "events", "classification": "PII touch", "variant": "emphasis" }], + "cards": [] +} +``` + +**Layout budget**: 2–5 stages at x = 100 + stage×215; 5 rows (`row` 0–4) at y `[128, 242, 356, 470, 584]`; default node 112×58. Default viewBox `[940, 720]`. Flow labels are mandatory and asset-like ("clickstream", "identity map", "feature vectors"); put sensitivity in `classification` ("PII touch", "approved only", "non-PII"). Variants: `emphasis` = primary path, `security` = PII/policy/consent, `dashed` = async/batch. + +### Lifecycle + +```json +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { "title": "Agent Run Lifecycle", "subtitle": "states and terminal outcomes", "output": "agent-run.html" }, + "lanes": [ + { "id": "main", "label": "Lifecycle phases" }, + { "id": "waiting", "label": "Interruptions" }, + { "id": "terminal", "label": "Terminal exits" } + ], + "states": [ + { "id": "queued", "type": "start", "label": "Queued", "lane": "main", "col": 0, "step": "01" }, + { "id": "running", "type": "active", "label": "Executing", "lane": "main", "col": 2, "step": "02" }, + { "id": "approval", "type": "waiting", "label": "Needs Approval", "lane": "waiting", "col": 0 }, + { "id": "done", "type": "success", "label": "Completed", "lane": "terminal", "col": 2 } + ], + "transitions": [ + { "from": "queued", "to": "running", "variant": "emphasis" }, + { + "from": "running", + "to": "approval", + "label": "needs approval", + "variant": "security", + "fromSide": "bottom", + "toSide": "right" + }, + { + "from": "running", + "to": "done", + "label": "success", + "variant": "emphasis", + "fromSide": "bottom", + "toSide": "top" + } + ], + "cards": [] +} +``` + +**Layout budget — lane ids are semantic and reserved**: `main` is required and maps to the top phase band (cols 0–4); `terminal` maps to the bottom outcome band (cols 0–2); **every other lane id shares the single middle event band** (cols 0–2) — separate same-band states with different `col` or `yOffset`. Band headers render from your lane labels. Default viewBox `[980, 660]`. Keep transition labels event-like and sparse ("retry", "timeout", "cancel"); prefer state `tag`s, `step` numbers, and summary cards over label-heavy arrows. Put terminal states in the `terminal` lane so endings are unambiguous. + +### Per-mode deep guidance + +Each renderer has a README with its full design language (route presets, semantic types, story guidance): `renderers/workflow/README.md`, `renderers/sequence/README.md`, `renderers/dataflow/README.md`, `renderers/lifecycle/README.md`. Read the matching one before your first diagram of that mode in a session. + +## Architecture Mode + +Architecture has the same read-schema-then-render loop as the other modes — prefer it. Hand-placed SVG is the fallback for when renderers can't run. + +```json +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { "title": "Sample Web App", "subtitle": "3-tier SaaS on AWS", "output": "web-app.html" }, + "components": [ + { "id": "users", "type": "external", "label": "Users", "sublabel": "Browser", "pos": [40, 300] }, + { "id": "api", "type": "backend", "label": "API Server", "sublabel": "FastAPI :8000", "pos": [460, 300] }, + { "id": "db", "type": "database", "label": "PostgreSQL", "sublabel": ":5432", "pos": [680, 300] } + ], + "boundaries": [{ "kind": "region", "label": "AWS us-west-2", "wraps": ["api", "db"] }], + "connections": [ + { "from": "users", "to": "api", "label": "HTTPS", "variant": "emphasis" }, + { "from": "api", "to": "db", "label": "SQL" } + ], + "cards": [] +} +``` + +Render: `node bin/archify.mjs render architecture .json .html`. + +**Free placement** — `pos: [x, y]` is the component's top-left; `size: [w, h]` defaults to `[120, 60]`. Unlike typed modes there is no lane/stage grid — asymmetric placement is yours to choose. `meta.viewBox` is optional (auto-fitted). + +**Grid placement (#8)** — when manual coordinates are painful, set semantic cells instead of doing arithmetic: + +```json +{ + "layout": { "mode": "grid", "cols": 7, "origin": [40, 100], "gapX": 24, "gapY": 48, "cellW": 120, "cellH": 60 }, + "components": [ + { "id": "agents", "type": "frontend", "label": "Agent Hosts", "row": 1, "col": 1 }, + { "id": "ir", "type": "messagebus", "label": "JSON IR", "row": 1, "col": 2 } + ] +} +``` + +`pos` still wins when present (override one cell). This is **not** auto-layout — spacing is fixed cell math. + +**Inspect layout (#9)** — after editing JSON, dump computed boxes without opening HTML: + +```bash +node bin/archify.mjs inspect architecture my.architecture.json +# or: node bin/archify.mjs validate architecture my.architecture.json --layout-json +``` + +Output includes component rects, boundaries, connection point paths, and label positions. + +**The renderer does the mechanical work that used to be hand-tuned**, so you only choose coordinates and meaning: + +- **Free coordinates** — `pos: [x, y]` is the component's top-left; `size: [w, h]` defaults to `[120, 60]`. Unlike the typed modes there is no lane/stage grid — asymmetric placement is yours to choose. `meta.viewBox` is optional (auto-fitted to your components + a legend row). +- **Grid placement** — optional `layout.mode: "grid"` with `row`/`col` per component (see above). Not dagre; fixed cell spacing only. +- **Boundaries from `wraps`** — list the component ids a `region` (dashed amber) or `security-group` (dashed rose) encloses; the renderer computes the box with correct 30/50 padding automatically. Never hand-arithmetic a boundary again. +- **Connections** route like edges (`variant`, `fromSide`/`toSide`, `route: straight|orthogonal-h|orthogonal-v|auto`, `via`, `labelDx/labelDy/labelAt`). For a vertical labeled connection, push the label into the gap with `labelDy` (the validator will tell you if it lands on a box). +- The renderer auto-emits the two-rect `c-mask` pattern, draws arrows before boxes (z-order), builds the legend from the component types you used, and **fails fast on component overlap, off-canvas components/boundaries, unknown wraps/connection ids, label-vs-component collisions, and non-finite coordinates** — the same reliability the other four modes already had. + +### Hand-placed fallback (no renderer available) + +When Node/ajv can't run, copy `assets/template.html` and place SVG by hand. Study the worked diagram inside the template and `examples/web-app-rendered.html` for coordinate idioms, follow the Design System below, and run the self-review checklist before delivering. + +### The Cardinal Rule: CSS classes, not inline colors + +The theme toggle works by switching CSS custom properties. Hardcoded `fill="rgba(...)"` or `stroke="#22d3ee"` will NOT update on theme change. Always use the class system: + +```svg + + +API Server +FastAPI :8000 +``` + +### Design system + +Component fills `c-frontend` (clients/UI), `c-backend` (services/APIs), `c-database` (stores/caches), `c-cloud` (managed infra), `c-security` (auth/secrets), `c-messagebus` (Kafka/queues), `c-external` (3rd parties); text accents `t-` plus neutrals `t-primary` / `t-muted` / `t-dim`. Arrows `a-default`, `a-emphasis` (hot path), `a-security` (dashed), `a-dashed` (async) — always set `stroke-width` and pair `marker-end="url(#arrowhead[-variant])"` with the matching class. Boundaries: `c-security-group` (dashed rose), `c-region` (dashed amber), `c-lane` (swimlane). + +Typography inherits JetBrains Mono from the SVG root. Sizes: 11–12px component names, 9px sublabels, 8px annotations, 7px tiny labels. + +### Hard layout rules + +- **Two-rect pattern everywhere**: opaque `c-mask` rect first, styled `c-` rect on top — semi-transparent fills otherwise let arrows bleed through. +- **Arrows before components** in document order (SVG paints in order; arrows must sit behind boxes). +- **Vertical stacking**: ≥40px gap between components; inline connectors (message buses, 20px tall) live inside the gap, never overlapping boxes. +- **Boundary padding**: boundary `y` = inner `y` − 30, boundary `height` = inner `height` + 50, label baseline 18px below the boundary top. +- **Legend placement**: outside ALL boundary boxes, ≥20px below the lowest one; grow the viewBox if needed. + +### Self-review checklist (run before delivering) + +1. `grep -E 'fill="(#|rgb)|stroke="(#|rgb)' out.html` inside the SVG returns nothing except the template's own defs (Cardinal Rule). +2. Every `c-` rect has an identical-geometry `c-mask` rect immediately before it. +3. All ``/`` arrows appear before all component rects in document order. +4. Compute max(y + height) over all SVG elements: viewBox height must exceed it by ≥20px; same for x/width. +5. Legend y is below every boundary's y + height. +6. The `.toolbar`, ` + + + + + + + + + + +
+ +
+
+
+

[PROJECT NAME] Architecture

+
+

[Subtitle description]

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Users + Browser/Mobile + + + + + + Auth Provider + + OAuth 2.0 + + + + AWS Region: us-west-2 + + + + + + CloudFront + + CDN + + + + + + S3 Buckets + + • bucket-one + • bucket-two + • bucket-three + OAI Protected + + + + sg-name :port + + + + + + Load Balancer + + HTTPS :443 + + + + + + API Server + + FastAPI :8000 + + + + + Database + PostgreSQL + + + + + Frontend + React + TypeScript + Additional detail + More info + domain.example.com + + + + + + HTTPS + + + + + + + OAI + + + + + TLS + + + + JWT + PKCE + + + Legend + + + Frontend + + + Backend + + + Cloud Service + + + Database + + + Security + + + Auth Flow + + + Security Group + + +
+ + +
+
+
+
+

Card Title 1

+
+
    +
  • • Item one
  • +
  • • Item two
  • +
  • • Item three
  • +
  • • Item four
  • +
+
+ +
+
+
+

Card Title 2

+
+
    +
  • • Item one
  • +
  • • Item two
  • +
  • • Item three
  • +
  • • Item four
  • +
+
+ +
+
+
+

Card Title 3

+
+
    +
  • • Item one
  • +
  • • Item two
  • +
  • • Item three
  • +
  • • Item four
  • +
+
+
+ + + + +
+ + + + diff --git a/.agents/skills/archify/bin/archify.mjs b/.agents/skills/archify/bin/archify.mjs new file mode 100755 index 00000000..a78adc02 --- /dev/null +++ b/.agents/skills/archify/bin/archify.mjs @@ -0,0 +1,277 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath, pathToFileURL } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') + +const TYPES = new Set(['architecture', 'workflow', 'sequence', 'dataflow', 'lifecycle']) + +function usage() { + return `Usage: + archify render [output.html] + archify validate [--json] [--layout-json] + archify inspect + archify check + archify examples + archify doctor + archify demo [output-directory] + +Types: + architecture, workflow, sequence, dataflow, lifecycle +` +} + +function fail(message, code = 2) { + console.error(message) + process.exit(code) +} + +function rendererPath(type) { + if (!TYPES.has(type)) { + fail(`Unknown diagram type "${type}". Expected one of: ${[...TYPES].join(', ')}`) + } + return path.join(skillRoot, 'renderers', type, `render-${type}.mjs`) +} + +function runNode(args, options = {}) { + return spawnSync(process.execPath, args, { + cwd: options.cwd || process.cwd(), + encoding: 'utf8', + stdio: options.stdio || 'inherit' + }) +} + +function exitFrom(result) { + if (result.error) fail(result.error.message, 1) + process.exit(result.status ?? 1) +} + +function commandRender(args) { + const [type, input, output] = args + if (!type || !input) fail(usage()) + const result = runNode([rendererPath(type), input, ...(output ? [output] : [])]) + if (result.status !== 0) exitFrom(result) +} + +function commandCheck(args) { + const [html] = args + if (!html) fail(usage()) + const result = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), html]) + if (result.status !== 0) exitFrom(result) +} + +function commandExamples() { + const result = runNode([path.join(skillRoot, 'scripts/render-examples.mjs')], { cwd: skillRoot }) + if (result.status !== 0) exitFrom(result) +} + +async function commandDoctor() { + const checks = [] + const nodeMajor = Number.parseInt(process.versions.node.split('.')[0], 10) + checks.push({ + label: `Node.js v${process.versions.node} (requires >=18)`, + ok: nodeMajor >= 18, + missing: 0, + failureLabel: 'unsupported' + }) + + const template = path.join(skillRoot, 'assets/template.html') + checks.push({ + label: 'Core template', + ok: fs.existsSync(template), + missing: fs.existsSync(template) ? 0 : 1 + }) + + const examplesRenderer = path.join(skillRoot, 'scripts/render-examples.mjs') + checks.push({ + label: 'Example renderer', + ok: fs.existsSync(examplesRenderer), + missing: fs.existsSync(examplesRenderer) ? 0 : 1 + }) + + const validators = path.join(skillRoot, 'renderers/shared/generated-validators.mjs') + const validatorsExist = fs.existsSync(validators) + let validatorsValid = false + if (validatorsExist) { + try { + const module = await import(`${pathToFileURL(validators).href}?doctor=${Date.now()}`) + validatorsValid = [...TYPES].every((type) => typeof module[type] === 'function') + } catch { + validatorsValid = false + } + } + checks.push({ + label: 'Standalone schema validators', + ok: validatorsValid, + missing: validatorsExist ? 0 : 1, + invalid: validatorsExist && !validatorsValid ? 1 : 0, + failureLabel: validatorsExist ? 'invalid' : 'missing' + }) + + const examples = { + architecture: 'web-app.architecture.json', + workflow: 'agent-tool-call.workflow.json', + sequence: 'cache-miss-request.sequence.json', + dataflow: 'product-analytics.dataflow.json', + lifecycle: 'agent-run.lifecycle.json' + } + + for (const type of TYPES) { + const required = [ + path.join(skillRoot, 'renderers', type, `render-${type}.mjs`), + path.join(skillRoot, 'schemas', `${type}.schema.json`), + path.join(skillRoot, 'examples', examples[type]) + ] + const missing = required.filter((file) => !fs.existsSync(file)).length + checks.push({ + label: `${type} renderer, schema, and example`, + ok: missing === 0, + missing + }) + } + + console.log('Archify doctor\n') + for (const check of checks) { + console.log(`[${check.ok ? 'ok' : check.failureLabel || 'missing'}] ${check.label}`) + } + + const nodeFailed = checks[0].ok ? 0 : 1 + const missingFiles = checks.reduce((count, check) => count + check.missing, 0) + const invalidRuntime = checks.reduce((count, check) => count + (check.invalid || 0), 0) + if (nodeFailed === 0 && missingFiles === 0 && invalidRuntime === 0) { + console.log('\nArchify is ready.') + return + } + + const problems = [] + if (nodeFailed) problems.push('Node.js 18 or newer is required') + if (missingFiles) problems.push(`${missingFiles} required file${missingFiles === 1 ? '' : 's'} missing`) + if (invalidRuntime) problems.push(`${invalidRuntime} runtime check${invalidRuntime === 1 ? '' : 's'} failed`) + console.error(`\nArchify is not ready: ${problems.join('; ')}.`) + process.exitCode = 1 +} + +function commandDemo(args) { + if (args.length > 1) fail(usage()) + + const outputDirectory = path.resolve(args[0] || process.cwd()) + const output = path.join(outputDirectory, 'archify-demo.html') + const input = path.join(skillRoot, 'examples/web-app.architecture.json') + + try { + fs.mkdirSync(outputDirectory, { recursive: true }) + } catch (error) { + fail(`Could not create demo directory "${outputDirectory}": ${error.message}`, 1) + } + + const result = runNode([rendererPath('architecture'), input, output]) + if (result.status !== 0) exitFrom(result) + + console.log(`\nDemo ready: ${output}`) + console.log('Next: open the HTML in your browser, then render your own diagram:') + console.log(' archify render architecture ') +} + +function commandValidate(args) { + const json = args.includes('--json') + const layoutJson = args.includes('--layout-json') + const rest = args.filter((arg) => arg !== '--json' && arg !== '--layout-json') + const [type, input] = rest + if (!type || !input) fail(usage()) + const renderer = rendererPath(type) + + if (layoutJson) { + if (type !== 'architecture') { + fail('--layout-json is currently supported for architecture diagrams only.') + } + const result = runNode([renderer, input, '/dev/null', '--layout-json'], { stdio: 'pipe' }) + if (result.status !== 0) { + if (result.stderr) process.stderr.write(result.stderr) + if (result.stdout) process.stdout.write(result.stdout) + process.exit(result.status ?? 1) + } + process.stdout.write(result.stdout) + return + } + + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-validate-')) + const out = path.join(tmp, `${type}.html`) + let exitCode = 0 + + try { + const render = runNode([renderer, input, out], { stdio: 'pipe' }) + if (render.status !== 0) { + if (render.stderr) process.stderr.write(render.stderr) + if (render.stdout) process.stdout.write(render.stdout) + exitCode = render.status ?? 1 + } else { + const check = runNode([path.join(skillRoot, 'scripts/check-render-output.mjs'), out], { stdio: 'pipe' }) + if (check.status !== 0) { + if (check.stdout) process.stdout.write(check.stdout) + if (check.stderr) process.stderr.write(check.stderr) + exitCode = check.status ?? 1 + } else { + const result = JSON.parse(check.stdout) + if (json) { + console.log( + JSON.stringify( + { + ok: true, + type, + input: path.resolve(input), + checks: result.checks + }, + null, + 2 + ) + ) + } else { + console.log(`ok ${type} ${path.resolve(input)} (${result.checks.length} checks)`) + } + } + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }) + } + + if (exitCode !== 0) process.exit(exitCode) +} + +const [command, ...args] = process.argv.slice(2) + +switch (command) { + case undefined: + case '-h': + case '--help': + case 'help': + console.log(usage()) + break + case 'render': + commandRender(args) + break + case 'validate': + commandValidate(args) + break + case 'inspect': + commandValidate([...args, '--layout-json']) + break + case 'check': + commandCheck(args) + break + case 'examples': + commandExamples() + break + case 'doctor': + await commandDoctor() + break + case 'demo': + commandDemo(args) + break + default: + fail(`Unknown command "${command}".\n\n${usage()}`) +} diff --git a/.agents/skills/archify/examples/agent-run.lifecycle.json b/.agents/skills/archify/examples/agent-run.lifecycle.json new file mode 100644 index 00000000..48f1bc28 --- /dev/null +++ b/.agents/skills/archify/examples/agent-run.lifecycle.json @@ -0,0 +1,181 @@ +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { + "title": "Agent Run Lifecycle", + "subtitle": "State machine for planning, tool execution, human approval, retries, and terminal outcomes", + "output": "examples/lifecycle-agent-run.html", + "viewBox": [980, 660] + }, + "lanes": [ + { "id": "main", "label": "Lifecycle phases" }, + { "id": "waiting", "label": "Interruptions" }, + { "id": "exceptions", "label": "Recovery loop" }, + { "id": "terminal", "label": "Terminal exits" } + ], + "states": [ + { + "id": "queued", + "type": "start", + "label": "Queued", + "sublabel": "request accepted", + "lane": "main", + "col": 0, + "step": "01", + "tag": "entry" + }, + { + "id": "planning", + "type": "active", + "label": "Planning", + "sublabel": "build task graph", + "lane": "main", + "col": 1, + "step": "02", + "tag": "model" + }, + { + "id": "executing", + "type": "active", + "label": "Executing", + "sublabel": "tool calls", + "lane": "main", + "col": 2, + "step": "03", + "tag": "work" + }, + { + "id": "reviewing", + "type": "decision", + "label": "Reviewing", + "sublabel": "quality gate", + "lane": "main", + "col": 3, + "step": "04", + "tag": "check" + }, + { + "id": "completed", + "type": "success", + "label": "Completed", + "sublabel": "final response", + "lane": "main", + "col": 4, + "step": "05", + "tag": "done" + }, + { + "id": "approval", + "type": "waiting", + "label": "Needs Approval", + "sublabel": "human gate", + "lane": "waiting", + "col": 0, + "tag": "pause" + }, + { + "id": "blocked", + "type": "waiting", + "label": "Blocked", + "sublabel": "missing input", + "lane": "waiting", + "col": 1, + "tag": "wait" + }, + { + "id": "failed", + "type": "failure", + "label": "Failed", + "sublabel": "recoverable error", + "lane": "exceptions", + "col": 0, + "yOffset": 78, + "tag": "retryable" + }, + { + "id": "cancelled", + "type": "failure", + "label": "Cancelled", + "sublabel": "user stopped", + "lane": "terminal", + "col": 0, + "tag": "terminal" + }, + { + "id": "expired", + "type": "failure", + "label": "Expired", + "sublabel": "timeout", + "lane": "terminal", + "col": 1, + "tag": "terminal" + } + ], + "transitions": [ + { + "from": "executing", + "to": "approval", + "variant": "security", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" + }, + { "from": "reviewing", "to": "blocked", "variant": "default", "route": "drop" }, + { + "from": "executing", + "to": "failed", + "variant": "security", + "fromSide": "left", + "toSide": "top", + "via": [ + [340, 342], + [402, 342] + ] + }, + { + "from": "blocked", + "to": "expired", + "variant": "security", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" + }, + { + "from": "approval", + "to": "cancelled", + "variant": "security", + "fromSide": "bottom", + "toSide": "top", + "route": "straight" + } + ], + "cards": [ + { + "dot": "emerald", + "title": "Main Path", + "items": [ + "The run has five ordered phases from queue to completion", + "The primary lifecycle is carried by one horizontal rail", + "Completion is a phase, not a detached side box" + ] + }, + { + "dot": "amber", + "title": "Human + Input Gates", + "items": [ + "Approval pauses execution without ending the run", + "Blocked waits for missing user input", + "Wait states can resume back into planning or execution" + ] + }, + { + "dot": "rose", + "title": "Terminal + Recovery", + "items": [ + "Failed loops back while retry budget remains", + "Cancelled and Expired are exits from the lifecycle", + "Terminal exits do not point back into active execution" + ] + } + ] +} diff --git a/.agents/skills/archify/examples/agent-tool-call.workflow.json b/.agents/skills/archify/examples/agent-tool-call.workflow.json new file mode 100644 index 00000000..ca20f742 --- /dev/null +++ b/.agents/skills/archify/examples/agent-tool-call.workflow.json @@ -0,0 +1,224 @@ +{ + "schema_version": 1, + "diagram_type": "workflow", + "meta": { + "title": "Agent Tool Call Workflow", + "subtitle": "Renderer-driven workflow prototype with lanes, anchored nodes, and orthogonal edges", + "output": "examples/workflow-agent-tool-call-rendered.html", + "viewBox": [720, 900] + }, + "lanes": [ + { "id": "ui", "label": "User Interface" }, + { "id": "agent", "label": "Agent Runtime" }, + { "id": "policy", "label": "Policy Boundary" }, + { "id": "exceptions", "label": "Exception Handling", "variant": "exception" }, + { "id": "tools", "label": "Tool Execution" }, + { "id": "trace", "label": "Observability" } + ], + "phases": [ + { "id": "intake", "label": "Intake", "fromCol": 0, "toCol": 1 }, + { "id": "reasoning", "label": "Plan + route", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "execution", "label": "Execute + report", "fromCol": 4, "toCol": 5, "variant": "dashed" } + ], + "groups": [ + { "id": "agent_loop", "label": "Planning loop", "lane": "agent", "fromCol": 2, "toCol": 3, "variant": "emphasis" }, + { "id": "tool_work", "label": "Tool work", "lane": "tools", "fromCol": 4, "toCol": 5, "variant": "dashed" }, + { + "id": "exception_path", + "label": "Human or policy stop", + "lane": "exceptions", + "fromCol": 3, + "toCol": 5, + "variant": "security" + } + ], + "mainPath": ["user", "chat", "planner", "router", "approval", "tool", "external", "final"], + "nodes": [ + { "id": "user", "lane": "ui", "col": 0, "type": "external", "label": "User", "sublabel": "asks for work" }, + { "id": "chat", "lane": "ui", "col": 1, "type": "frontend", "label": "Chat Surface", "sublabel": "thread + files" }, + { + "id": "final", + "lane": "ui", + "col": 5, + "type": "backend", + "label": "Final Reply", + "sublabel": "answer + changes" + }, + { + "id": "planner", + "lane": "agent", + "col": 2, + "type": "backend", + "label": "Agent Planner", + "sublabel": "plan next step", + "tag": "context aware" + }, + { + "id": "router", + "lane": "agent", + "col": 3, + "type": "backend", + "label": "Tool Router", + "sublabel": "choose capability" + }, + { + "id": "approval", + "lane": "policy", + "col": 3, + "type": "security", + "label": "Approval Gate", + "sublabel": "scope + consent", + "tag": "block risky ops" + }, + { + "id": "blocked", + "lane": "exceptions", + "col": 4, + "type": "security", + "label": "Blocked", + "sublabel": "wait or reject" + }, + { + "id": "retry", + "lane": "exceptions", + "col": 5, + "type": "messagebus", + "label": "Retry Path", + "sublabel": "revise request" + }, + { + "id": "tool", + "lane": "tools", + "col": 4, + "type": "messagebus", + "label": "Tool Call", + "sublabel": "shell / browser / MCP", + "tag": "structured result" + }, + { + "id": "external", + "lane": "tools", + "col": 5, + "type": "cloud", + "label": "External API", + "sublabel": "network service" + }, + { + "id": "store", + "lane": "trace", + "col": 1, + "type": "database", + "label": "Context Store", + "sublabel": "repo + memory" + }, + { + "id": "trace", + "lane": "trace", + "col": 4, + "type": "database", + "label": "Trace Log", + "sublabel": "events + output" + } + ], + "edges": [ + { "from": "user", "to": "chat", "variant": "default" }, + { + "from": "chat", + "to": "planner", + "label": "plan", + "variant": "emphasis", + "fromSide": "bottom", + "toSide": "top", + "route": "drop", + "labelSegment": 1 + }, + { "from": "planner", "to": "router", "variant": "default" }, + { + "from": "router", + "to": "approval", + "label": "needs approval?", + "variant": "security", + "fromSide": "bottom", + "toSide": "top", + "route": "drop", + "labelSegment": 0, + "labelDx": 34, + "labelDy": 18 + }, + { + "from": "approval", + "to": "tool", + "variant": "emphasis", + "fromSide": "left", + "toSide": "left", + "route": "return-left" + }, + { + "from": "approval", + "to": "blocked", + "label": "denied", + "variant": "security", + "role": "error", + "fromSide": "bottom", + "toSide": "top", + "route": "drop", + "labelSegment": 1, + "labelDy": 12 + }, + { "from": "blocked", "to": "retry", "variant": "dashed", "role": "branch" }, + { "from": "tool", "to": "external", "variant": "default" }, + { + "from": "external", + "to": "final", + "variant": "emphasis", + "role": "return", + "fromSide": "right", + "toSide": "right", + "route": "outside-right", + "width": 1.2 + }, + { + "from": "external", + "to": "trace", + "label": "record result", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "bottom", + "route": "bottom-channel", + "labelSegment": 1 + }, + { "from": "store", "to": "trace", "label": "trace + memory", "variant": "dashed", "labelAt": [365, 735] } + ], + "cards": [ + { + "dot": "cyan", + "title": "Renderer Rules", + "items": [ + "Lanes and columns determine node placement", + "Edges attach to explicit node anchors", + "Cross-lane paths use orthogonal routing", + "Short adjacent links stay unlabeled" + ] + }, + { + "dot": "rose", + "title": "Workflow Semantics", + "items": [ + "Approval is a first-class policy step", + "Consent gates are visible in the main path", + "External calls stay inside the tool lane", + "Trace writes are separate from the hot path" + ] + }, + { + "dot": "emerald", + "title": "Why It Matters", + "items": [ + "This is closer to a diagram_type renderer", + "The graph can be edited without SVG surgery", + "Layout rules can be tested and improved", + "A future IR can reuse this shape directly" + ] + } + ] +} diff --git a/.agents/skills/archify/examples/cache-miss-request.sequence.json b/.agents/skills/archify/examples/cache-miss-request.sequence.json new file mode 100644 index 00000000..6a10a97e --- /dev/null +++ b/.agents/skills/archify/examples/cache-miss-request.sequence.json @@ -0,0 +1,75 @@ +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Cache Miss Request Sequence", + "subtitle": "Frontend request path with auth, cache fallback, persistence, and async trace", + "output": "examples/sequence-cache-miss-request.html", + "viewBox": [820, 760] + }, + "participants": [ + { "id": "user", "type": "external", "label": "User", "sublabel": "browser session" }, + { "id": "web", "type": "frontend", "label": "Web App", "sublabel": "React UI" }, + { "id": "api", "type": "backend", "label": "API", "sublabel": "request handler" }, + { "id": "auth", "type": "security", "label": "Auth", "sublabel": "JWT verify" }, + { "id": "redis", "type": "database", "label": "Redis", "sublabel": "cache" }, + { "id": "db", "type": "database", "label": "Postgres", "sublabel": "source of truth" }, + { "id": "trace", "type": "messagebus", "label": "Trace", "sublabel": "async event" } + ], + "segments": [ + { "from": 150, "to": 295, "label": "Request" }, + { "from": 315, "to": 505, "label": "Fallback" }, + { "from": 525, "to": 665, "label": "Response + trace" } + ], + "messages": [ + { "from": "user", "to": "web", "y": 185, "label": "open page", "variant": "default" }, + { "from": "web", "to": "api", "y": 228, "label": "GET /dashboard", "variant": "emphasis" }, + { "from": "api", "to": "auth", "y": 270, "label": "verify JWT", "variant": "security" }, + { "from": "auth", "to": "api", "y": 305, "label": "claims ok", "variant": "return" }, + { "from": "api", "to": "redis", "y": 354, "label": "read cache", "variant": "default" }, + { "from": "redis", "to": "api", "y": 391, "label": "miss", "variant": "return" }, + { "from": "api", "to": "db", "y": 443, "label": "query profile + metrics", "variant": "emphasis" }, + { "from": "db", "to": "api", "y": 489, "label": "rows", "variant": "return" }, + { "from": "api", "to": "redis", "y": 536, "label": "set cache", "variant": "dashed" }, + { "from": "api", "to": "trace", "y": 580, "label": "emit trace", "variant": "dashed" }, + { "from": "api", "to": "web", "y": 625, "label": "200 JSON", "variant": "return" }, + { "from": "web", "to": "user", "y": 662, "label": "render", "variant": "return" } + ], + "activations": [ + { "participant": "web", "from": 220, "to": 668, "type": "frontend" }, + { "participant": "api", "from": 228, "to": 632, "type": "backend" }, + { "participant": "auth", "from": 265, "to": 310, "type": "security" }, + { "participant": "redis", "from": 349, "to": 398, "type": "database" }, + { "participant": "db", "from": 438, "to": 496, "type": "database" }, + { "participant": "trace", "from": 575, "to": 630, "type": "messagebus" } + ], + "cards": [ + { + "dot": "emerald", + "title": "Happy Path", + "items": [ + "The main request is Web App -> API -> data source -> response", + "Return messages are quieter than forward calls", + "Activation bars make ownership duration visible" + ] + }, + { + "dot": "rose", + "title": "Policy + Fallback", + "items": [ + "JWT verification is colored as a security interaction", + "Cache miss is visible without overpowering the main path", + "Database access only appears after cache fallback" + ] + }, + { + "dot": "orange", + "title": "Async Trace", + "items": [ + "Trace emission is dashed and secondary", + "It does not block the response path", + "The diagram separates user-facing latency from observability" + ] + } + ] +} diff --git a/.agents/skills/archify/examples/dataflow-product-analytics.html b/.agents/skills/archify/examples/dataflow-product-analytics.html new file mode 100644 index 00000000..832c000e --- /dev/null +++ b/.agents/skills/archify/examples/dataflow-product-analytics.html @@ -0,0 +1,1522 @@ + + + + + + + Product Analytics Data Flow Diagram + + + + + + + + + + + +
+ +
+
+
+

Product Analytics Data Flow

+
+

Events, consent, PII isolation, warehouse sync, and downstream analytics

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + 01 / Sources + + + 02 / Ingest + + + 03 / Process + + + 04 / Store + + + 05 / Consume + + + + + + + + + + + + + + + + + Web App + browser SDK + events + + + + Mobile + iOS / Android + events + + + + Edge API + collector + TLS + + + + Consent Gate + policy filter + PII guard + + + + Event Stream + Kafka topic + ordered + + + + PII Vault + encrypted + restricted + + + + Warehouse + analytics tables + curated + + + + Feature Store + daily batch + derived + + + + Dashboards + product metrics + SQL + + + + ML Model + ranking job + features + + + + clickstream + user events + + app events + device events + + identity + consent + PII touch + + accepted events + append-only + + identity map + encrypted PII + + normalized facts + non-PII + + daily aggregates + batch + + metrics SQL + read-only + + feature vectors + derived + + restricted join + approved only + + + Legend + + primary data + + policy / PII + + async batch + + data store + +
+ + +
+
+
+
+

Primary Data Path

+
+
    +
  • • Events move left to right through source, ingest, process, store, and consume stages
  • +
  • • The hot path stays visually clear even with secondary batch flows
  • +
  • • Labels name data assets instead of generic API verbs
  • +
+
+ +
+
+
+

Sensitive Boundary

+
+
    +
  • • Consent and PII paths are styled as security flows
  • +
  • • PII lands in a restricted vault, separate from the analytics warehouse
  • +
  • • Restricted joins are visible without implying default access
  • +
+
+ +
+
+
+

Derived Consumers

+
+
    +
  • • Dashboards read curated facts from the warehouse
  • +
  • • Feature vectors are derived by batch from analytics tables
  • +
  • • Consumption paths stay distinct from collection and consent handling
  • +
+
+
+ + + +
+ + + + diff --git a/.agents/skills/archify/examples/lifecycle-agent-run.html b/.agents/skills/archify/examples/lifecycle-agent-run.html new file mode 100644 index 00000000..913dae3f --- /dev/null +++ b/.agents/skills/archify/examples/lifecycle-agent-run.html @@ -0,0 +1,1492 @@ + + + + + + + Agent Run Lifecycle Diagram + + + + + + + + + + + +
+ +
+
+
+

Agent Run Lifecycle

+
+

State machine for planning, tool execution, human approval, retries, and terminal outcomes

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + 01 / Lifecycle phases + + 02 / Interruptions + Recovery loop + + 03 / Terminal exits + + + + + + + + + + + + + + + 01 + Queued + request accepted + entry + + + + 02 + Planning + build task graph + model + + + + 03 + Executing + tool calls + work + + + + 04 + Reviewing + quality gate + check + + + + 05 + Completed + final response + done + + + + Needs Approval + human gate + pause + + + + Blocked + missing input + wait + + + + Failed + recoverable error + retryable + + + + Cancelled + user stopped + terminal + + + + Expired + timeout + terminal + + + + + + + + + + Legend + + active state + + waiting + + terminal success + + failure / exit + +
+ + +
+
+
+
+

Main Path

+
+
    +
  • • The run has five ordered phases from queue to completion
  • +
  • • The primary lifecycle is carried by one horizontal rail
  • +
  • • Completion is a phase, not a detached side box
  • +
+
+ +
+
+
+

Human + Input Gates

+
+
    +
  • • Approval pauses execution without ending the run
  • +
  • • Blocked waits for missing user input
  • +
  • • Wait states can resume back into planning or execution
  • +
+
+ +
+
+
+

Terminal + Recovery

+
+
    +
  • • Failed loops back while retry budget remains
  • +
  • • Cancelled and Expired are exits from the lifecycle
  • +
  • • Terminal exits do not point back into active execution
  • +
+
+
+ + + +
+ + + + diff --git a/.agents/skills/archify/examples/product-analytics.dataflow.json b/.agents/skills/archify/examples/product-analytics.dataflow.json new file mode 100644 index 00000000..761e46a9 --- /dev/null +++ b/.agents/skills/archify/examples/product-analytics.dataflow.json @@ -0,0 +1,256 @@ +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { + "title": "Product Analytics Data Flow", + "subtitle": "Events, consent, PII isolation, warehouse sync, and downstream analytics", + "output": "examples/dataflow-product-analytics.html", + "viewBox": [1080, 760] + }, + "stages": [ + { "label": "Sources" }, + { "label": "Ingest" }, + { "label": "Process" }, + { "label": "Store" }, + { "label": "Consume" } + ], + "nodes": [ + { + "id": "web", + "type": "frontend", + "label": "Web App", + "sublabel": "browser SDK", + "stage": 0, + "row": 0, + "tag": "events" + }, + { + "id": "mobile", + "type": "frontend", + "label": "Mobile", + "sublabel": "iOS / Android", + "stage": 0, + "row": 2, + "tag": "events" + }, + { "id": "edge", "type": "cloud", "label": "Edge API", "sublabel": "collector", "stage": 1, "row": 1, "tag": "TLS" }, + { + "id": "consent", + "type": "security", + "label": "Consent Gate", + "sublabel": "policy filter", + "stage": 2, + "row": 0, + "tag": "PII guard" + }, + { + "id": "stream", + "type": "messagebus", + "label": "Event Stream", + "sublabel": "Kafka topic", + "stage": 2, + "row": 2, + "tag": "ordered" + }, + { + "id": "pii", + "type": "security", + "label": "PII Vault", + "sublabel": "encrypted", + "stage": 3, + "row": 0, + "tag": "restricted" + }, + { + "id": "warehouse", + "type": "database", + "label": "Warehouse", + "sublabel": "analytics tables", + "stage": 3, + "row": 2, + "tag": "curated" + }, + { + "id": "features", + "type": "database", + "label": "Feature Store", + "sublabel": "daily batch", + "stage": 3, + "row": 4, + "tag": "derived" + }, + { + "id": "dashboard", + "type": "backend", + "label": "Dashboards", + "sublabel": "product metrics", + "stage": 4, + "row": 1, + "tag": "SQL" + }, + { + "id": "model", + "type": "backend", + "label": "ML Model", + "sublabel": "ranking job", + "stage": 4, + "row": 4, + "tag": "features" + } + ], + "flows": [ + { + "from": "web", + "to": "edge", + "label": "clickstream", + "classification": "user events", + "variant": "emphasis", + "fromSide": "right", + "toSide": "left", + "via": [ + [184, 157], + [184, 271] + ], + "labelAt": [204, 190] + }, + { + "from": "mobile", + "to": "edge", + "label": "app events", + "classification": "device events", + "variant": "default", + "fromSide": "right", + "toSide": "left", + "via": [ + [222, 385], + [222, 271] + ], + "labelAt": [220, 342] + }, + { + "from": "edge", + "to": "consent", + "label": "identity + consent", + "classification": "PII touch", + "variant": "security", + "fromSide": "top", + "toSide": "left", + "via": [ + [315, 112], + [450, 112], + [450, 157] + ], + "labelAt": [382, 100] + }, + { + "from": "edge", + "to": "stream", + "label": "accepted events", + "classification": "append-only", + "variant": "emphasis", + "fromSide": "right", + "toSide": "left", + "via": [ + [420, 271], + [420, 385] + ], + "labelAt": [438, 324] + }, + { + "from": "consent", + "to": "pii", + "label": "identity map", + "classification": "encrypted PII", + "variant": "security", + "route": "straight", + "labelAt": [638, 144] + }, + { + "from": "stream", + "to": "warehouse", + "label": "normalized facts", + "classification": "non-PII", + "variant": "emphasis", + "route": "straight", + "labelAt": [638, 372] + }, + { + "from": "warehouse", + "to": "features", + "label": "daily aggregates", + "classification": "batch", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "top", + "route": "straight", + "labelAt": [745, 496] + }, + { + "from": "warehouse", + "to": "dashboard", + "label": "metrics SQL", + "classification": "read-only", + "variant": "default", + "fromSide": "right", + "toSide": "left", + "via": [ + [852, 385], + [852, 271] + ], + "labelAt": [830, 326] + }, + { + "from": "features", + "to": "model", + "label": "feature vectors", + "classification": "derived", + "variant": "dashed", + "route": "straight", + "labelAt": [852, 598] + }, + { + "from": "pii", + "to": "dashboard", + "label": "restricted join", + "classification": "approved only", + "variant": "security", + "fromSide": "right", + "toSide": "top", + "via": [ + [878, 157], + [878, 212], + [960, 212] + ], + "labelAt": [880, 198] + } + ], + "cards": [ + { + "dot": "emerald", + "title": "Primary Data Path", + "items": [ + "Events move left to right through source, ingest, process, store, and consume stages", + "The hot path stays visually clear even with secondary batch flows", + "Labels name data assets instead of generic API verbs" + ] + }, + { + "dot": "rose", + "title": "Sensitive Boundary", + "items": [ + "Consent and PII paths are styled as security flows", + "PII lands in a restricted vault, separate from the analytics warehouse", + "Restricted joins are visible without implying default access" + ] + }, + { + "dot": "orange", + "title": "Derived Consumers", + "items": [ + "Dashboards read curated facts from the warehouse", + "Feature vectors are derived by batch from analytics tables", + "Consumption paths stay distinct from collection and consent handling" + ] + } + ] +} diff --git a/.agents/skills/archify/examples/sequence-cache-miss-request.html b/.agents/skills/archify/examples/sequence-cache-miss-request.html new file mode 100644 index 00000000..4b0c1d5c --- /dev/null +++ b/.agents/skills/archify/examples/sequence-cache-miss-request.html @@ -0,0 +1,1519 @@ + + + + + + + Cache Miss Request Sequence Diagram + + + + + + + + + + + +
+ +
+
+
+

Cache Miss Request Sequence

+
+

Frontend request path with auth, cache fallback, persistence, and async trace

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + Request + + + Fallback + + + Response + trace + + + + + + + + + + + + + + open page + + + + GET /dashboard + + + + verify JWT + + + + claims ok + + + + read cache + + + + miss + + + + query profile + metrics + + + + rows + + + + set cache + + + + emit trace + + + + 200 JSON + + + + render + + + + + + + + + + + + + + + + + + + User + browser session + + + + Web App + React UI + + + + API + request handler + + + + Auth + JWT verify + + + + Redis + cache + + + + Postgres + source of truth + + + + Trace + async event + + + Legend + + request + + return + + security + + async trace + +
+ + +
+
+
+
+

Happy Path

+
+
    +
  • • The main request is Web App -> API -> data source -> response
  • +
  • • Return messages are quieter than forward calls
  • +
  • • Activation bars make ownership duration visible
  • +
+
+ +
+
+
+

Policy + Fallback

+
+
    +
  • • JWT verification is colored as a security interaction
  • +
  • • Cache miss is visible without overpowering the main path
  • +
  • • Database access only appears after cache fallback
  • +
+
+ +
+
+
+

Async Trace

+
+
    +
  • • Trace emission is dashed and secondary
  • +
  • • It does not block the response path
  • +
  • • The diagram separates user-facing latency from observability
  • +
+
+
+ + + +
+ + + + diff --git a/.agents/skills/archify/examples/web-app-rendered.html b/.agents/skills/archify/examples/web-app-rendered.html new file mode 100644 index 00000000..30e6c138 --- /dev/null +++ b/.agents/skills/archify/examples/web-app-rendered.html @@ -0,0 +1,1491 @@ + + + + + + + Sample Web App Diagram + + + + + + + + + + + +
+ +
+
+
+

Sample Web App

+
+

Classic 3-tier SaaS on AWS — rendered by Archify

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + AWS Region: us-west-2 + + + sg-api :443/:8000 + + + + + + + + + + + + + + + + Users + Browser / Mobile + + + + Auth Provider + OAuth 2.0 + JWT + PKCE + + + + CloudFront + CDN + + + + Load Balancer + HTTPS :443 + + + + API Server + FastAPI :8000 + + + + Redis + cache :6379 + + + + PostgreSQL + primary :5432 + + + + S3 + static assets + OAI protected + + + + SQS + job queue + + + + Worker + async jobs + + + + HTTPS + + verify JWT + + + static + + + read-through + + SQL + + enqueue + + + + Legend + + External + + Security + + Cloud + + Backend + + Database + + Message bus + +
+ + +
+
+
+
+

Edge

+
+
    +
  • • CloudFront CDN fronts all traffic
  • +
  • • S3 serves static assets via OAI
  • +
+
+ +
+
+
+

Application

+
+
    +
  • • FastAPI behind an HTTPS load balancer
  • +
  • • Redis read-through cache
  • +
  • • Async work drained from SQS by a worker
  • +
+
+ +
+
+
+

Security

+
+
    +
  • • OAuth 2.0 with JWT + PKCE
  • +
  • • API + LB isolated in a security group
  • +
+
+
+ + + +
+ + + + diff --git a/.agents/skills/archify/examples/web-app.architecture.json b/.agents/skills/archify/examples/web-app.architecture.json new file mode 100644 index 00000000..43c0febd --- /dev/null +++ b/.agents/skills/archify/examples/web-app.architecture.json @@ -0,0 +1,142 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "Sample Web App", + "subtitle": "Classic 3-tier SaaS on AWS — rendered by Archify", + "output": "web-app-rendered.html" + }, + "components": [ + { + "id": "users", + "type": "external", + "label": "Users", + "sublabel": "Browser / Mobile", + "pos": [40, 300], + "size": [120, 60] + }, + { + "id": "auth", + "type": "security", + "label": "Auth Provider", + "sublabel": "OAuth 2.0", + "pos": [40, 110], + "size": [120, 64], + "tag": "JWT + PKCE" + }, + { "id": "cdn", "type": "cloud", "label": "CloudFront", "sublabel": "CDN", "pos": [250, 300], "size": [130, 60] }, + { + "id": "lb", + "type": "cloud", + "label": "Load Balancer", + "sublabel": "HTTPS :443", + "pos": [460, 300], + "size": [130, 60] + }, + { + "id": "api", + "type": "backend", + "label": "API Server", + "sublabel": "FastAPI :8000", + "pos": [670, 300], + "size": [130, 60] + }, + { + "id": "cache", + "type": "database", + "label": "Redis", + "sublabel": "cache :6379", + "pos": [670, 150], + "size": [130, 60] + }, + { + "id": "db", + "type": "database", + "label": "PostgreSQL", + "sublabel": "primary :5432", + "pos": [880, 300], + "size": [130, 60] + }, + { + "id": "s3", + "type": "cloud", + "label": "S3", + "sublabel": "static assets", + "pos": [250, 440], + "size": [130, 60], + "tag": "OAI protected" + }, + { + "id": "queue", + "type": "messagebus", + "label": "SQS", + "sublabel": "job queue", + "pos": [670, 440], + "size": [130, 60] + }, + { + "id": "worker", + "type": "backend", + "label": "Worker", + "sublabel": "async jobs", + "pos": [880, 440], + "size": [130, 60] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "AWS Region: us-west-2", + "wraps": ["cdn", "lb", "api", "cache", "db", "s3", "queue", "worker"] + }, + { "kind": "security-group", "label": "sg-api :443/:8000", "wraps": ["lb", "api"] } + ], + "connections": [ + { "from": "users", "to": "cdn", "label": "HTTPS", "variant": "emphasis" }, + { "from": "auth", "to": "api", "label": "verify JWT", "variant": "security", "fromSide": "right", "toSide": "top" }, + { "from": "cdn", "to": "lb" }, + { + "from": "cdn", + "to": "s3", + "label": "static", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "top", + "labelDy": 58 + }, + { "from": "lb", "to": "api" }, + { "from": "api", "to": "cache", "label": "read-through", "fromSide": "top", "toSide": "bottom", "labelDy": -68 }, + { "from": "api", "to": "db", "label": "SQL" }, + { + "from": "api", + "to": "queue", + "label": "enqueue", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "top", + "labelDy": 58 + }, + { "from": "queue", "to": "worker" } + ], + "cards": [ + { + "dot": "cyan", + "title": "Edge", + "items": ["CloudFront CDN fronts all traffic", "S3 serves static assets via OAI"] + }, + { + "dot": "emerald", + "title": "Application", + "items": [ + "FastAPI behind an HTTPS load balancer", + "Redis read-through cache", + "Async work drained from SQS by a worker" + ] + }, + { + "dot": "rose", + "title": "Security", + "items": ["OAuth 2.0 with JWT + PKCE", "API + LB isolated in a security group"] + } + ] +} diff --git a/.agents/skills/archify/examples/workflow-agent-tool-call-rendered.html b/.agents/skills/archify/examples/workflow-agent-tool-call-rendered.html new file mode 100644 index 00000000..9b58ecd7 --- /dev/null +++ b/.agents/skills/archify/examples/workflow-agent-tool-call-rendered.html @@ -0,0 +1,1540 @@ + + + + + + + Agent Tool Call Workflow Diagram + + + + + + + + + + + +
+ +
+
+
+

Agent Tool Call Workflow

+
+

Renderer-driven workflow prototype with lanes, anchored nodes, and orthogonal edges

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + 01 / User Interface + + + 02 / Agent Runtime + + + 03 / Policy Boundary + + + + EX / Exception Handling + + + 05 / Tool Execution + + + 06 / Observability + + + + + Intake + + + Plan + route + + + Execute + report + + + + Planning loop + + Tool work + + Human or policy stop + + + + + + + + + + + + + + + + + + User + asks for work + + + + Chat Surface + thread + files + + + + Final Reply + answer + changes + + + + Agent Planner + plan next step + context aware + + + + Tool Router + choose capability + + + + Approval Gate + scope + consent + block risky ops + + + + Blocked + wait or reject + + + + Retry Path + revise request + + + + Tool Call + shell / browser / MCP + structured result + + + + External API + network service + + + + Context Store + repo + memory + + + + Trace Log + events + output + + + + + plan + + + needs approval? + + + denied + + + + + record result + + trace + memory + + + Legend + + User UI + + Agent logic + + Policy + + Tool action + + Context / trace + +
+ + +
+
+
+
+

Renderer Rules

+
+
    +
  • • Lanes and columns determine node placement
  • +
  • • Edges attach to explicit node anchors
  • +
  • • Cross-lane paths use orthogonal routing
  • +
  • • Short adjacent links stay unlabeled
  • +
+
+ +
+
+
+

Workflow Semantics

+
+
    +
  • • Approval is a first-class policy step
  • +
  • • Consent gates are visible in the main path
  • +
  • • External calls stay inside the tool lane
  • +
  • • Trace writes are separate from the hot path
  • +
+
+ +
+
+
+

Why It Matters

+
+
    +
  • • This is closer to a diagram_type renderer
  • +
  • • The graph can be edited without SVG surgery
  • +
  • • Layout rules can be tested and improved
  • +
  • • A future IR can reuse this shape directly
  • +
+
+
+ + + +
+ + + + diff --git a/.agents/skills/archify/package-lock.json b/.agents/skills/archify/package-lock.json new file mode 100644 index 00000000..8ab37918 --- /dev/null +++ b/.agents/skills/archify/package-lock.json @@ -0,0 +1,80 @@ +{ + "name": "archify", + "version": "2.11.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "archify", + "version": "2.11.0", + "license": "MIT", + "bin": { + "archify": "bin/archify.mjs" + }, + "devDependencies": { + "ajv": "^8.17.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", + "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + } + } +} diff --git a/.agents/skills/archify/package.json b/.agents/skills/archify/package.json new file mode 100644 index 00000000..d1a43b48 --- /dev/null +++ b/.agents/skills/archify/package.json @@ -0,0 +1,23 @@ +{ + "name": "archify", + "version": "2.11.0", + "private": true, + "description": "JSON-IR diagram renderers (architecture / workflow / sequence / dataflow / lifecycle).", + "license": "MIT", + "bin": { + "archify": "./bin/archify.mjs" + }, + "type": "module", + "scripts": { + "generate:validators": "node scripts/generate-validators.mjs", + "check:validators": "node scripts/generate-validators.mjs --check", + "test": "npm run check:validators && node test/golden.mjs && node --test test/*.test.mjs", + "render:examples": "node scripts/render-examples.mjs" + }, + "devDependencies": { + "ajv": "^8.17.1" + }, + "engines": { + "node": ">=18" + } +} diff --git a/.agents/skills/archify/renderers/architecture/grid.mjs b/.agents/skills/archify/renderers/architecture/grid.mjs new file mode 100644 index 00000000..8105a66d --- /dev/null +++ b/.agents/skills/archify/renderers/architecture/grid.mjs @@ -0,0 +1,62 @@ +/** Grid placement for architecture IR (#8). Not auto-layout — fixed cell math only. */ + +export const DEFAULT_GRID = { + mode: 'grid', + origin: [40, 80], + cols: 4, + gapX: 30, + gapY: 40, + cellW: 130, + cellH: 64 +} + +export function gridLayout(arch) { + const raw = arch.layout + if (!raw || raw.mode !== 'grid') return null + return { ...DEFAULT_GRID, ...raw } +} + +export function resolveComponentPos(component, grid) { + if (Array.isArray(component.pos) && component.pos.length === 2) { + return component.pos + } + if (!grid) return [NaN, NaN] + if (!Number.isInteger(component.row) || !Number.isInteger(component.col)) { + return [NaN, NaN] + } + const [ox, oy] = grid.origin + const stepX = grid.cellW + grid.gapX + const stepY = grid.cellH + grid.gapY + return [ox + component.col * stepX, oy + component.row * stepY] +} + +export function validateGridPlacement(arch, grid, problems) { + if (!grid) return + if (arch.layout !== undefined && arch.layout.mode !== 'grid') { + problems.push('layout.mode must be "grid" when layout is set (free placement omits layout entirely).') + return + } + const seen = new Map() + for (const c of arch.components ?? []) { + const hasPos = Array.isArray(c.pos) && c.pos.length === 2 + const hasCell = Number.isInteger(c.row) && Number.isInteger(c.col) + if (hasPos) continue // pos wins; row/col are optional hints only + if (!hasPos && !hasCell) { + problems.push(`Component "${c.id}" needs pos [x,y] or grid row/col when layout.mode is "grid".`) + continue + } + if (c.row < 0 || c.col < 0) { + problems.push(`Component "${c.id}" row/col must be non-negative integers.`) + continue + } + if (c.col >= grid.cols) { + problems.push(`Component "${c.id}" col ${c.col} exceeds layout.cols ${grid.cols} (valid: 0..${grid.cols - 1}).`) + } + const key = `${c.row},${c.col}` + if (seen.has(key)) { + problems.push(`Components "${seen.get(key)}" and "${c.id}" share grid cell row ${c.row} col ${c.col}.`) + } else { + seen.set(key, c.id) + } + } +} diff --git a/.agents/skills/archify/renderers/architecture/render-architecture.mjs b/.agents/skills/archify/renderers/architecture/render-architecture.mjs new file mode 100644 index 00000000..d657902f --- /dev/null +++ b/.agents/skills/archify/renderers/architecture/render-architecture.mjs @@ -0,0 +1,411 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs' +import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs' +import { componentBox, boundaryBox, connectionPath } from '../shared/layout-report.mjs' +import { gridLayout, resolveComponentPos, validateGridPlacement } from './grid.mjs' +import { + asArray, + isFinitePoint, + rectsOverlap, + suggestLabelObstacleFix, + suggestComponentSeparation, + anchor, + defaultFromSide, + defaultToSide, + chosenSide, + roundedPath, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const layoutJsonMode = process.argv.includes('--layout-json') +const cliArgs = process.argv.filter((arg) => arg !== '--layout-json') +const { + diagram: arch, + template, + outPath +} = loadDiagram({ + rendererDir: __dirname, + diagramType: 'architecture', + defaultExample: 'web-app.architecture.json', + argv: cliArgs +}) + +const grid = gridLayout(arch) + +const layout = { + defaultW: 120, + defaultH: 60, + margin: 40, + // Boundary padding — the 30/50 rule that was a hand-arithmetic footgun + // (CHANGELOG v2.2.1): 30px on top/left/right, plus 20px extra at the bottom. + boundaryPad: 30, + boundaryExtraBottom: 20, + legendH: 28 +} + +// ---- Measure components from free coordinates -------------------------------- +function measureComponent(c) { + const [x, y] = resolveComponentPos(c, grid) + const [w, h] = Array.isArray(c.size) ? c.size : [layout.defaultW, layout.defaultH] + return { ...c, x, y, width: w, height: h, cx: x + w / 2, cy: y + h / 2 } +} + +const components = new Map(asArray(arch.components).map((c) => [c.id, measureComponent(c)])) +const componentSteps = new Map() +for (const [index, conn] of asArray(arch.connections).entries()) { + if (!componentSteps.has(conn.from)) componentSteps.set(conn.from, index) + if (!componentSteps.has(conn.to)) componentSteps.set(conn.to, index + 1) +} +for (const [index, c] of asArray(arch.components).entries()) { + if (!componentSteps.has(c.id)) componentSteps.set(c.id, index) +} + +// ---- Boundaries computed from the `wraps` id list --------------------------- +function boundaryRect(boundary) { + const members = asArray(boundary.wraps) + .map((id) => components.get(id)) + .filter(Boolean) + if (!members.length) return null + const minX = Math.min(...members.map((m) => m.x)) + const minY = Math.min(...members.map((m) => m.y)) + const maxX = Math.max(...members.map((m) => m.x + m.width)) + const maxY = Math.max(...members.map((m) => m.y + m.height)) + const pad = boundary.pad ?? layout.boundaryPad + return { + ...boundary, + x: minX - pad, + y: minY - pad, + width: maxX - minX + pad * 2, + height: maxY - minY + pad + layout.boundaryExtraBottom + } +} + +const boundaries = asArray(arch.boundaries).map(boundaryRect).filter(Boolean) + +// ---- Auto viewBox: fit all geometry + a legend row -------------------------- +function autoViewBox() { + let maxX = 0 + let maxY = 0 + for (const c of components.values()) { + maxX = Math.max(maxX, c.x + c.width) + maxY = Math.max(maxY, c.y + c.height) + } + for (const b of boundaries) { + maxX = Math.max(maxX, b.x + b.width) + maxY = Math.max(maxY, b.y + b.height) + } + return [Math.ceil(maxX + layout.margin), Math.ceil(maxY + layout.margin + layout.legendH)] +} + +const viewBox = arch.meta?.viewBox || autoViewBox() +const legendY = () => viewBox[1] - 16 + +// ---- Validation: mechanical correctness, never layout taste ----------------- +function validateArchitecture() { + const problems = [] + if (arch.schema_version !== 1) problems.push('Architecture files must set "schema_version": 1.') + if (arch.diagram_type !== 'architecture') problems.push('Architecture files must set "diagram_type": "architecture".') + if (!arch.meta?.title) problems.push('Architecture files must include meta.title.') + if (!Array.isArray(arch.components) || arch.components.length < 1) { + problems.push('Architecture diagrams need at least one component.') + } + if (arch.connections !== undefined && !Array.isArray(arch.connections)) + problems.push('Architecture "connections" must be an array.') + if (arch.boundaries !== undefined && !Array.isArray(arch.boundaries)) + problems.push('Architecture "boundaries" must be an array.') + if (arch.cards !== undefined && !Array.isArray(arch.cards)) problems.push('Architecture "cards" must be an array.') + if (components.size !== asArray(arch.components).length) problems.push('Component ids must be unique.') + if (grid) { + validateGridPlacement(arch, grid, problems) + } else { + for (const c of asArray(arch.components)) { + if (!Array.isArray(c.pos) || c.pos.length !== 2) { + problems.push(`Component "${c.id}" must include pos [x, y] when layout.mode is omitted (free placement).`) + } + } + } + + for (const c of components.values()) { + if (!isFinitePoint(c.x, c.y, c.width, c.height)) { + problems.push(`Component "${c.id}" has non-finite pos/size — pos and size must be [number, number].`) + continue + } + if (c.width <= 0 || c.height <= 0) { + problems.push( + `Component "${c.id}" has invalid size ${c.width}x${c.height} — width and height must be greater than 0.` + ) + continue + } + if (c.x < 0 || c.y < 0 || c.x + c.width > viewBox[0] || c.y + c.height > viewBox[1]) { + problems.push( + `Component "${c.id}" falls outside the viewBox ${viewBox[0]}x${viewBox[1]} — adjust pos/size or set a larger meta.viewBox.` + ) + } + const estLabelW = textUnits(c.label) * 6.6 + if (estLabelW > c.width + 8) { + problems.push( + `Label "${c.label}" (~${Math.round(estLabelW)}px) is wider than component "${c.id}" (${c.width}px) — shorten the label, move detail to sublabel, or widen size.` + ) + } + } + + // Component overlap — the highest-traffic hand-placement failure mode. + const list = [...components.values()] + for (let i = 0; i < list.length; i += 1) { + for (let j = i + 1; j < list.length; j += 1) { + if (rectsOverlap(list[i], list[j], 8)) { + problems.push( + `Components "${list[i].id}" and "${list[j].id}" are less than 8px apart — move one or shrink its size.\n${suggestComponentSeparation(list[i], list[j], 8)}` + ) + } + } + } + + // Boundaries: every wrapped id must exist; the computed box must stay in view. + for (const boundary of asArray(arch.boundaries)) { + for (const id of asArray(boundary.wraps)) { + if (!components.has(id)) problems.push(`Boundary "${boundary.label}" wraps unknown component "${id}".`) + } + } + for (const b of boundaries) { + if (b.x < 0 || b.y < 0 || b.x + b.width > viewBox[0] || b.y + b.height > viewBox[1]) { + problems.push( + `Boundary "${b.label}" extends outside the viewBox — its members sit too close to the canvas edge; add margin or enlarge meta.viewBox.` + ) + } + } + + for (const conn of asArray(arch.connections)) { + if (!components.has(conn.from)) + problems.push(`Connection "${conn.label || conn.from}" references unknown source "${conn.from}".`) + if (!components.has(conn.to)) + problems.push(`Connection "${conn.label || conn.to}" references unknown target "${conn.to}".`) + if (components.has(conn.from) && components.has(conn.to)) { + const routed = pathFor(conn) + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]] + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]) + if (distance < 24) + problems.push( + `Connection "${conn.label || `${conn.from}->${conn.to}`}" is too short (${Math.round(distance)}px; minimum 24px) — place its components farther apart.` + ) + } + } + + // Connection labels must not land on top of components. + const labelRects = [] + for (const conn of asArray(arch.connections)) { + if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue + const [lx, ly] = labelPoint(conn, pathFor(conn).points) + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10) + labelRects.push({ label: conn.label, x: lx - w / 2, y: ly - 10, width: w, height: 14, lx, ly }) + } + for (const rect of labelRects) { + for (const c of components.values()) { + if (rectsOverlap(rect, c, -2)) { + problems.push( + `Label "${rect.label}" overlaps component "${c.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, c)}` + ) + } + } + } + + if (problems.length) { + throw new Error(`Architecture layout validation failed:\n- ${problems.join('\n- ')}`) + } +} + +function buildLayoutReport() { + const labels = [] + for (const conn of asArray(arch.connections)) { + if (!conn.label || !components.has(conn.from) || !components.has(conn.to)) continue + const [lx, ly] = labelPoint(conn, pathFor(conn).points) + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10) + labels.push({ + text: conn.label, + x: Math.round(lx - w / 2), + y: Math.round(ly - 10), + width: Math.round(w), + height: 14, + labelAt: [Math.round(lx), Math.round(ly)] + }) + } + return { + ok: true, + diagram_type: 'architecture', + layout: grid ? { mode: 'grid', ...grid } : { mode: 'free' }, + viewBox, + components: [...components.values()].map(componentBox), + boundaries: boundaries.map(boundaryBox), + connections: asArray(arch.connections) + .filter((conn) => components.has(conn.from) && components.has(conn.to)) + .map((conn) => { + const routed = pathFor(conn) + const labelAt = conn.label ? labelPoint(conn, routed.points) : null + return connectionPath(conn, routed, labelAt) + }), + labels + } +} + +// ---- Connection routing ------------------------------------------------------ +function routeVia(conn, from, to, start, end) { + if (conn.via) return conn.via + switch (conn.route || 'auto') { + case 'straight': + return [] + case 'orthogonal-h': { + const midX = (start[0] + end[0]) / 2 + return [ + [midX, start[1]], + [midX, end[1]] + ] + } + case 'orthogonal-v': { + const midY = (start[1] + end[1]) / 2 + return [ + [start[0], midY], + [end[0], midY] + ] + } + case 'auto': + default: { + // Direct line unless the anchors are clearly orthogonal-friendly. + if (Math.abs(start[0] - end[0]) < 4 || Math.abs(start[1] - end[1]) < 4) return [] + const midX = (start[0] + end[0]) / 2 + return [ + [midX, start[1]], + [midX, end[1]] + ] + } + } +} + +const pathCache = new Map() +function pathFor(conn) { + if (pathCache.has(conn)) return pathCache.get(conn) + const from = components.get(conn.from) + const to = components.get(conn.to) + const start = anchor(from, chosenSide(conn.fromSide, defaultFromSide(from, to))) + const end = anchor(to, chosenSide(conn.toSide, defaultToSide(from, to))) + const points = [start, ...routeVia(conn, from, to, start, end), end] + const routed = { d: roundedPath(points, 8), points } + pathCache.set(conn, routed) + return routed +} + +// ---- Rendering --------------------------------------------------------------- +function renderBoundary(b) { + const cls = b.kind === 'security-group' ? 'c-security-group' : 'c-region' + const labelCls = b.kind === 'security-group' ? 't-security' : 't-cloud' + const rx = b.kind === 'security-group' ? 8 : 12 + return ` + ${esc(b.label)}` +} + +function renderConnectionPath(conn, index) { + const [cls, marker] = arrowClassMap[conn.variant || 'default'] || arrowClassMap.default + const routed = pathFor(conn) + const strokeWidth = conn.width || (conn.variant === 'emphasis' ? 1.8 : 1.5) + return ` ` +} + +function renderConnectionLabel(conn) { + if (!conn.label) return '' + const [lx, ly] = labelPoint(conn, pathFor(conn).points) + const w = Math.max(30, textUnits(conn.label) * 4.8 + 10) + return ` + ${esc(conn.label)}` +} + +function renderComponent(c) { + const fill = componentFill[c.type] || 'c-external' + const accent = componentText[c.type] || 't-muted' + const cx = c.cx + const hasSub = c.sublabel != null && c.sublabel !== '' + const labelY = hasSub ? c.y + c.height / 2 - 2 : c.y + c.height / 2 + 4 + const sub = hasSub + ? `\n ${esc(c.sublabel)}` + : '' + const tag = c.tag + ? `\n ${esc(c.tag)}` + : '' + return ` + + ${esc(c.label)}${sub}${tag}` +} + +// Auto legend: one swatch per component type actually used, left to right. +const TYPE_LABELS = { + frontend: 'Frontend', + backend: 'Backend', + database: 'Database', + cloud: 'Cloud', + security: 'Security', + messagebus: 'Message bus', + external: 'External' +} +function renderLegend() { + const used = [] + const seen = new Set() + for (const c of components.values()) { + if (!seen.has(c.type)) { + seen.add(c.type) + used.push(c.type) + } + } + const y = legendY() + let x = layout.margin + const parts = [` Legend`] + for (const type of used) { + parts.push( + ` ` + ) + parts.push(` ${TYPE_LABELS[type] || type}`) + x += 30 + (textUnits(TYPE_LABELS[type] || type) * 5 + 28) + } + return parts.join('\n') +} + +function renderSvg() { + return ` +${renderDefinitions()} + + + + + +${boundaries.map(renderBoundary).join('\n\n')} + + +${asArray(arch.connections).map(renderConnectionPath).join('\n')} + + +${[...components.values()].map(renderComponent).join('\n\n')} + + +${asArray(arch.connections).map(renderConnectionLabel).join('\n')} + + +${renderLegend()} + ` +} + +validateArchitecture() +if (layoutJsonMode) { + console.log(JSON.stringify(buildLayoutReport(), null, 2)) + process.exit(0) +} +writeDiagram({ + outPath, + template, + meta: arch.meta, + footerLabel: 'Architecture diagram', + svg: renderSvg(), + cards: arch.cards +}) diff --git a/.agents/skills/archify/renderers/dataflow/README.md b/.agents/skills/archify/renderers/dataflow/README.md new file mode 100644 index 00000000..1ab830c3 --- /dev/null +++ b/.agents/skills/archify/renderers/dataflow/README.md @@ -0,0 +1,82 @@ +# Data Flow Renderer + +Render `diagram_type: "dataflow"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/dataflow/render-dataflow.mjs input.dataflow.json output.html +``` + +The renderer validates input against `archify/schemas/dataflow.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `dataflow.html` in the current working directory. + +## Input + +Data-flow JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "dataflow", + "meta": { + "title": "Product Analytics Data Flow", + "subtitle": "Events, consent, PII isolation, warehouse sync, and consumers", + "viewBox": [940, 720] + }, + "stages": [], + "nodes": [], + "flows": [], + "cards": [] +} +``` + +A complete worked example lives at +`archify/examples/product-analytics.dataflow.json`. + +The schema lives at: + +```text +archify/schemas/dataflow.schema.json +``` + +## Layout budget + +| Constant | Value | +| -------------------- | ------------------------------------------------------------------- | +| viewBox | default `[940, 720]`; schema minimum `[360, 360]` | +| Stages (2–5) | centers at x = 100 + stage×215; stage band 168 wide, header at y 46 | +| Row tops (`row` 0–4) | y = 128, 242, 356, 470, 584 (plus `yOffset`) | +| Default node | 112×58 | +| Node area | x within `[24, width − 24]`; y within `[104, height − 74]` | +| Node spacing | ≥10px between any two nodes (checked across stages and rows) | +| Flow length | ≥34px between endpoints | +| Legend row | y = height − 36 | + +Route presets for flows: `straight`, `vertical-channel`, `bottom-channel`, +`top-channel`, explicit `via` points, or the default `auto` (midpoint elbow). + +## Design Rules + +- Use stages for data lifecycle boundaries: source, ingest, process, store, + consume. +- Place nodes by stage index and row index; do not hand-place raw SVG for the + common case. +- Use flow labels to name the data asset, not the transport primitive: + `clickstream`, `identity map`, `normalized facts`, `feature vectors`. +- Use `classification` for short sensitivity or governance context: + `PII touch`, `non-PII`, `approved only`, `batch`, `read-only`. +- Use `security` for PII, policy, consent, access-control, or restricted joins. +- Use `emphasis` for the primary data path and `dashed` for async or batch + derivations. +- Keep labels short enough to fit in narrow previews. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including missing stages, duplicate node IDs, nodes outside +the readable diagram area, node overlap, labels colliding with nodes or other +labels, labels wider than their node, unknown flow endpoints, missing flow +labels, unreadably short flows, or stages that exceed the viewBox. Text width +is estimated CJK-aware: fullwidth glyphs count as two units. diff --git a/.agents/skills/archify/renderers/dataflow/render-dataflow.mjs b/.agents/skills/archify/renderers/dataflow/render-dataflow.mjs new file mode 100644 index 00000000..e8a37daf --- /dev/null +++ b/.agents/skills/archify/renderers/dataflow/render-dataflow.mjs @@ -0,0 +1,332 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs' +import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs' +import { + asArray, + isFinitePoint, + rectsOverlap, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + defaultFromSide, + defaultToSide, + chosenSide, + polylinePath, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const { + diagram: dataflow, + template, + outPath +} = loadDiagram({ + rendererDir: __dirname, + diagramType: 'dataflow', + defaultExample: 'product-analytics.dataflow.json' +}) + +const viewBox = dataflow.meta?.viewBox || [940, 720] +const layout = { + stageY: 46, + stageH: 36, + stageBottomPad: 74, + leftX: 100, + colGap: 215, + stageW: 168, + nodeW: 112, + nodeH: 58, + rowYs: [128, 242, 356, 470, 584], + labelH: 16 +} + +function stageX(index) { + return layout.leftX + index * layout.colGap +} + +function measureNode(node) { + const width = node.width || layout.nodeW + const height = node.height || layout.nodeH + const cx = stageX(node.stage) + const y = layout.rowYs[node.row] + (node.yOffset || 0) + return { + ...node, + width, + height, + cx, + cy: y + height / 2, + x: cx - width / 2, + y + } +} + +const nodes = new Map(asArray(dataflow.nodes).map((node) => [node.id, measureNode(node)])) +const nodeSteps = new Map() +for (const [index, flow] of asArray(dataflow.flows).entries()) { + if (!nodeSteps.has(flow.from)) nodeSteps.set(flow.from, index) + if (!nodeSteps.has(flow.to)) nodeSteps.set(flow.to, index + 1) +} +for (const [index, node] of asArray(dataflow.nodes).entries()) { + if (!nodeSteps.has(node.id)) nodeSteps.set(node.id, index) +} + +function validateDataflow() { + const problems = [] + if (dataflow.schema_version !== 1) problems.push('Data-flow files must set "schema_version": 1.') + if (dataflow.diagram_type !== 'dataflow') problems.push('Data-flow files must set "diagram_type": "dataflow".') + if (!dataflow.meta?.title) problems.push('Data-flow files must include meta.title.') + if (!Array.isArray(dataflow.stages) || dataflow.stages.length < 2) { + problems.push('Data-flow diagrams need at least two stages.') + } + if (!Array.isArray(dataflow.nodes) || dataflow.nodes.length < 2) { + problems.push('Data-flow diagrams need at least two nodes.') + } + if (!Array.isArray(dataflow.flows)) problems.push('Data-flow diagrams must include a flows array.') + if (dataflow.cards !== undefined && !Array.isArray(dataflow.cards)) + problems.push('Data-flow "cards" must be an array.') + if (nodes.size !== asArray(dataflow.nodes).length) problems.push('Node ids must be unique.') + + const stageCount = asArray(dataflow.stages).length + for (const node of nodes.values()) { + if (typeof node.stage !== 'number' || node.stage < 0 || node.stage >= stageCount) { + problems.push(`Node "${node.id}" uses invalid stage ${node.stage} — valid stages are 0..${stageCount - 1}.`) + } + if (typeof node.row !== 'number' || node.row < 0 || node.row >= layout.rowYs.length) { + problems.push(`Node "${node.id}" uses invalid row ${node.row} — valid rows are 0..${layout.rowYs.length - 1}.`) + } + if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) { + problems.push( + `Node "${node.id}" produced non-finite coordinates — check stage, row, width, height, and yOffset are numbers.` + ) + continue + } + if (node.x < 24 || node.x + node.width > viewBox[0] - 24) { + problems.push( + `Node "${node.id}" exceeds the horizontal bounds of the viewBox — reduce node.width or increase meta.viewBox[0].` + ) + } + if (node.y < layout.stageY + layout.stageH + 22 || node.y + node.height > viewBox[1] - layout.stageBottomPad) { + problems.push( + `Node "${node.id}" exceeds the readable diagram area — keep y between ${layout.stageY + layout.stageH + 22} and ${viewBox[1] - layout.stageBottomPad} (adjust row/yOffset or increase meta.viewBox[1]).` + ) + } + const estLabelW = textUnits(node.label) * 6.2 + if (estLabelW > node.width + 6) { + problems.push( + `Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label, move detail to sublabel, or increase node.width.` + ) + } + } + + const nodeList = asArray(dataflow.nodes) + for (let i = 0; i < nodeList.length; i += 1) { + for (let j = i + 1; j < nodeList.length; j += 1) { + const a = nodes.get(nodeList[i].id) + const b = nodes.get(nodeList[j].id) + if (rectsOverlap(a, b, 10)) { + problems.push( + `Nodes "${a.id}" and "${b.id}" are less than 10px apart — move one to another stage/row or adjust yOffset.` + ) + } + } + } + + for (const flow of asArray(dataflow.flows)) { + if (!nodes.has(flow.from)) + problems.push(`Flow "${flow.label || flow.from}" references unknown source "${flow.from}".`) + if (!nodes.has(flow.to)) problems.push(`Flow "${flow.label || flow.to}" references unknown target "${flow.to}".`) + if (!flow.label) problems.push(`Flow "${flow.from}" -> "${flow.to}" must include a short data label.`) + if (nodes.has(flow.from) && nodes.has(flow.to)) { + const routed = pathFor(flow) + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]] + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]) + if (distance < 34) + problems.push( + `Flow "${flow.label}" is too short (${Math.round(distance)}px; minimum 34px) — route it through a channel or spread its nodes.` + ) + } + } + + const labelRects = [] + for (const flow of asArray(dataflow.flows)) { + if (!flow.label || !nodes.has(flow.from) || !nodes.has(flow.to)) continue + const [lx, ly] = labelPoint(flow, pathFor(flow).points) + const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || '')) + const width = Math.max(34, longestLine * 4.9 + 12) + const height = flow.classification ? 27 : layout.labelH + labelRects.push({ label: flow.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly }) + } + for (const rect of labelRects) { + for (const node of nodes.values()) { + if (rectsOverlap(rect, node, -2)) { + problems.push( + `Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}` + ) + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push( + `Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}` + ) + } + } + } + + const lastStageX = stageX(asArray(dataflow.stages).length - 1) + if (lastStageX + layout.stageW / 2 > viewBox[0] - 24) { + problems.push( + `Stages exceed viewBox width — set meta.viewBox[0] to at least ${Math.ceil(lastStageX + layout.stageW / 2 + 24)}.` + ) + } + + if (problems.length) { + throw new Error(`Data-flow layout validation failed:\n- ${problems.join('\n- ')}`) + } +} + +function routeVia(flow, from, to, start, end) { + if (flow.via) return flow.via + switch (flow.route || 'auto') { + case 'straight': + return [] + case 'vertical-channel': { + const x = flow.channelX ?? start[0] + (end[0] > start[0] ? 44 : -44) + return [ + [x, start[1]], + [x, end[1]] + ] + } + case 'bottom-channel': { + const y = flow.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 26 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'top-channel': { + const y = flow.channelY ?? Math.min(from.y, to.y) - 24 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'auto': + default: { + if (Math.abs(start[1] - end[1]) < 4) return [] + const midX = start[0] + (end[0] - start[0]) / 2 + return [ + [midX, start[1]], + [midX, end[1]] + ] + } + } +} + +const pathCache = new Map() + +function pathFor(flow) { + if (pathCache.has(flow)) return pathCache.get(flow) + const from = nodes.get(flow.from) + const to = nodes.get(flow.to) + const start = anchor(from, chosenSide(flow.fromSide, defaultFromSide(from, to))) + const end = anchor(to, chosenSide(flow.toSide, defaultToSide(from, to))) + const points = [start, ...routeVia(flow, from, to, start, end), end] + const routed = { d: polylinePath(points), points } + pathCache.set(flow, routed) + return routed +} + +function renderStage(stage, index) { + const cx = stageX(index) + const x = cx - layout.stageW / 2 + const h = viewBox[1] - layout.stageY - layout.stageBottomPad + return ` + ${String(index + 1).padStart(2, '0')} / ${esc(stage.label)}` +} + +function renderNode(node) { + const fill = componentFill[node.type] || 'c-external' + const accent = componentText[node.type] || 't-muted' + const tag = node.tag + ? `\n ${esc(node.tag)}` + : '' + return ` + + ${esc(node.label)} + ${esc(node.sublabel || '')}${tag}` +} + +function renderFlowPath(flow, index) { + const [cls, marker] = arrowClassMap[flow.variant || 'default'] || arrowClassMap.default + const routed = pathFor(flow) + const strokeWidth = flow.width || (flow.variant === 'emphasis' ? 1.8 : 1.4) + return ` ` +} + +function renderFlowLabel(flow) { + const routed = pathFor(flow) + const [lx, ly] = labelPoint(flow, routed.points) + const longestLine = Math.max(textUnits(flow.label), textUnits(flow.classification || '')) + const labelW = Math.max(34, longestLine * 4.9 + 12) + const classification = flow.classification + ? `\n ${esc(flow.classification)}` + : '' + const labelH = flow.classification ? 27 : layout.labelH + return ` + ${esc(flow.label)}${classification}` +} + +function renderLegend() { + const y = viewBox[1] - 36 + return ` Legend + + primary data + + policy / PII + + async batch + + data store` +} + +function renderSvg() { + return ` +${renderDefinitions()} + + + + + +${dataflow.stages.map(renderStage).join('\n\n')} + + +${asArray(dataflow.flows).map(renderFlowPath).join('\n')} + + +${[...nodes.values()].map(renderNode).join('\n\n')} + + +${asArray(dataflow.flows).map(renderFlowLabel).join('\n')} + + +${renderLegend()} + ` +} + +validateDataflow() +writeDiagram({ + outPath, + template, + meta: dataflow.meta, + footerLabel: 'Data-flow diagram', + svg: renderSvg(), + cards: dataflow.cards +}) diff --git a/.agents/skills/archify/renderers/lifecycle/README.md b/.agents/skills/archify/renderers/lifecycle/README.md new file mode 100644 index 00000000..e966774f --- /dev/null +++ b/.agents/skills/archify/renderers/lifecycle/README.md @@ -0,0 +1,91 @@ +# Lifecycle Renderer + +Render `diagram_type: "lifecycle"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/lifecycle/render-lifecycle.mjs input.lifecycle.json output.html +``` + +The renderer validates input against `archify/schemas/lifecycle.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `lifecycle.html` in the current working directory. + +## Input + +Lifecycle JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "lifecycle", + "meta": { + "title": "Agent Run Lifecycle", + "subtitle": "Lifecycle phases, interruptions, recovery, and terminal exits", + "viewBox": [980, 660] + }, + "lanes": [], + "states": [], + "transitions": [], + "cards": [] +} +``` + +Lane ids are semantic and reserved: a lane with id `main` is required and maps +to the top phase band; `terminal` maps to the bottom outcome band; every other +lane id (up to 4 lanes total) shares the single middle event band. The three +band headers render from your lane labels — the middle band joins the labels of +all event lanes with `+`. A complete worked example lives at +`archify/examples/agent-run.lifecycle.json`. + +The schema lives at: + +```text +archify/schemas/lifecycle.schema.json +``` + +## Layout budget + +| Band | Lane id | Top y | Column centers | Default state | +| ------- | ----------------- | ----- | -------------------------------------- | ------------- | +| Phase | `main` (required) | 126 | `col` 0–4 → x = 94, 248, 402, 556, 710 | 118×62 | +| Event | any other id | 278 | `col` 0–2 → x = 402, 556, 710 | 126×58 | +| Outcome | `terminal` | 450 | `col` 0–2 → x = 402, 556, 710 | 118×58 | + +| Constant | Value | +| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| viewBox | default `[980, 660]`; schema minimum `[420, 360]` | +| State area | x within `[32, width − 32]`; y within `[64, legend y − 24]` | +| State spacing | ≥10px between any two states — checked across lanes, because all event lanes share one band; separate same-band states with `col` or `yOffset` | +| Transition length | ≥32px between endpoints | +| Legend row | y = height − 98 | + +The primary lifecycle rail runs along the phase band and extends to the +furthest occupied phase column. Route presets for transitions: `straight`, +`drop` (bend at `channelY`, defaulting to the vertical midpoint), +`bottom-channel`, `top-channel`, `right-channel`, `left-channel`, explicit +`via` points, or the default `auto`. Multi-segment transitions get rounded +corners; tune them with `cornerRadius` (default 10, `0` for sharp bends). + +## Design Rules + +- Treat lifecycle diagrams as a phase map, not a dense state-transition graph. +- Put the primary lifecycle on one horizontal rail using the `main` lane. +- Use `step` labels for ordered phases, such as `01`, `02`, and `03`. +- Use lower lanes only for interruptions, recovery, and terminal exits. +- Keep transition labels out of the main SVG unless the label is essential; + prefer node labels, tags, legend entries, and summary cards. +- Avoid diagonal and crossing lines. Terminal exits should drop vertically from + their source event whenever possible. +- Use `success` for completion, `failure` for failure/terminal exits, + `waiting` for pauses, and `decision` for quality gates. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including a missing `main` lane, duplicate state IDs, unknown +lanes, unknown transition endpoints, states outside the lifecycle area, +overlapping states (including across lanes), labels colliding with states or +other labels, labels wider than their state, or unreadably short transitions. +Text width is estimated CJK-aware: fullwidth glyphs count as two units. diff --git a/.agents/skills/archify/renderers/lifecycle/render-lifecycle.mjs b/.agents/skills/archify/renderers/lifecycle/render-lifecycle.mjs new file mode 100644 index 00000000..918b2991 --- /dev/null +++ b/.agents/skills/archify/renderers/lifecycle/render-lifecycle.mjs @@ -0,0 +1,427 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs' +import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs' +import { + asArray, + isFinitePoint, + rectsOverlap, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + defaultFromSide, + defaultToSide, + chosenSide, + roundedPath, + labelPoint, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const { + diagram: lifecycle, + template, + outPath +} = loadDiagram({ + rendererDir: __dirname, + diagramType: 'lifecycle', + defaultExample: 'agent-run.lifecycle.json' +}) + +const viewBox = lifecycle.meta?.viewBox || [980, 660] +const layout = { + phaseY: 126, + eventY: 278, + outcomeY: 450, + phaseW: 118, + phaseH: 62, + eventW: 126, + eventH: 58, + outcomeW: 118, + outcomeH: 58, + phaseXs: [94, 248, 402, 556, 710], + eventXs: [402, 556, 710], + outcomeXs: [402, 556, 710] +} + +const typeClass = { + start: 'c-frontend', + active: 'c-backend', + waiting: 'c-cloud', + decision: 'c-security', + success: 'c-database', + failure: 'c-security', + neutral: 'c-external', + external: 'c-external' +} + +const textClass = { + start: 't-frontend', + active: 't-backend', + waiting: 't-cloud', + decision: 't-security', + success: 't-database', + failure: 't-security', + neutral: 't-muted', + external: 't-muted' +} + +function legendY() { + return viewBox[1] - 98 +} + +// Lane semantics are fixed: lane id "main" maps to the top phase band, lane id +// "terminal" maps to the bottom outcome band, and every other lane shares the +// middle event band (separated visually via yOffset). +function bandFor(lane) { + if (lane === 'main') return 'phase' + if (lane === 'terminal') return 'outcome' + return 'event' +} + +function measureState(state) { + const isPhase = bandFor(state.lane) === 'phase' + const isOutcome = bandFor(state.lane) === 'outcome' + const width = state.width || (isPhase ? layout.phaseW : isOutcome ? layout.outcomeW : layout.eventW) + const height = state.height || (isPhase ? layout.phaseH : isOutcome ? layout.outcomeH : layout.eventH) + const xs = isPhase ? layout.phaseXs : isOutcome ? layout.outcomeXs : layout.eventXs + const cx = xs[state.col] ?? xs[xs.length - 1] + const y = (isPhase ? layout.phaseY : isOutcome ? layout.outcomeY : layout.eventY) + (state.yOffset || 0) + return { + ...state, + width, + height, + x: cx - width / 2, + y, + cx, + cy: y + height / 2 + } +} + +const states = new Map(asArray(lifecycle.states).map((state) => [state.id, measureState(state)])) +const stateSteps = new Map() +for (const [index, transition] of asArray(lifecycle.transitions).entries()) { + if (!stateSteps.has(transition.from)) stateSteps.set(transition.from, index) + if (!stateSteps.has(transition.to)) stateSteps.set(transition.to, index + 1) +} +for (const [index, state] of asArray(lifecycle.states).entries()) { + if (!stateSteps.has(state.id)) stateSteps.set(state.id, index) +} + +function validateLifecycle() { + const problems = [] + if (lifecycle.schema_version !== 1) problems.push('Lifecycle files must set "schema_version": 1.') + if (lifecycle.diagram_type !== 'lifecycle') problems.push('Lifecycle files must set "diagram_type": "lifecycle".') + if (!lifecycle.meta?.title) problems.push('Lifecycle files must include meta.title.') + if (!Array.isArray(lifecycle.lanes) || lifecycle.lanes.length < 1) + problems.push('Lifecycle diagrams need at least one lane.') + if (!Array.isArray(lifecycle.states) || lifecycle.states.length < 2) + problems.push('Lifecycle diagrams need at least two states.') + if (!Array.isArray(lifecycle.transitions)) problems.push('Lifecycle diagrams must include a transitions array.') + if (lifecycle.cards !== undefined && !Array.isArray(lifecycle.cards)) + problems.push('Lifecycle "cards" must be an array.') + if (states.size !== asArray(lifecycle.states).length) problems.push('State ids must be unique.') + + // The three bands are fixed at y=112/264/436; the legend (viewBox[1] - 98) + // must clear the outcome band's header zone. + if (legendY() - 20 < 448) { + problems.push( + `viewBox height ${viewBox[1]} is too short for the fixed band layout — set meta.viewBox[1] to at least 566.` + ) + } + + const laneIds = new Set(asArray(lifecycle.lanes).map((lane) => lane.id)) + if (laneIds.size !== asArray(lifecycle.lanes).length) problems.push('Lane ids must be unique.') + if (!laneIds.has('main')) { + problems.push( + 'Lifecycle diagrams need a lane with id "main" (the phase rail). Lane ids "main" and "terminal" are reserved: "main" maps to the top phase band, "terminal" to the bottom outcome band, and all other lanes share the middle event band.' + ) + } + + for (const state of states.values()) { + if (!laneIds.has(state.lane)) { + problems.push(`State "${state.id}" uses unknown lane "${state.lane}".`) + continue + } + const band = bandFor(state.lane) + const maxCol = + band === 'phase' ? layout.phaseXs.length : band === 'outcome' ? layout.outcomeXs.length : layout.eventXs.length + if (!Number.isInteger(state.col) || state.col < 0 || state.col >= maxCol) { + problems.push( + `State "${state.id}" uses invalid column ${state.col} — the ${band} band has integer columns 0..${maxCol - 1}.` + ) + continue + } + if (!isFinitePoint(state.x, state.y, state.cx, state.cy)) { + problems.push( + `State "${state.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.` + ) + continue + } + if (state.x < 32 || state.x + state.width > viewBox[0] - 32) { + problems.push( + `State "${state.id}" exceeds the horizontal bounds of the diagram — reduce state.width or increase meta.viewBox[0].` + ) + } + if (state.y < 64 || state.y + state.height > legendY() - 24) { + problems.push( + `State "${state.id}" exceeds the vertical lifecycle area — keep y between 64 and ${legendY() - 24} (adjust yOffset or increase meta.viewBox[1]).` + ) + } + const estLabelW = textUnits(state.label) * 6.2 + if (estLabelW > state.width + 6) { + problems.push( + `Label "${state.label}" (~${Math.round(estLabelW)}px) is wider than state "${state.id}" (${state.width}px) — shorten the label, move detail to sublabel, or increase state.width.` + ) + } + } + + // All non-main/non-terminal lanes share the same y band, so the overlap + // check must run across lanes — not per-lane. + const allStates = [...states.values()] + for (let i = 0; i < allStates.length; i += 1) { + for (let j = i + 1; j < allStates.length; j += 1) { + if (rectsOverlap(allStates[i], allStates[j], 10)) { + problems.push( + `States "${allStates[i].id}" and "${allStates[j].id}" are less than 10px apart — move one to another col or separate them with yOffset (lanes other than "main"/"terminal" share one band).` + ) + } + } + } + + for (const transition of asArray(lifecycle.transitions)) { + if (!states.has(transition.from)) + problems.push( + `Transition "${transition.label || transition.from}" references unknown source "${transition.from}".` + ) + if (!states.has(transition.to)) + problems.push(`Transition "${transition.label || transition.to}" references unknown target "${transition.to}".`) + if (states.has(transition.from) && states.has(transition.to)) { + const routed = pathFor(transition) + const [start, end] = [routed.points[0], routed.points[routed.points.length - 1]] + const distance = Math.hypot(end[0] - start[0], end[1] - start[1]) + if (distance < 32) + problems.push( + `Transition "${transition.label || `${transition.from}->${transition.to}`}" is too short (${Math.round(distance)}px; minimum 32px) — route it through a channel or drop its label.` + ) + } + } + + const labelRects = [] + for (const transition of asArray(lifecycle.transitions)) { + if (!transition.label || !states.has(transition.from) || !states.has(transition.to)) continue + const [lx, ly] = labelPoint(transition, pathFor(transition).points) + const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || '')) + const width = Math.max(32, longestLine * 4.9 + 12) + const height = transition.note ? 27 : 16 + labelRects.push({ label: transition.label, x: lx - width / 2, y: ly - 11, width, height, lx, ly }) + } + for (const rect of labelRects) { + for (const state of states.values()) { + if (rectsOverlap(rect, state, -2)) { + problems.push( + `Label "${rect.label}" overlaps state "${state.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, state, 'state')}` + ) + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push( + `Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}` + ) + } + } + } + + if (problems.length) { + throw new Error(`Lifecycle layout validation failed:\n- ${problems.join('\n- ')}`) + } +} + +function routeVia(transition, from, to, start, end) { + if (transition.via) return transition.via + switch (transition.route || 'auto') { + case 'straight': + return [] + case 'drop': { + const y = transition.channelY ?? (start[1] + end[1]) / 2 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'bottom-channel': { + const y = transition.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 34 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'top-channel': { + const y = transition.channelY ?? Math.min(from.y, to.y) - 28 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'right-channel': { + const x = transition.channelX ?? Math.max(from.x + from.width, to.x + to.width) + 36 + return [ + [x, start[1]], + [x, end[1]] + ] + } + case 'left-channel': { + const x = transition.channelX ?? Math.min(from.x, to.x) - 36 + return [ + [x, start[1]], + [x, end[1]] + ] + } + case 'auto': + default: { + if (from.lane === to.lane) return [] + const y = transition.channelY ?? (start[1] + end[1]) / 2 + return [ + [start[0], y], + [end[0], y] + ] + } + } +} + +const pathCache = new Map() + +function pathFor(transition) { + if (pathCache.has(transition)) return pathCache.get(transition) + const from = states.get(transition.from) + const to = states.get(transition.to) + const start = anchor(from, chosenSide(transition.fromSide, defaultFromSide(from, to))) + const end = anchor(to, chosenSide(transition.toSide, defaultToSide(from, to))) + const points = [start, ...routeVia(transition, from, to, start, end), end] + const routed = { + d: roundedPath(points, transition.cornerRadius ?? 10), + points + } + pathCache.set(transition, routed) + return routed +} + +function bandTitles() { + const lanes = asArray(lifecycle.lanes) + const mainLane = lanes.find((lane) => lane.id === 'main') + const terminalLane = lanes.find((lane) => lane.id === 'terminal') + const eventLanes = lanes.filter((lane) => lane.id !== 'main' && lane.id !== 'terminal') + return [ + mainLane?.label || 'Lifecycle phases', + eventLanes.length ? eventLanes.map((lane) => lane.label).join(' + ') : 'Interruptions + recovery', + terminalLane?.label || 'Outcomes' + ] +} + +function renderBands() { + const right = viewBox[0] - 72 + const titles = bandTitles() + return ` + 01 / ${esc(titles[0])} + + 02 / ${esc(titles[1])} + + 03 / ${esc(titles[2])}` +} + +function renderState(state) { + const fill = typeClass[state.type] || typeClass.neutral + const accent = textClass[state.type] || 't-muted' + const tag = state.tag + ? `\n ${esc(state.tag)}` + : '' + const step = state.step + ? `\n ${esc(state.step)}` + : '' + return ` + ${step} + ${esc(state.label)} + ${esc(state.sublabel || '')}${tag}` +} + +function renderTransitionPath(transition, index) { + const [cls, marker] = arrowClassMap[transition.variant || 'default'] || arrowClassMap.default + const routed = pathFor(transition) + const strokeWidth = transition.width || (transition.variant === 'emphasis' ? 2 : 1.1) + return ` ` +} + +function renderTransitionLabel(transition) { + if (!transition.label) return '' + const routed = pathFor(transition) + const [lx, ly] = labelPoint(transition, routed.points) + const longestLine = Math.max(textUnits(transition.label), textUnits(transition.note || '')) + const labelW = Math.max(32, longestLine * 4.9 + 12) + const labelH = transition.note ? 27 : 16 + const note = transition.note + ? `\n ${esc(transition.note)}` + : '' + return ` + ${esc(transition.label)}${note}` +} + +function renderLegend() { + const y = legendY() + return ` Legend + + active state + + waiting + + terminal success + + failure / exit` +} + +function renderLifecycleRail() { + const mainCols = [...states.values()].filter((state) => bandFor(state.lane) === 'phase').map((state) => state.col) + if (!mainCols.length) return '' + const railEnd = layout.phaseXs[Math.max(...mainCols)] + 38 + return ` ` +} + +function renderSvg() { + return ` +${renderDefinitions()} + + + + + +${renderBands()} + + +${renderLifecycleRail()} + + +${asArray(lifecycle.transitions).map(renderTransitionPath).join('\n')} + + +${[...states.values()].map(renderState).join('\n\n')} + + +${asArray(lifecycle.transitions).map(renderTransitionLabel).join('\n')} + + +${renderLegend()} + ` +} + +validateLifecycle() +writeDiagram({ + outPath, + template, + meta: lifecycle.meta, + footerLabel: 'Lifecycle diagram', + svg: renderSvg(), + cards: lifecycle.cards +}) diff --git a/.agents/skills/archify/renderers/sequence/README.md b/.agents/skills/archify/renderers/sequence/README.md new file mode 100644 index 00000000..d3773496 --- /dev/null +++ b/.agents/skills/archify/renderers/sequence/README.md @@ -0,0 +1,84 @@ +# Sequence Renderer + +Render `diagram_type: "sequence"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/sequence/render-sequence.mjs input.sequence.json output.html +``` + +The renderer validates input against `archify/schemas/sequence.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `sequence.html` in the current working directory. + +## Input + +Sequence JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "sequence", + "meta": { + "title": "Cache Miss Request Sequence", + "subtitle": "Frontend request path with auth and cache fallback", + "viewBox": [920, 760] + }, + "participants": [], + "segments": [], + "messages": [], + "activations": [], + "cards": [] +} +``` + +The timeline scales with the viewBox height: a taller `meta.viewBox` buys more +message room, a shorter one shrinks the readable band instead of clipping. A +complete worked example lives at +`archify/examples/cache-miss-request.sequence.json`. + +The schema lives at: + +```text +archify/schemas/sequence.schema.json +``` + +## Layout budget + +| Constant | Value | +| ----------------- | -------------------------------------------------------------------- | +| viewBox | default `[920, 760]`; schema minimum `[480, 480]` | +| Participant boxes | 86×54 at y 72; centers at x = 62 + index×108 | +| Participant count | last center + 43 must be ≤ width − 40 (8 fit at width 920) | +| Lifelines | from y 142 down to height − 65; band must be ≥120px tall | +| Message `y` range | `[160, height − 83]` | +| Message spacing | ≥28px vertical between messages that share horizontal space | +| Arrow span | ≥60px horizontal between the two participants | +| Segments | y pixel ranges with `to > from`, inside `[72, lifeline bottom + 20]` | +| Legend row | y = height − 54 | + +`segments[].from/to` and `activations[].from/to` are y pixel coordinates, not +participant ids; activations also require `to > from`. + +## Design Rules + +- Put participants across the top, ordered by the story the reader should + follow. +- Time moves downward. +- Use `emphasis` for the main request path. +- Use `security` for auth, consent, permission, and policy calls. +- Use `return` for quiet response messages. +- Use `dashed` for async trace, event, logging, and non-blocking work. +- Use segments as light background guides; keep segment labels short. +- Keep labels short enough to fit in narrow previews. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including missing participants, duplicate participant IDs, +participant labels wider than their box, unknown message endpoints, messages +outside the readable timeline, overly tight vertical spacing between messages +that overlap horizontally, invalid segment or activation ranges, or +participants that exceed the viewBox. Text width is estimated CJK-aware: +fullwidth glyphs count as two units. diff --git a/.agents/skills/archify/renderers/sequence/render-sequence.mjs b/.agents/skills/archify/renderers/sequence/render-sequence.mjs new file mode 100644 index 00000000..2f4f96b4 --- /dev/null +++ b/.agents/skills/archify/renderers/sequence/render-sequence.mjs @@ -0,0 +1,291 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs' +import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs' +import { componentFill, arrowClassMap, rectsOverlap, asArray } from '../shared/geometry.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const { + diagram: sequence, + template, + outPath +} = loadDiagram({ + rendererDir: __dirname, + diagramType: 'sequence', + defaultExample: 'cache-miss-request.sequence.json' +}) + +const viewBox = sequence.meta?.viewBox || [920, 760] +// The timeline scales with viewBox height: a taller viewBox gains message room, +// a shorter one shrinks the readable band (validated below) instead of clipping. +const layout = { + topY: 72, + participantW: 86, + participantH: 54, + lifelineTop: 142, + lifelineBottom: viewBox[1] - 65, + legendY: viewBox[1] - 54, + leftX: 62, + colGap: 108, + labelH: 16 +} + +const arrowClass = { + ...arrowClassMap, + return: ['a-default', 'arrowhead'] +} + +function participantX(index) { + return layout.leftX + index * layout.colGap +} + +const participants = new Map( + asArray(sequence.participants).map((participant, index) => [ + participant.id, + { + ...participant, + index, + cx: participantX(index), + x: participantX(index) - layout.participantW / 2 + } + ]) +) + +function validateSequence() { + const problems = [] + if (sequence.schema_version !== 1) problems.push('Sequence files must set "schema_version": 1.') + if (sequence.diagram_type !== 'sequence') problems.push('Sequence files must set "diagram_type": "sequence".') + if (!sequence.meta?.title) problems.push('Sequence files must include meta.title.') + if (!Array.isArray(sequence.participants) || sequence.participants.length < 2) { + problems.push('Sequence diagrams need at least two participants.') + } + if (participants.size !== asArray(sequence.participants).length) problems.push('Participant ids must be unique.') + if (!Array.isArray(sequence.messages) || sequence.messages.length < 1) { + problems.push('Sequence diagrams need at least one message.') + } + if (sequence.cards !== undefined && !Array.isArray(sequence.cards)) + problems.push('Sequence "cards" must be an array.') + for (const arr of ['segments', 'activations']) { + if (sequence[arr] !== undefined && !Array.isArray(sequence[arr])) + problems.push(`Sequence "${arr}" must be an array.`) + } + + if (layout.lifelineBottom - layout.lifelineTop < 120) { + problems.push( + `viewBox height ${viewBox[1]} leaves under 120px of timeline — set meta.viewBox[1] to at least ${layout.lifelineTop + 120 + 65}.` + ) + } + + for (const participant of participants.values()) { + const estLabelW = textUnits(participant.label) * 6.8 + if (estLabelW > layout.participantW + 6) { + problems.push( + `Label "${participant.label}" (~${Math.round(estLabelW)}px) is wider than the ${layout.participantW}px participant box — shorten it or move detail to sublabel.` + ) + } + } + + for (const message of asArray(sequence.messages)) { + if (!participants.has(message.from)) + problems.push(`Message "${message.label}" references unknown source "${message.from}".`) + if (!participants.has(message.to)) + problems.push(`Message "${message.label}" references unknown target "${message.to}".`) + if (typeof message.y !== 'number') problems.push(`Message "${message.label}" must provide a numeric y.`) + if (message.y < layout.lifelineTop + 18 || message.y > layout.lifelineBottom - 18) { + problems.push( + `Message "${message.label}" sits outside the readable timeline — keep y between ${layout.lifelineTop + 18} and ${layout.lifelineBottom - 18}.` + ) + } + if (participants.has(message.from) && participants.has(message.to)) { + const distance = Math.abs(participants.get(message.to).cx - participants.get(message.from).cx) + if (distance < 60) + problems.push( + `Message "${message.label}" spans ${Math.round(distance)}px (minimum 60px) — give its participants more column distance.` + ) + } + } + + // Vertical crowding only matters when the arrows share horizontal space; + // disjoint arrows may legitimately run in parallel rows. + const placed = asArray(sequence.messages) + .filter((m) => participants.has(m.from) && participants.has(m.to)) + .map((m) => ({ + label: m.label, + y: m.y, + x1: Math.min(participants.get(m.from).cx, participants.get(m.to).cx), + x2: Math.max(participants.get(m.from).cx, participants.get(m.to).cx) + })) + .sort((a, b) => a.y - b.y) + for (let i = 0; i < placed.length; i += 1) { + for (let j = i + 1; j < placed.length && placed[j].y - placed[i].y < 28; j += 1) { + if (placed[i].x1 < placed[j].x2 && placed[j].x1 < placed[i].x2) { + problems.push( + `Messages "${placed[i].label}" and "${placed[j].label}" are less than 28px apart and share horizontal space — spread their y values.` + ) + } + } + } + + // Label masks can extend well past the arrow span, so check the actual + // label rectangles too — tangent arrows with long labels still collide. + const labelRects = asArray(sequence.messages) + .filter((m) => participants.has(m.from) && participants.has(m.to) && typeof m.y === 'number') + .map((m) => { + const x1 = participants.get(m.from).cx + const x2 = participants.get(m.to).cx + const width = Math.max(34, textUnits(m.label) * 5.2 + 12) + return { label: m.label, x: (x1 + x2) / 2 - width / 2, y: m.y - 20, width, height: layout.labelH } + }) + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push( + `Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — spread their message y values or shorten the labels.` + ) + } + } + } + + for (const segment of asArray(sequence.segments)) { + if (segment.to <= segment.from) { + problems.push( + `Segment "${segment.label}" has invalid y range (from ${segment.from} to ${segment.to}) — "to" must be greater than "from".` + ) + } + if (segment.from < layout.topY || segment.to > layout.lifelineBottom + 20) { + problems.push( + `Segment "${segment.label}" extends outside the canvas — keep its y range between ${layout.topY} and ${layout.lifelineBottom + 20}.` + ) + } + } + + for (const activation of asArray(sequence.activations)) { + if (!participants.has(activation.participant)) + problems.push(`Activation references unknown participant "${activation.participant}".`) + if (activation.to <= activation.from) + problems.push( + `Activation for "${activation.participant}" has invalid time range — "to" must be greater than "from".` + ) + } + + const lastParticipant = asArray(sequence.participants)[asArray(sequence.participants).length - 1] + if (lastParticipant && participants.get(lastParticipant.id).cx + layout.participantW / 2 > viewBox[0] - 40) { + const requiredWidth = Math.ceil(participants.get(lastParticipant.id).cx + layout.participantW / 2 + 40) + problems.push( + `Participants exceed viewBox width — set meta.viewBox[0] to at least ${requiredWidth} or remove a participant.` + ) + } + + if (problems.length) { + throw new Error(`Sequence layout validation failed:\n- ${problems.join('\n- ')}`) + } +} + +function renderParticipant(participant) { + const fill = componentFill[participant.type] || 'c-external' + return ` + + ${esc(participant.label)} + ${esc(participant.sublabel)}` +} + +function renderLifeline(participant) { + return ` ` +} + +function renderSegment(segment) { + return ` + ${esc(segment.label)}` +} + +function renderActivation(activation) { + const participant = participants.get(activation.participant) + const fill = componentFill[activation.type] || componentFill[participant.type] || 'c-external' + const x = participant.cx - 5 + const height = activation.to - activation.from + return ` + ` +} + +function messageLabel(message, x1, x2) { + const center = (x1 + x2) / 2 + const y = message.y - 10 + const labelW = Math.max(34, textUnits(message.label) * 5.2 + 12) + const accent = + message.variant === 'security' + ? 't-security' + : message.variant === 'dashed' + ? 't-messagebus' + : message.variant === 'return' + ? 't-muted' + : 't-backend' + return ` + ${esc(message.label)}` +} + +function renderMessage(message, index) { + const from = participants.get(message.from) + const to = participants.get(message.to) + const direction = to.cx > from.cx ? 1 : -1 + const start = from.cx + direction * 7 + const end = to.cx - direction * 7 + const [cls, marker] = arrowClass[message.variant || 'default'] || arrowClass.default + const strokeWidth = message.variant === 'emphasis' ? 1.8 : 1.4 + const dash = message.variant === 'return' ? ' stroke-dasharray="3,5"' : '' + const note = message.note + ? `\n ${esc(message.note)}` + : '' + return ` +${messageLabel(message, start, end)}${note}` +} + +function renderLegend() { + const y = layout.legendY + return ` Legend + + request + + return + + security + + async trace` +} + +function renderSvg() { + const participantList = [...participants.values()] + return ` +${renderDefinitions()} + + + + + +${asArray(sequence.segments).map(renderSegment).join('\n\n')} + + +${participantList.map(renderLifeline).join('\n')} + + +${asArray(sequence.messages).map(renderMessage).join('\n\n')} + + +${asArray(sequence.activations).map(renderActivation).join('\n')} + + +${participantList.map(renderParticipant).join('\n\n')} + + +${renderLegend()} + ` +} + +validateSequence() +writeDiagram({ + outPath, + template, + meta: sequence.meta, + footerLabel: 'Sequence diagram', + svg: renderSvg(), + cards: sequence.cards +}) diff --git a/.agents/skills/archify/renderers/shared/cli.mjs b/.agents/skills/archify/renderers/shared/cli.mjs new file mode 100644 index 00000000..0973ed47 --- /dev/null +++ b/.agents/skills/archify/renderers/shared/cli.mjs @@ -0,0 +1,47 @@ +import fs from 'node:fs' +import path from 'node:path' +import { applyTemplate, renderCards, esc } from './utils.mjs' +import { validateSchema } from './validator.mjs' + +// Common CLI head: node render-.mjs [input.json] [output.html] +export function loadDiagram({ rendererDir, diagramType, defaultExample, argv = process.argv }) { + const skillRoot = path.resolve(rendererDir, '../..') + const inputPath = path.resolve(argv[2] || path.join(skillRoot, 'examples', defaultExample)) + const diagram = JSON.parse(fs.readFileSync(inputPath, 'utf8')) + validateSchema(diagramType, diagram) + const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8') + // Optional chaining: in degraded mode (no ajv) malformed input must still + // reach the renderer's friendly layout checks instead of crashing here. + const outPath = path.resolve(process.cwd(), argv[3] || diagram.meta?.output || `${diagramType}.html`) + return { diagram, template, outPath } +} + +// Common CLI tail: fill the template and write the standalone HTML file. +// The keyboard hint is screen-only — it means nothing on paper. +export function writeDiagram({ outPath, template, meta, footerLabel, svg, cards }) { + fs.mkdirSync(path.dirname(outPath), { recursive: true }) + fs.writeFileSync( + outPath, + applyTemplate(template, { + title: meta.title, + subtitle: meta.subtitle, + footer: `${footerLabel} • Built with Archify • Press T for theme and E for export`, + svg, + cards: renderCards(cards) + }) + ) + console.log(outPath) +} + +// Accessible name for the generated diagram SVG. +export function svgRootAttrs(meta, kind) { + const name = meta.subtitle ? `${meta.title} — ${meta.subtitle}` : meta.title + const animation = meta.animation === 'trace' ? ' data-animation="trace"' : '' + return `role="img" aria-label="${esc(`${name} (${kind})`)}"${animation}` +} + +export function animateAttr(meta, kind, step) { + if (meta.animation !== 'trace') return '' + const safeStep = Number.isFinite(step) && step >= 0 ? Math.floor(step) : 0 + return ` data-animate="${kind}" style="--step:${safeStep}"` +} diff --git a/.agents/skills/archify/renderers/shared/generated-validators.mjs b/.agents/skills/archify/renderers/shared/generated-validators.mjs new file mode 100644 index 00000000..1215c87a --- /dev/null +++ b/.agents/skills/archify/renderers/shared/generated-validators.mjs @@ -0,0 +1,14 @@ +/* oxlint-disable */ +// Generated by scripts/generate-validators.mjs. Do not edit by hand. +"use strict";export const workflow = validate20;const schema31 = {"$schema":"https://json-schema.org/draft/2020-12/schema","$id":"https://github.com/tt-a1i/archify/schemas/workflow.schema.json","title":"Archify Workflow Diagram","type":"object","additionalProperties":false,"required":["schema_version","diagram_type","meta","lanes","nodes","edges"],"properties":{"schema_version":{"const":1},"diagram_type":{"const":"workflow"},"meta":{"type":"object","additionalProperties":false,"required":["title"],"properties":{"title":{"type":"string","minLength":1},"subtitle":{"type":"string"},"output":{"type":"string"},"animation":{"enum":["trace","none"]},"viewBox":{"type":"array","prefixItems":[{"type":"number","minimum":700},{"type":"number","minimum":240}],"items":false,"minItems":2,"maxItems":2}}},"lanes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"variant":{"enum":["normal","exception"]}}}},"phases":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"groups":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["id","label","lane","fromCol","toCol"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string","minLength":1},"lane":{"$ref":"common.schema.json#/$defs/id"},"fromCol":{"type":"integer","minimum":0,"maximum":5},"toCol":{"type":"integer","minimum":0,"maximum":5},"variant":{"enum":["default","emphasis","security","dashed"]}}}},"mainPath":{"type":"array","minItems":2,"items":{"$ref":"common.schema.json#/$defs/id"}},"nodes":{"type":"array","minItems":1,"items":{"type":"object","additionalProperties":false,"required":["id","lane","col","type","label"],"properties":{"id":{"$ref":"common.schema.json#/$defs/id"},"lane":{"$ref":"common.schema.json#/$defs/id"},"col":{"type":"integer","minimum":0,"maximum":5},"type":{"$ref":"common.schema.json#/$defs/componentType"},"label":{"type":"string","minLength":1},"sublabel":{"type":"string"},"tag":{"type":"string"},"width":{"type":"number","minimum":32},"height":{"type":"number","minimum":32},"yOffset":{"type":"number"}}}},"edges":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["from","to"],"properties":{"from":{"$ref":"common.schema.json#/$defs/id"},"to":{"$ref":"common.schema.json#/$defs/id"},"label":{"type":"string"},"variant":{"$ref":"common.schema.json#/$defs/variant"},"role":{"enum":["main","branch","async","return","error"]},"fromSide":{"$ref":"#/$defs/side"},"toSide":{"$ref":"#/$defs/side"},"route":{"enum":["auto","straight","drop","outside-right","return-left","bottom-channel","up-channel"]},"via":{"type":"array","items":{"$ref":"common.schema.json#/$defs/point"}},"labelAt":{"$ref":"common.schema.json#/$defs/point"},"labelDx":{"type":"number"},"labelDy":{"type":"number"},"labelSegment":{"type":"integer","minimum":0},"channelX":{"type":"number"},"channelY":{"type":"number"},"bias":{"type":"number","minimum":0,"maximum":1},"width":{"type":"number","minimum":0.5}}}},"cards":{"$ref":"common.schema.json#/$defs/cards"}},"$defs":{"side":{"enum":["left","right","top","bottom"]}}};const schema33 = {"type":"string","pattern":"^[a-zA-Z][a-zA-Z0-9_-]*$"};const schema40 = {"enum":["frontend","backend","database","cloud","security","messagebus","external"]};const schema43 = {"enum":["default","emphasis","security","dashed"]};const schema44 = {"enum":["left","right","top","bottom"]};const schema46 = {"type":"array","prefixItems":[{"type":"number"},{"type":"number"}],"items":false,"minItems":2,"maxItems":2};const schema48 = {"type":"array","items":{"type":"object","additionalProperties":false,"required":["dot","title","items"],"properties":{"dot":{"enum":["cyan","emerald","violet","amber","rose","orange","slate"]},"title":{"type":"string","minLength":1},"items":{"type":"array","items":{"type":"string"}}}}};const func1 = Object.prototype.hasOwnProperty;const func2 = function ucs2length(str) { + const len = str.length; + let length = 0; + let pos = 0; + while (pos < len) { + length += 1; + const value = str.charCodeAt(pos++); + if (value >= 0xd800 && value <= 0xdbff && pos < len + && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1; + } + return length; +};const pattern4 = new RegExp("^[a-zA-Z][a-zA-Z0-9_-]*$", "u");function validate20(data, {instancePath="", parentData, parentDataProperty, rootData=data, dynamicAnchors={}}={}){/*# sourceURL="https://github.com/tt-a1i/archify/schemas/workflow.schema.json" */;let vErrors = null;let errors = 0;const evaluated0 = validate20.evaluated;if(evaluated0.dynamicProps){evaluated0.props = undefined;}if(evaluated0.dynamicItems){evaluated0.items = undefined;}if(data && typeof data == "object" && !Array.isArray(data)){if(data.schema_version === undefined){const err0 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "schema_version"},message:"must have required property '"+"schema_version"+"'"};if(vErrors === null){vErrors = [err0];}else {vErrors.push(err0);}errors++;}if(data.diagram_type === undefined){const err1 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "diagram_type"},message:"must have required property '"+"diagram_type"+"'"};if(vErrors === null){vErrors = [err1];}else {vErrors.push(err1);}errors++;}if(data.meta === undefined){const err2 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "meta"},message:"must have required property '"+"meta"+"'"};if(vErrors === null){vErrors = [err2];}else {vErrors.push(err2);}errors++;}if(data.lanes === undefined){const err3 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "lanes"},message:"must have required property '"+"lanes"+"'"};if(vErrors === null){vErrors = [err3];}else {vErrors.push(err3);}errors++;}if(data.nodes === undefined){const err4 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "nodes"},message:"must have required property '"+"nodes"+"'"};if(vErrors === null){vErrors = [err4];}else {vErrors.push(err4);}errors++;}if(data.edges === undefined){const err5 = {instancePath,schemaPath:"#/required",keyword:"required",params:{missingProperty: "edges"},message:"must have required property '"+"edges"+"'"};if(vErrors === null){vErrors = [err5];}else {vErrors.push(err5);}errors++;}for(const key0 in data){if(!(func1.call(schema31.properties, key0))){const err6 = {instancePath,schemaPath:"#/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key0},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err6];}else {vErrors.push(err6);}errors++;}}if(data.schema_version !== undefined){if(1 !== data.schema_version){const err7 = {instancePath:instancePath+"/schema_version",schemaPath:"#/properties/schema_version/const",keyword:"const",params:{allowedValue: 1},message:"must be equal to constant"};if(vErrors === null){vErrors = [err7];}else {vErrors.push(err7);}errors++;}}if(data.diagram_type !== undefined){if("workflow" !== data.diagram_type){const err8 = {instancePath:instancePath+"/diagram_type",schemaPath:"#/properties/diagram_type/const",keyword:"const",params:{allowedValue: "workflow"},message:"must be equal to constant"};if(vErrors === null){vErrors = [err8];}else {vErrors.push(err8);}errors++;}}if(data.meta !== undefined){let data2 = data.meta;if(data2 && typeof data2 == "object" && !Array.isArray(data2)){if(data2.title === undefined){const err9 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/required",keyword:"required",params:{missingProperty: "title"},message:"must have required property '"+"title"+"'"};if(vErrors === null){vErrors = [err9];}else {vErrors.push(err9);}errors++;}for(const key1 in data2){if(!(((((key1 === "title") || (key1 === "subtitle")) || (key1 === "output")) || (key1 === "animation")) || (key1 === "viewBox"))){const err10 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key1},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err10];}else {vErrors.push(err10);}errors++;}}if(data2.title !== undefined){let data3 = data2.title;if(typeof data3 === "string"){if(func2(data3) < 1){const err11 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err11];}else {vErrors.push(err11);}errors++;}}else {const err12 = {instancePath:instancePath+"/meta/title",schemaPath:"#/properties/meta/properties/title/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err12];}else {vErrors.push(err12);}errors++;}}if(data2.subtitle !== undefined){if(typeof data2.subtitle !== "string"){const err13 = {instancePath:instancePath+"/meta/subtitle",schemaPath:"#/properties/meta/properties/subtitle/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err13];}else {vErrors.push(err13);}errors++;}}if(data2.output !== undefined){if(typeof data2.output !== "string"){const err14 = {instancePath:instancePath+"/meta/output",schemaPath:"#/properties/meta/properties/output/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}}if(data2.animation !== undefined){let data6 = data2.animation;if(!((data6 === "trace") || (data6 === "none"))){const err15 = {instancePath:instancePath+"/meta/animation",schemaPath:"#/properties/meta/properties/animation/enum",keyword:"enum",params:{allowedValues: schema31.properties.meta.properties.animation.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}}if(data2.viewBox !== undefined){let data7 = data2.viewBox;if(Array.isArray(data7)){if(data7.length > 2){const err16 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(data7.length < 2){const err17 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}const len0 = data7.length;if(len0 > 0){let data8 = data7[0];if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 700 || isNaN(data8)){const err18 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 700},message:"must be >= 700"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(len0 > 1){let data9 = data7[1];if((typeof data9 == "number") && (isFinite(data9))){if(data9 < 240 || isNaN(data9)){const err20 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}const len1 = data7.length;if(!(len1 <= 2)){const err22 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.lanes !== undefined){let data10 = data.lanes;if(Array.isArray(data10)){if(data10.length < 1){const err25 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}const len2 = data10.length;for(let i0=0; i0 5 || isNaN(data19)){const err46 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err46];}else {vErrors.push(err46);}errors++;}if(data19 < 0 || isNaN(data19)){const err47 = {instancePath:instancePath+"/phases/" + i1+"/fromCol",schemaPath:"#/properties/phases/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err47];}else {vErrors.push(err47);}errors++;}}}if(data16.toCol !== undefined){let data20 = data16.toCol;if(!(((typeof data20 == "number") && (!(data20 % 1) && !isNaN(data20))) && (isFinite(data20)))){const err48 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}if((typeof data20 == "number") && (isFinite(data20))){if(data20 > 5 || isNaN(data20)){const err49 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}if(data20 < 0 || isNaN(data20)){const err50 = {instancePath:instancePath+"/phases/" + i1+"/toCol",schemaPath:"#/properties/phases/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}if(data16.variant !== undefined){let data21 = data16.variant;if(!((((data21 === "default") || (data21 === "emphasis")) || (data21 === "security")) || (data21 === "dashed"))){const err51 = {instancePath:instancePath+"/phases/" + i1+"/variant",schemaPath:"#/properties/phases/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.phases.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}}else {const err52 = {instancePath:instancePath+"/phases/" + i1,schemaPath:"#/properties/phases/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}}else {const err53 = {instancePath:instancePath+"/phases",schemaPath:"#/properties/phases/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}if(data.groups !== undefined){let data22 = data.groups;if(Array.isArray(data22)){const len4 = data22.length;for(let i2=0; i2 5 || isNaN(data27)){const err67 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}if(data27 < 0 || isNaN(data27)){const err68 = {instancePath:instancePath+"/groups/" + i2+"/fromCol",schemaPath:"#/properties/groups/items/properties/fromCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}}if(data23.toCol !== undefined){let data28 = data23.toCol;if(!(((typeof data28 == "number") && (!(data28 % 1) && !isNaN(data28))) && (isFinite(data28)))){const err69 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}if((typeof data28 == "number") && (isFinite(data28))){if(data28 > 5 || isNaN(data28)){const err70 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}if(data28 < 0 || isNaN(data28)){const err71 = {instancePath:instancePath+"/groups/" + i2+"/toCol",schemaPath:"#/properties/groups/items/properties/toCol/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}}if(data23.variant !== undefined){let data29 = data23.variant;if(!((((data29 === "default") || (data29 === "emphasis")) || (data29 === "security")) || (data29 === "dashed"))){const err72 = {instancePath:instancePath+"/groups/" + i2+"/variant",schemaPath:"#/properties/groups/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema31.properties.groups.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}}else {const err73 = {instancePath:instancePath+"/groups/" + i2,schemaPath:"#/properties/groups/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}}else {const err74 = {instancePath:instancePath+"/groups",schemaPath:"#/properties/groups/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}if(data.mainPath !== undefined){let data30 = data.mainPath;if(Array.isArray(data30)){if(data30.length < 2){const err75 = {instancePath:instancePath+"/mainPath",schemaPath:"#/properties/mainPath/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}const len5 = data30.length;for(let i3=0; i3 5 || isNaN(data36)){const err91 = {instancePath:instancePath+"/nodes/" + i4+"/col",schemaPath:"#/properties/nodes/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 5},message:"must be <= 5"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}if(data36 < 0 || isNaN(data36)){const err92 = {instancePath:instancePath+"/nodes/" + i4+"/col",schemaPath:"#/properties/nodes/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}}if(data33.type !== undefined){let data37 = data33.type;if(!(((((((data37 === "frontend") || (data37 === "backend")) || (data37 === "database")) || (data37 === "cloud")) || (data37 === "security")) || (data37 === "messagebus")) || (data37 === "external"))){const err93 = {instancePath:instancePath+"/nodes/" + i4+"/type",schemaPath:"common.schema.json#/$defs/componentType/enum",keyword:"enum",params:{allowedValues: schema40.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}if(data33.label !== undefined){let data38 = data33.label;if(typeof data38 === "string"){if(func2(data38) < 1){const err94 = {instancePath:instancePath+"/nodes/" + i4+"/label",schemaPath:"#/properties/nodes/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/nodes/" + i4+"/label",schemaPath:"#/properties/nodes/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data33.sublabel !== undefined){if(typeof data33.sublabel !== "string"){const err96 = {instancePath:instancePath+"/nodes/" + i4+"/sublabel",schemaPath:"#/properties/nodes/items/properties/sublabel/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(data33.tag !== undefined){if(typeof data33.tag !== "string"){const err97 = {instancePath:instancePath+"/nodes/" + i4+"/tag",schemaPath:"#/properties/nodes/items/properties/tag/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}if(data33.width !== undefined){let data41 = data33.width;if((typeof data41 == "number") && (isFinite(data41))){if(data41 < 32 || isNaN(data41)){const err98 = {instancePath:instancePath+"/nodes/" + i4+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}else {const err99 = {instancePath:instancePath+"/nodes/" + i4+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}if(data33.height !== undefined){let data42 = data33.height;if((typeof data42 == "number") && (isFinite(data42))){if(data42 < 32 || isNaN(data42)){const err100 = {instancePath:instancePath+"/nodes/" + i4+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 32},message:"must be >= 32"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}else {const err101 = {instancePath:instancePath+"/nodes/" + i4+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err101];}else {vErrors.push(err101);}errors++;}}if(data33.yOffset !== undefined){let data43 = data33.yOffset;if(!((typeof data43 == "number") && (isFinite(data43)))){const err102 = {instancePath:instancePath+"/nodes/" + i4+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err102];}else {vErrors.push(err102);}errors++;}}}else {const err103 = {instancePath:instancePath+"/nodes/" + i4,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}}}else {const err104 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}}if(data.edges !== undefined){let data44 = data.edges;if(Array.isArray(data44)){const len7 = data44.length;for(let i5=0; i5 2){const err118 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if(data55.length < 2){const err119 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}const len9 = data55.length;if(len9 > 0){let data56 = data55[0];if(!((typeof data56 == "number") && (isFinite(data56)))){const err120 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}if(len9 > 1){let data57 = data55[1];if(!((typeof data57 == "number") && (isFinite(data57)))){const err121 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}const len10 = data55.length;if(!(len10 <= 2)){const err122 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}else {const err123 = {instancePath:instancePath+"/edges/" + i5+"/via/" + i6,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}}else {const err124 = {instancePath:instancePath+"/edges/" + i5+"/via",schemaPath:"#/properties/edges/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err124];}else {vErrors.push(err124);}errors++;}}if(data45.labelAt !== undefined){let data58 = data45.labelAt;if(Array.isArray(data58)){if(data58.length > 2){const err125 = {instancePath:instancePath+"/edges/" + i5+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err125];}else {vErrors.push(err125);}errors++;}if(data58.length < 2){const err126 = {instancePath:instancePath+"/edges/" + i5+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err126];}else {vErrors.push(err126);}errors++;}const len11 = data58.length;if(len11 > 0){let data59 = data58[0];if(!((typeof data59 == "number") && (isFinite(data59)))){const err127 = {instancePath:instancePath+"/edges/" + i5+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err127];}else {vErrors.push(err127);}errors++;}}if(len11 > 1){let data60 = data58[1];if(!((typeof data60 == "number") && (isFinite(data60)))){const err128 = {instancePath:instancePath+"/edges/" + i5+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err128];}else {vErrors.push(err128);}errors++;}}const len12 = data58.length;if(!(len12 <= 2)){const err129 = {instancePath:instancePath+"/edges/" + i5+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err129];}else {vErrors.push(err129);}errors++;}}else {const err130 = {instancePath:instancePath+"/edges/" + i5+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err130];}else {vErrors.push(err130);}errors++;}}if(data45.labelDx !== undefined){let data61 = data45.labelDx;if(!((typeof data61 == "number") && (isFinite(data61)))){const err131 = {instancePath:instancePath+"/edges/" + i5+"/labelDx",schemaPath:"#/properties/edges/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err131];}else {vErrors.push(err131);}errors++;}}if(data45.labelDy !== undefined){let data62 = data45.labelDy;if(!((typeof data62 == "number") && (isFinite(data62)))){const err132 = {instancePath:instancePath+"/edges/" + i5+"/labelDy",schemaPath:"#/properties/edges/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err132];}else {vErrors.push(err132);}errors++;}}if(data45.labelSegment !== undefined){let data63 = data45.labelSegment;if(!(((typeof data63 == "number") && (!(data63 % 1) && !isNaN(data63))) && (isFinite(data63)))){const err133 = {instancePath:instancePath+"/edges/" + i5+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err133];}else {vErrors.push(err133);}errors++;}if((typeof data63 == "number") && (isFinite(data63))){if(data63 < 0 || isNaN(data63)){const err134 = {instancePath:instancePath+"/edges/" + i5+"/labelSegment",schemaPath:"#/properties/edges/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err134];}else {vErrors.push(err134);}errors++;}}}if(data45.channelX !== undefined){let data64 = data45.channelX;if(!((typeof data64 == "number") && (isFinite(data64)))){const err135 = {instancePath:instancePath+"/edges/" + i5+"/channelX",schemaPath:"#/properties/edges/items/properties/channelX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err135];}else {vErrors.push(err135);}errors++;}}if(data45.channelY !== undefined){let data65 = data45.channelY;if(!((typeof data65 == "number") && (isFinite(data65)))){const err136 = {instancePath:instancePath+"/edges/" + i5+"/channelY",schemaPath:"#/properties/edges/items/properties/channelY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err136];}else {vErrors.push(err136);}errors++;}}if(data45.bias !== undefined){let data66 = data45.bias;if((typeof data66 == "number") && (isFinite(data66))){if(data66 > 1 || isNaN(data66)){const err137 = {instancePath:instancePath+"/edges/" + i5+"/bias",schemaPath:"#/properties/edges/items/properties/bias/maximum",keyword:"maximum",params:{comparison: "<=", limit: 1},message:"must be <= 1"};if(vErrors === null){vErrors = [err137];}else {vErrors.push(err137);}errors++;}if(data66 < 0 || isNaN(data66)){const err138 = {instancePath:instancePath+"/edges/" + i5+"/bias",schemaPath:"#/properties/edges/items/properties/bias/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err138];}else {vErrors.push(err138);}errors++;}}else {const err139 = {instancePath:instancePath+"/edges/" + i5+"/bias",schemaPath:"#/properties/edges/items/properties/bias/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err139];}else {vErrors.push(err139);}errors++;}}if(data45.width !== undefined){let data67 = data45.width;if((typeof data67 == "number") && (isFinite(data67))){if(data67 < 0.5 || isNaN(data67)){const err140 = {instancePath:instancePath+"/edges/" + i5+"/width",schemaPath:"#/properties/edges/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err140];}else {vErrors.push(err140);}errors++;}}else {const err141 = {instancePath:instancePath+"/edges/" + i5+"/width",schemaPath:"#/properties/edges/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err141];}else {vErrors.push(err141);}errors++;}}}else {const err142 = {instancePath:instancePath+"/edges/" + i5,schemaPath:"#/properties/edges/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err142];}else {vErrors.push(err142);}errors++;}}}else {const err143 = {instancePath:instancePath+"/edges",schemaPath:"#/properties/edges/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err143];}else {vErrors.push(err143);}errors++;}}if(data.cards !== undefined){let data68 = data.cards;if(Array.isArray(data68)){const len13 = data68.length;for(let i7=0; i7 2){const err15 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}if(data7.length < 2){const err16 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}const len0 = data7.length;if(len0 > 0){let data8 = data7[0];if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 480 || isNaN(data8)){const err17 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}else {const err18 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}if(len0 > 1){let data9 = data7[1];if((typeof data9 == "number") && (isFinite(data9))){if(data9 < 480 || isNaN(data9)){const err19 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 480},message:"must be >= 480"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}else {const err20 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}const len1 = data7.length;if(!(len1 <= 2)){const err21 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}else {const err22 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}}else {const err23 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}if(data.participants !== undefined){let data10 = data.participants;if(Array.isArray(data10)){if(data10.length < 2){const err24 = {instancePath:instancePath+"/participants",schemaPath:"#/properties/participants/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}const len2 = data10.length;for(let i0=0; i0=", limit: 160},message:"must be >= 160"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}else {const err58 = {instancePath:instancePath+"/messages/" + i2+"/y",schemaPath:"#/properties/messages/items/properties/y/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}if(data22.label !== undefined){let data26 = data22.label;if(typeof data26 === "string"){if(func2(data26) < 1){const err59 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/minLength",keyword:"minLength",params:{limit: 1},message:"must NOT have fewer than 1 characters"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}else {const err60 = {instancePath:instancePath+"/messages/" + i2+"/label",schemaPath:"#/properties/messages/items/properties/label/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}if(data22.variant !== undefined){let data27 = data22.variant;if(!(((((data27 === "default") || (data27 === "emphasis")) || (data27 === "security")) || (data27 === "dashed")) || (data27 === "return"))){const err61 = {instancePath:instancePath+"/messages/" + i2+"/variant",schemaPath:"#/properties/messages/items/properties/variant/enum",keyword:"enum",params:{allowedValues: schema49.properties.messages.items.properties.variant.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}if(data22.note !== undefined){if(typeof data22.note !== "string"){const err62 = {instancePath:instancePath+"/messages/" + i2+"/note",schemaPath:"#/properties/messages/items/properties/note/type",keyword:"type",params:{type: "string"},message:"must be string"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}}else {const err63 = {instancePath:instancePath+"/messages/" + i2,schemaPath:"#/properties/messages/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}}else {const err64 = {instancePath:instancePath+"/messages",schemaPath:"#/properties/messages/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}if(data.activations !== undefined){let data29 = data.activations;if(Array.isArray(data29)){const len5 = data29.length;for(let i3=0; i3 2){const err16 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(data7.length < 2){const err17 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}const len0 = data7.length;if(len0 > 0){let data8 = data7[0];if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 360 || isNaN(data8)){const err18 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(len0 > 1){let data9 = data7[1];if((typeof data9 == "number") && (isFinite(data9))){if(data9 < 360 || isNaN(data9)){const err20 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 360},message:"must be >= 360"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}const len1 = data7.length;if(!(len1 <= 2)){const err22 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.stages !== undefined){let data10 = data.stages;if(Array.isArray(data10)){if(data10.length > 5){const err25 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/maxItems",keyword:"maxItems",params:{limit: 5},message:"must NOT have more than 5 items"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data10.length < 2){const err26 = {instancePath:instancePath+"/stages",schemaPath:"#/properties/stages/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}const len2 = data10.length;for(let i0=0; i0=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err48];}else {vErrors.push(err48);}errors++;}}}if(data14.row !== undefined){let data21 = data14.row;if(!(((typeof data21 == "number") && (!(data21 % 1) && !isNaN(data21))) && (isFinite(data21)))){const err49 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err49];}else {vErrors.push(err49);}errors++;}if((typeof data21 == "number") && (isFinite(data21))){if(data21 < 0 || isNaN(data21)){const err50 = {instancePath:instancePath+"/nodes/" + i1+"/row",schemaPath:"#/properties/nodes/items/properties/row/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err50];}else {vErrors.push(err50);}errors++;}}}if(data14.width !== undefined){let data22 = data14.width;if((typeof data22 == "number") && (isFinite(data22))){if(data22 < 48 || isNaN(data22)){const err51 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err51];}else {vErrors.push(err51);}errors++;}}else {const err52 = {instancePath:instancePath+"/nodes/" + i1+"/width",schemaPath:"#/properties/nodes/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err52];}else {vErrors.push(err52);}errors++;}}if(data14.height !== undefined){let data23 = data14.height;if((typeof data23 == "number") && (isFinite(data23))){if(data23 < 36 || isNaN(data23)){const err53 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err53];}else {vErrors.push(err53);}errors++;}}else {const err54 = {instancePath:instancePath+"/nodes/" + i1+"/height",schemaPath:"#/properties/nodes/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}}if(data14.yOffset !== undefined){let data24 = data14.yOffset;if(!((typeof data24 == "number") && (isFinite(data24)))){const err55 = {instancePath:instancePath+"/nodes/" + i1+"/yOffset",schemaPath:"#/properties/nodes/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}else {const err56 = {instancePath:instancePath+"/nodes/" + i1,schemaPath:"#/properties/nodes/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}}else {const err57 = {instancePath:instancePath+"/nodes",schemaPath:"#/properties/nodes/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}if(data.flows !== undefined){let data25 = data.flows;if(Array.isArray(data25)){const len4 = data25.length;for(let i2=0; i2 2){const err75 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}if(data37.length < 2){const err76 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err76];}else {vErrors.push(err76);}errors++;}const len5 = data37.length;if(len5 > 0){let data38 = data37[0];if(!((typeof data38 == "number") && (isFinite(data38)))){const err77 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err77];}else {vErrors.push(err77);}errors++;}}if(len5 > 1){let data39 = data37[1];if(!((typeof data39 == "number") && (isFinite(data39)))){const err78 = {instancePath:instancePath+"/flows/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}const len6 = data37.length;if(!(len6 <= 2)){const err79 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}else {const err80 = {instancePath:instancePath+"/flows/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}}if(data26.labelDx !== undefined){let data40 = data26.labelDx;if(!((typeof data40 == "number") && (isFinite(data40)))){const err81 = {instancePath:instancePath+"/flows/" + i2+"/labelDx",schemaPath:"#/properties/flows/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}}if(data26.labelDy !== undefined){let data41 = data26.labelDy;if(!((typeof data41 == "number") && (isFinite(data41)))){const err82 = {instancePath:instancePath+"/flows/" + i2+"/labelDy",schemaPath:"#/properties/flows/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(data26.labelSegment !== undefined){let data42 = data26.labelSegment;if(!(((typeof data42 == "number") && (!(data42 % 1) && !isNaN(data42))) && (isFinite(data42)))){const err83 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}if((typeof data42 == "number") && (isFinite(data42))){if(data42 < 0 || isNaN(data42)){const err84 = {instancePath:instancePath+"/flows/" + i2+"/labelSegment",schemaPath:"#/properties/flows/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}}if(data26.via !== undefined){let data43 = data26.via;if(Array.isArray(data43)){const len7 = data43.length;for(let i3=0; i3 2){const err85 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}if(data44.length < 2){const err86 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}const len8 = data44.length;if(len8 > 0){let data45 = data44[0];if(!((typeof data45 == "number") && (isFinite(data45)))){const err87 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(len8 > 1){let data46 = data44[1];if(!((typeof data46 == "number") && (isFinite(data46)))){const err88 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}const len9 = data44.length;if(!(len9 <= 2)){const err89 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}else {const err90 = {instancePath:instancePath+"/flows/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}}else {const err91 = {instancePath:instancePath+"/flows/" + i2+"/via",schemaPath:"#/properties/flows/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}}if(data26.width !== undefined){let data47 = data26.width;if((typeof data47 == "number") && (isFinite(data47))){if(data47 < 0.5 || isNaN(data47)){const err92 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"#/properties/flows/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}else {const err93 = {instancePath:instancePath+"/flows/" + i2+"/width",schemaPath:"#/properties/flows/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}}else {const err94 = {instancePath:instancePath+"/flows/" + i2,schemaPath:"#/properties/flows/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}}else {const err95 = {instancePath:instancePath+"/flows",schemaPath:"#/properties/flows/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}if(data.cards !== undefined){let data48 = data.cards;if(Array.isArray(data48)){const len10 = data48.length;for(let i4=0; i4 2){const err16 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}if(data7.length < 2){const err17 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}const len0 = data7.length;if(len0 > 0){let data8 = data7[0];if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 420 || isNaN(data8)){const err18 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 420},message:"must be >= 420"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}if(len0 > 1){let data9 = data7[1];if((typeof data9 == "number") && (isFinite(data9))){if(data9 < 566 || isNaN(data9)){const err20 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 566},message:"must be >= 566"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}const len1 = data7.length;if(!(len1 <= 2)){const err22 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}else {const err23 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}}}else {const err24 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data.lanes !== undefined){let data10 = data.lanes;if(Array.isArray(data10)){if(data10.length > 4){const err25 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/maxItems",keyword:"maxItems",params:{limit: 4},message:"must NOT have more than 4 items"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}if(data10.length < 1){const err26 = {instancePath:instancePath+"/lanes",schemaPath:"#/properties/lanes/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}const len2 = data10.length;for(let i0=0; i0 4 || isNaN(data23)){const err54 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/maximum",keyword:"maximum",params:{comparison: "<=", limit: 4},message:"must be <= 4"};if(vErrors === null){vErrors = [err54];}else {vErrors.push(err54);}errors++;}if(data23 < 0 || isNaN(data23)){const err55 = {instancePath:instancePath+"/states/" + i1+"/col",schemaPath:"#/properties/states/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err55];}else {vErrors.push(err55);}errors++;}}}if(data15.width !== undefined){let data24 = data15.width;if((typeof data24 == "number") && (isFinite(data24))){if(data24 < 48 || isNaN(data24)){const err56 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 48},message:"must be >= 48"};if(vErrors === null){vErrors = [err56];}else {vErrors.push(err56);}errors++;}}else {const err57 = {instancePath:instancePath+"/states/" + i1+"/width",schemaPath:"#/properties/states/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}if(data15.height !== undefined){let data25 = data15.height;if((typeof data25 == "number") && (isFinite(data25))){if(data25 < 36 || isNaN(data25)){const err58 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/minimum",keyword:"minimum",params:{comparison: ">=", limit: 36},message:"must be >= 36"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}}else {const err59 = {instancePath:instancePath+"/states/" + i1+"/height",schemaPath:"#/properties/states/items/properties/height/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}if(data15.yOffset !== undefined){let data26 = data15.yOffset;if(!((typeof data26 == "number") && (isFinite(data26)))){const err60 = {instancePath:instancePath+"/states/" + i1+"/yOffset",schemaPath:"#/properties/states/items/properties/yOffset/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}}}else {const err61 = {instancePath:instancePath+"/states/" + i1,schemaPath:"#/properties/states/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}}}else {const err62 = {instancePath:instancePath+"/states",schemaPath:"#/properties/states/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(data.transitions !== undefined){let data27 = data.transitions;if(Array.isArray(data27)){const len4 = data27.length;for(let i2=0; i2=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err78];}else {vErrors.push(err78);}errors++;}}else {const err79 = {instancePath:instancePath+"/transitions/" + i2+"/cornerRadius",schemaPath:"#/properties/transitions/items/properties/cornerRadius/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err79];}else {vErrors.push(err79);}errors++;}}if(data28.labelAt !== undefined){let data40 = data28.labelAt;if(Array.isArray(data40)){if(data40.length > 2){const err80 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err80];}else {vErrors.push(err80);}errors++;}if(data40.length < 2){const err81 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err81];}else {vErrors.push(err81);}errors++;}const len5 = data40.length;if(len5 > 0){let data41 = data40[0];if(!((typeof data41 == "number") && (isFinite(data41)))){const err82 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err82];}else {vErrors.push(err82);}errors++;}}if(len5 > 1){let data42 = data40[1];if(!((typeof data42 == "number") && (isFinite(data42)))){const err83 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err83];}else {vErrors.push(err83);}errors++;}}const len6 = data40.length;if(!(len6 <= 2)){const err84 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err84];}else {vErrors.push(err84);}errors++;}}else {const err85 = {instancePath:instancePath+"/transitions/" + i2+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err85];}else {vErrors.push(err85);}errors++;}}if(data28.labelDx !== undefined){let data43 = data28.labelDx;if(!((typeof data43 == "number") && (isFinite(data43)))){const err86 = {instancePath:instancePath+"/transitions/" + i2+"/labelDx",schemaPath:"#/properties/transitions/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err86];}else {vErrors.push(err86);}errors++;}}if(data28.labelDy !== undefined){let data44 = data28.labelDy;if(!((typeof data44 == "number") && (isFinite(data44)))){const err87 = {instancePath:instancePath+"/transitions/" + i2+"/labelDy",schemaPath:"#/properties/transitions/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}if(data28.labelSegment !== undefined){let data45 = data28.labelSegment;if(!(((typeof data45 == "number") && (!(data45 % 1) && !isNaN(data45))) && (isFinite(data45)))){const err88 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}if((typeof data45 == "number") && (isFinite(data45))){if(data45 < 0 || isNaN(data45)){const err89 = {instancePath:instancePath+"/transitions/" + i2+"/labelSegment",schemaPath:"#/properties/transitions/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}}if(data28.via !== undefined){let data46 = data28.via;if(Array.isArray(data46)){const len7 = data46.length;for(let i3=0; i3 2){const err90 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}if(data47.length < 2){const err91 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err91];}else {vErrors.push(err91);}errors++;}const len8 = data47.length;if(len8 > 0){let data48 = data47[0];if(!((typeof data48 == "number") && (isFinite(data48)))){const err92 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err92];}else {vErrors.push(err92);}errors++;}}if(len8 > 1){let data49 = data47[1];if(!((typeof data49 == "number") && (isFinite(data49)))){const err93 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err93];}else {vErrors.push(err93);}errors++;}}const len9 = data47.length;if(!(len9 <= 2)){const err94 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err94];}else {vErrors.push(err94);}errors++;}}else {const err95 = {instancePath:instancePath+"/transitions/" + i2+"/via/" + i3,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err95];}else {vErrors.push(err95);}errors++;}}}else {const err96 = {instancePath:instancePath+"/transitions/" + i2+"/via",schemaPath:"#/properties/transitions/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err96];}else {vErrors.push(err96);}errors++;}}if(data28.width !== undefined){let data50 = data28.width;if((typeof data50 == "number") && (isFinite(data50))){if(data50 < 0.5 || isNaN(data50)){const err97 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"#/properties/transitions/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err97];}else {vErrors.push(err97);}errors++;}}else {const err98 = {instancePath:instancePath+"/transitions/" + i2+"/width",schemaPath:"#/properties/transitions/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err98];}else {vErrors.push(err98);}errors++;}}}else {const err99 = {instancePath:instancePath+"/transitions/" + i2,schemaPath:"#/properties/transitions/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err99];}else {vErrors.push(err99);}errors++;}}}else {const err100 = {instancePath:instancePath+"/transitions",schemaPath:"#/properties/transitions/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err100];}else {vErrors.push(err100);}errors++;}}if(data.cards !== undefined){let data51 = data.cards;if(Array.isArray(data51)){const len10 = data51.length;for(let i4=0; i4 2){const err14 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err14];}else {vErrors.push(err14);}errors++;}if(data7.length < 2){const err15 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err15];}else {vErrors.push(err15);}errors++;}const len0 = data7.length;if(len0 > 0){let data8 = data7[0];if((typeof data8 == "number") && (isFinite(data8))){if(data8 < 320 || isNaN(data8)){const err16 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/minimum",keyword:"minimum",params:{comparison: ">=", limit: 320},message:"must be >= 320"};if(vErrors === null){vErrors = [err16];}else {vErrors.push(err16);}errors++;}}else {const err17 = {instancePath:instancePath+"/meta/viewBox/0",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err17];}else {vErrors.push(err17);}errors++;}}if(len0 > 1){let data9 = data7[1];if((typeof data9 == "number") && (isFinite(data9))){if(data9 < 240 || isNaN(data9)){const err18 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/minimum",keyword:"minimum",params:{comparison: ">=", limit: 240},message:"must be >= 240"};if(vErrors === null){vErrors = [err18];}else {vErrors.push(err18);}errors++;}}else {const err19 = {instancePath:instancePath+"/meta/viewBox/1",schemaPath:"#/properties/meta/properties/viewBox/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err19];}else {vErrors.push(err19);}errors++;}}const len1 = data7.length;if(!(len1 <= 2)){const err20 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err20];}else {vErrors.push(err20);}errors++;}}else {const err21 = {instancePath:instancePath+"/meta/viewBox",schemaPath:"#/properties/meta/properties/viewBox/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err21];}else {vErrors.push(err21);}errors++;}}}else {const err22 = {instancePath:instancePath+"/meta",schemaPath:"#/properties/meta/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err22];}else {vErrors.push(err22);}errors++;}}if(data.layout !== undefined){let data10 = data.layout;if(data10 && typeof data10 == "object" && !Array.isArray(data10)){if(data10.mode === undefined){const err23 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/required",keyword:"required",params:{missingProperty: "mode"},message:"must have required property '"+"mode"+"'"};if(vErrors === null){vErrors = [err23];}else {vErrors.push(err23);}errors++;}for(const key2 in data10){if(!(((((((key2 === "mode") || (key2 === "origin")) || (key2 === "cols")) || (key2 === "gapX")) || (key2 === "gapY")) || (key2 === "cellW")) || (key2 === "cellH"))){const err24 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/additionalProperties",keyword:"additionalProperties",params:{additionalProperty: key2},message:"must NOT have additional properties"};if(vErrors === null){vErrors = [err24];}else {vErrors.push(err24);}errors++;}}if(data10.mode !== undefined){if(!(data10.mode === "grid")){const err25 = {instancePath:instancePath+"/layout/mode",schemaPath:"#/properties/layout/properties/mode/enum",keyword:"enum",params:{allowedValues: schema76.properties.layout.properties.mode.enum},message:"must be equal to one of the allowed values"};if(vErrors === null){vErrors = [err25];}else {vErrors.push(err25);}errors++;}}if(data10.origin !== undefined){let data12 = data10.origin;if(Array.isArray(data12)){if(data12.length > 2){const err26 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err26];}else {vErrors.push(err26);}errors++;}if(data12.length < 2){const err27 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err27];}else {vErrors.push(err27);}errors++;}const len2 = data12.length;if(len2 > 0){let data13 = data12[0];if(!((typeof data13 == "number") && (isFinite(data13)))){const err28 = {instancePath:instancePath+"/layout/origin/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err28];}else {vErrors.push(err28);}errors++;}}if(len2 > 1){let data14 = data12[1];if(!((typeof data14 == "number") && (isFinite(data14)))){const err29 = {instancePath:instancePath+"/layout/origin/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err29];}else {vErrors.push(err29);}errors++;}}const len3 = data12.length;if(!(len3 <= 2)){const err30 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err30];}else {vErrors.push(err30);}errors++;}}else {const err31 = {instancePath:instancePath+"/layout/origin",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err31];}else {vErrors.push(err31);}errors++;}}if(data10.cols !== undefined){let data15 = data10.cols;if(!(((typeof data15 == "number") && (!(data15 % 1) && !isNaN(data15))) && (isFinite(data15)))){const err32 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err32];}else {vErrors.push(err32);}errors++;}if((typeof data15 == "number") && (isFinite(data15))){if(data15 > 12 || isNaN(data15)){const err33 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/maximum",keyword:"maximum",params:{comparison: "<=", limit: 12},message:"must be <= 12"};if(vErrors === null){vErrors = [err33];}else {vErrors.push(err33);}errors++;}if(data15 < 1 || isNaN(data15)){const err34 = {instancePath:instancePath+"/layout/cols",schemaPath:"#/properties/layout/properties/cols/minimum",keyword:"minimum",params:{comparison: ">=", limit: 1},message:"must be >= 1"};if(vErrors === null){vErrors = [err34];}else {vErrors.push(err34);}errors++;}}}if(data10.gapX !== undefined){let data16 = data10.gapX;if((typeof data16 == "number") && (isFinite(data16))){if(data16 < 0 || isNaN(data16)){const err35 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err35];}else {vErrors.push(err35);}errors++;}}else {const err36 = {instancePath:instancePath+"/layout/gapX",schemaPath:"#/properties/layout/properties/gapX/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err36];}else {vErrors.push(err36);}errors++;}}if(data10.gapY !== undefined){let data17 = data10.gapY;if((typeof data17 == "number") && (isFinite(data17))){if(data17 < 0 || isNaN(data17)){const err37 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err37];}else {vErrors.push(err37);}errors++;}}else {const err38 = {instancePath:instancePath+"/layout/gapY",schemaPath:"#/properties/layout/properties/gapY/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err38];}else {vErrors.push(err38);}errors++;}}if(data10.cellW !== undefined){let data18 = data10.cellW;if((typeof data18 == "number") && (isFinite(data18))){if(data18 < 40 || isNaN(data18)){const err39 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/minimum",keyword:"minimum",params:{comparison: ">=", limit: 40},message:"must be >= 40"};if(vErrors === null){vErrors = [err39];}else {vErrors.push(err39);}errors++;}}else {const err40 = {instancePath:instancePath+"/layout/cellW",schemaPath:"#/properties/layout/properties/cellW/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err40];}else {vErrors.push(err40);}errors++;}}if(data10.cellH !== undefined){let data19 = data10.cellH;if((typeof data19 == "number") && (isFinite(data19))){if(data19 < 24 || isNaN(data19)){const err41 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/minimum",keyword:"minimum",params:{comparison: ">=", limit: 24},message:"must be >= 24"};if(vErrors === null){vErrors = [err41];}else {vErrors.push(err41);}errors++;}}else {const err42 = {instancePath:instancePath+"/layout/cellH",schemaPath:"#/properties/layout/properties/cellH/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err42];}else {vErrors.push(err42);}errors++;}}}else {const err43 = {instancePath:instancePath+"/layout",schemaPath:"#/properties/layout/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err43];}else {vErrors.push(err43);}errors++;}}if(data.components !== undefined){let data20 = data.components;if(Array.isArray(data20)){if(data20.length < 1){const err44 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/minItems",keyword:"minItems",params:{limit: 1},message:"must NOT have fewer than 1 items"};if(vErrors === null){vErrors = [err44];}else {vErrors.push(err44);}errors++;}const len4 = data20.length;for(let i0=0; i0=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err57];}else {vErrors.push(err57);}errors++;}}}if(data21.col !== undefined){let data28 = data21.col;if(!(((typeof data28 == "number") && (!(data28 % 1) && !isNaN(data28))) && (isFinite(data28)))){const err58 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err58];}else {vErrors.push(err58);}errors++;}if((typeof data28 == "number") && (isFinite(data28))){if(data28 < 0 || isNaN(data28)){const err59 = {instancePath:instancePath+"/components/" + i0+"/col",schemaPath:"#/properties/components/items/properties/col/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err59];}else {vErrors.push(err59);}errors++;}}}if(data21.pos !== undefined){let data29 = data21.pos;if(Array.isArray(data29)){if(data29.length > 2){const err60 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err60];}else {vErrors.push(err60);}errors++;}if(data29.length < 2){const err61 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err61];}else {vErrors.push(err61);}errors++;}const len5 = data29.length;if(len5 > 0){let data30 = data29[0];if(!((typeof data30 == "number") && (isFinite(data30)))){const err62 = {instancePath:instancePath+"/components/" + i0+"/pos/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err62];}else {vErrors.push(err62);}errors++;}}if(len5 > 1){let data31 = data29[1];if(!((typeof data31 == "number") && (isFinite(data31)))){const err63 = {instancePath:instancePath+"/components/" + i0+"/pos/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err63];}else {vErrors.push(err63);}errors++;}}const len6 = data29.length;if(!(len6 <= 2)){const err64 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err64];}else {vErrors.push(err64);}errors++;}}else {const err65 = {instancePath:instancePath+"/components/" + i0+"/pos",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err65];}else {vErrors.push(err65);}errors++;}}if(data21.size !== undefined){let data32 = data21.size;if(Array.isArray(data32)){if(data32.length > 2){const err66 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err66];}else {vErrors.push(err66);}errors++;}if(data32.length < 2){const err67 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err67];}else {vErrors.push(err67);}errors++;}const len7 = data32.length;if(len7 > 0){let data33 = data32[0];if((typeof data33 == "number") && (isFinite(data33))){if(data33 <= 0 || isNaN(data33)){const err68 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err68];}else {vErrors.push(err68);}errors++;}}else {const err69 = {instancePath:instancePath+"/components/" + i0+"/size/0",schemaPath:"#/properties/components/items/properties/size/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err69];}else {vErrors.push(err69);}errors++;}}if(len7 > 1){let data34 = data32[1];if((typeof data34 == "number") && (isFinite(data34))){if(data34 <= 0 || isNaN(data34)){const err70 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/exclusiveMinimum",keyword:"exclusiveMinimum",params:{comparison: ">", limit: 0},message:"must be > 0"};if(vErrors === null){vErrors = [err70];}else {vErrors.push(err70);}errors++;}}else {const err71 = {instancePath:instancePath+"/components/" + i0+"/size/1",schemaPath:"#/properties/components/items/properties/size/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err71];}else {vErrors.push(err71);}errors++;}}const len8 = data32.length;if(!(len8 <= 2)){const err72 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err72];}else {vErrors.push(err72);}errors++;}}else {const err73 = {instancePath:instancePath+"/components/" + i0+"/size",schemaPath:"#/properties/components/items/properties/size/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err73];}else {vErrors.push(err73);}errors++;}}}else {const err74 = {instancePath:instancePath+"/components/" + i0,schemaPath:"#/properties/components/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err74];}else {vErrors.push(err74);}errors++;}}}else {const err75 = {instancePath:instancePath+"/components",schemaPath:"#/properties/components/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err75];}else {vErrors.push(err75);}errors++;}}if(data.boundaries !== undefined){let data35 = data.boundaries;if(Array.isArray(data35)){const len9 = data35.length;for(let i1=0; i1=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err87];}else {vErrors.push(err87);}errors++;}}else {const err88 = {instancePath:instancePath+"/boundaries/" + i1+"/pad",schemaPath:"#/properties/boundaries/items/properties/pad/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err88];}else {vErrors.push(err88);}errors++;}}}else {const err89 = {instancePath:instancePath+"/boundaries/" + i1,schemaPath:"#/properties/boundaries/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err89];}else {vErrors.push(err89);}errors++;}}}else {const err90 = {instancePath:instancePath+"/boundaries",schemaPath:"#/properties/boundaries/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err90];}else {vErrors.push(err90);}errors++;}}if(data.connections !== undefined){let data42 = data.connections;if(Array.isArray(data42)){const len11 = data42.length;for(let i3=0; i3 2){const err103 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4,schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err103];}else {vErrors.push(err103);}errors++;}if(data52.length < 2){const err104 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4,schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err104];}else {vErrors.push(err104);}errors++;}const len13 = data52.length;if(len13 > 0){let data53 = data52[0];if(!((typeof data53 == "number") && (isFinite(data53)))){const err105 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4+"/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err105];}else {vErrors.push(err105);}errors++;}}if(len13 > 1){let data54 = data52[1];if(!((typeof data54 == "number") && (isFinite(data54)))){const err106 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4+"/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err106];}else {vErrors.push(err106);}errors++;}}const len14 = data52.length;if(!(len14 <= 2)){const err107 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4,schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err107];}else {vErrors.push(err107);}errors++;}}else {const err108 = {instancePath:instancePath+"/connections/" + i3+"/via/" + i4,schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err108];}else {vErrors.push(err108);}errors++;}}}else {const err109 = {instancePath:instancePath+"/connections/" + i3+"/via",schemaPath:"#/properties/connections/items/properties/via/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err109];}else {vErrors.push(err109);}errors++;}}if(data43.labelAt !== undefined){let data55 = data43.labelAt;if(Array.isArray(data55)){if(data55.length > 2){const err110 = {instancePath:instancePath+"/connections/" + i3+"/labelAt",schemaPath:"common.schema.json#/$defs/point/maxItems",keyword:"maxItems",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err110];}else {vErrors.push(err110);}errors++;}if(data55.length < 2){const err111 = {instancePath:instancePath+"/connections/" + i3+"/labelAt",schemaPath:"common.schema.json#/$defs/point/minItems",keyword:"minItems",params:{limit: 2},message:"must NOT have fewer than 2 items"};if(vErrors === null){vErrors = [err111];}else {vErrors.push(err111);}errors++;}const len15 = data55.length;if(len15 > 0){let data56 = data55[0];if(!((typeof data56 == "number") && (isFinite(data56)))){const err112 = {instancePath:instancePath+"/connections/" + i3+"/labelAt/0",schemaPath:"common.schema.json#/$defs/point/prefixItems/0/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err112];}else {vErrors.push(err112);}errors++;}}if(len15 > 1){let data57 = data55[1];if(!((typeof data57 == "number") && (isFinite(data57)))){const err113 = {instancePath:instancePath+"/connections/" + i3+"/labelAt/1",schemaPath:"common.schema.json#/$defs/point/prefixItems/1/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err113];}else {vErrors.push(err113);}errors++;}}const len16 = data55.length;if(!(len16 <= 2)){const err114 = {instancePath:instancePath+"/connections/" + i3+"/labelAt",schemaPath:"common.schema.json#/$defs/point/items",keyword:"items",params:{limit: 2},message:"must NOT have more than 2 items"};if(vErrors === null){vErrors = [err114];}else {vErrors.push(err114);}errors++;}}else {const err115 = {instancePath:instancePath+"/connections/" + i3+"/labelAt",schemaPath:"common.schema.json#/$defs/point/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err115];}else {vErrors.push(err115);}errors++;}}if(data43.labelDx !== undefined){let data58 = data43.labelDx;if(!((typeof data58 == "number") && (isFinite(data58)))){const err116 = {instancePath:instancePath+"/connections/" + i3+"/labelDx",schemaPath:"#/properties/connections/items/properties/labelDx/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err116];}else {vErrors.push(err116);}errors++;}}if(data43.labelDy !== undefined){let data59 = data43.labelDy;if(!((typeof data59 == "number") && (isFinite(data59)))){const err117 = {instancePath:instancePath+"/connections/" + i3+"/labelDy",schemaPath:"#/properties/connections/items/properties/labelDy/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err117];}else {vErrors.push(err117);}errors++;}}if(data43.labelSegment !== undefined){let data60 = data43.labelSegment;if(!(((typeof data60 == "number") && (!(data60 % 1) && !isNaN(data60))) && (isFinite(data60)))){const err118 = {instancePath:instancePath+"/connections/" + i3+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/type",keyword:"type",params:{type: "integer"},message:"must be integer"};if(vErrors === null){vErrors = [err118];}else {vErrors.push(err118);}errors++;}if((typeof data60 == "number") && (isFinite(data60))){if(data60 < 0 || isNaN(data60)){const err119 = {instancePath:instancePath+"/connections/" + i3+"/labelSegment",schemaPath:"#/properties/connections/items/properties/labelSegment/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0},message:"must be >= 0"};if(vErrors === null){vErrors = [err119];}else {vErrors.push(err119);}errors++;}}}if(data43.width !== undefined){let data61 = data43.width;if((typeof data61 == "number") && (isFinite(data61))){if(data61 < 0.5 || isNaN(data61)){const err120 = {instancePath:instancePath+"/connections/" + i3+"/width",schemaPath:"#/properties/connections/items/properties/width/minimum",keyword:"minimum",params:{comparison: ">=", limit: 0.5},message:"must be >= 0.5"};if(vErrors === null){vErrors = [err120];}else {vErrors.push(err120);}errors++;}}else {const err121 = {instancePath:instancePath+"/connections/" + i3+"/width",schemaPath:"#/properties/connections/items/properties/width/type",keyword:"type",params:{type: "number"},message:"must be number"};if(vErrors === null){vErrors = [err121];}else {vErrors.push(err121);}errors++;}}}else {const err122 = {instancePath:instancePath+"/connections/" + i3,schemaPath:"#/properties/connections/items/type",keyword:"type",params:{type: "object"},message:"must be object"};if(vErrors === null){vErrors = [err122];}else {vErrors.push(err122);}errors++;}}}else {const err123 = {instancePath:instancePath+"/connections",schemaPath:"#/properties/connections/type",keyword:"type",params:{type: "array"},message:"must be array"};if(vErrors === null){vErrors = [err123];}else {vErrors.push(err123);}errors++;}}if(data.cards !== undefined){let data62 = data.cards;if(Array.isArray(data62)){const len17 = data62.length;for(let i5=0; i5` into the output. Used by the validators as a backstop. +export function isFinitePoint(...coords) { + return coords.every((c) => Number.isFinite(c)) +} + +export function rectsOverlap(a, b, gap = 0) { + return !( + a.x + a.width + gap <= b.x || + b.x + b.width + gap <= a.x || + a.y + a.height + gap <= b.y || + b.y + b.height + gap <= a.y + ) +} + +export function segmentIntersectsRect(segment, rect, gap = 0) { + const box = { + x1: rect.x - gap, + y1: rect.y - gap, + x2: rect.x + rect.width + gap, + y2: rect.y + rect.height + gap + } + const [a, b] = [segment.start, segment.end] + if (pointInBox(a, box) || pointInBox(b, box)) return true + return ( + segmentsIntersect(a, b, [box.x1, box.y1], [box.x2, box.y1]) || + segmentsIntersect(a, b, [box.x2, box.y1], [box.x2, box.y2]) || + segmentsIntersect(a, b, [box.x2, box.y2], [box.x1, box.y2]) || + segmentsIntersect(a, b, [box.x1, box.y2], [box.x1, box.y1]) + ) +} + +function pointInBox(point, box) { + return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2 +} + +function segmentsIntersect(a, b, c, d) { + const o1 = orientation(a, b, c) + const o2 = orientation(a, b, d) + const o3 = orientation(c, d, a) + const o4 = orientation(c, d, b) + + if (o1 === 0 && onSegment(a, c, b)) return true + if (o2 === 0 && onSegment(a, d, b)) return true + if (o3 === 0 && onSegment(c, a, d)) return true + if (o4 === 0 && onSegment(c, b, d)) return true + + return o1 !== o2 && o3 !== o4 +} + +function orientation(a, b, c) { + const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]) + if (Math.abs(value) < 0.0001) return 0 + return value > 0 ? 1 : 2 +} + +function onSegment(a, b, c) { + return ( + b[0] <= Math.max(a[0], c[0]) && + b[0] >= Math.min(a[0], c[0]) && + b[1] <= Math.max(a[1], c[1]) && + b[1] >= Math.min(a[1], c[1]) + ) +} + +export function anchor(rect, side) { + switch (side) { + case 'left': + return [rect.x, rect.cy] + case 'right': + return [rect.x + rect.width, rect.cy] + case 'top': + return [rect.cx, rect.y] + case 'bottom': + return [rect.cx, rect.y + rect.height] + default: + return [rect.x + rect.width, rect.cy] + } +} + +export function defaultFromSide(from, to) { + if (to.cx < from.cx) return 'left' + if (to.cx > from.cx) return 'right' + if (to.cy > from.cy) return 'bottom' + return 'top' +} + +export function defaultToSide(from, to) { + if (to.cx < from.cx) return 'right' + if (to.cx > from.cx) return 'left' + if (to.cy > from.cy) return 'top' + return 'bottom' +} + +export function chosenSide(side, fallback) { + return side && side !== 'auto' ? side : fallback +} + +export function polylinePath(points) { + return points.map(([x, y], index) => `${index === 0 ? 'M' : 'L'} ${x} ${y}`).join(' ') +} + +export function roundedPath(points, radius) { + if (points.length < 3 || radius <= 0) { + return polylinePath(points) + } + + const commands = [`M ${points[0][0]} ${points[0][1]}`] + for (let i = 1; i < points.length - 1; i += 1) { + const [px, py] = points[i - 1] + const [cx, cy] = points[i] + const [nx, ny] = points[i + 1] + const prevLen = Math.hypot(cx - px, cy - py) + const nextLen = Math.hypot(nx - cx, ny - cy) + const r = Math.min(radius, prevLen / 2, nextLen / 2) + if (r < 1) { + commands.push(`L ${cx} ${cy}`) + continue + } + const before = [cx - ((cx - px) / prevLen) * r, cy - ((cy - py) / prevLen) * r] + const after = [cx + ((nx - cx) / nextLen) * r, cy + ((ny - cy) / nextLen) * r] + commands.push(`L ${before[0]} ${before[1]}`) + commands.push(`Q ${cx} ${cy} ${after[0]} ${after[1]}`) + } + const [endX, endY] = points[points.length - 1] + commands.push(`L ${endX} ${endY}`) + return commands.join(' ') +} + +// Shared by edges/flows/transitions: all carry the same optional +// labelAt/labelDx/labelDy/labelSegment knobs. +export function labelPoint(item, points) { + if (item.labelAt) return item.labelAt + if (points.length === 2) { + return [(points[0][0] + points[1][0]) / 2 + (item.labelDx || 0), points[0][1] - 10 + (item.labelDy || 0)] + } + const segmentIndex = Math.min(points.length - 2, Math.max(0, item.labelSegment ?? 1)) + const a = points[segmentIndex] + const b = points[segmentIndex + 1] + return [(a[0] + b[0]) / 2 + (item.labelDx || 0), (a[1] + b[1]) / 2 - 10 + (item.labelDy || 0)] +} + +export const componentFill = { + frontend: 'c-frontend', + backend: 'c-backend', + database: 'c-database', + cloud: 'c-cloud', + security: 'c-security', + messagebus: 'c-messagebus', + external: 'c-external' +} + +export const componentText = { + frontend: 't-frontend', + backend: 't-backend', + database: 't-database', + cloud: 't-cloud', + security: 't-security', + messagebus: 't-messagebus', + external: 't-external' +} + +export const arrowClassMap = { + default: ['a-default', 'arrowhead'], + emphasis: ['a-emphasis', 'arrowhead-emphasis'], + security: ['a-security', 'arrowhead-security'], + dashed: ['a-dashed', 'arrowhead-dashed'] +} + +// Label accent per edge variant. Workflow colors dashed (async trace) labels +// like the trace store it points at; the other renderers use the bus color. +export function variantAccent(variant, { dashed = 't-messagebus' } = {}) { + return variant === 'security' + ? 't-security' + : variant === 'emphasis' + ? 't-backend' + : variant === 'dashed' + ? dashed + : 't-muted' +} + +export function formatRect(r) { + return `[${Math.round(r.x)}, ${Math.round(r.y)}, ${Math.round(r.width)}, ${Math.round(r.height)}]` +} + +function formatDelta(n) { + const v = Math.round(n) + return v >= 0 ? `+${v}` : String(v) +} + +/** Actionable hint when an edge label rect hits a node/component box (#7). */ +export function suggestLabelObstacleFix(labelRect, lx, ly, obstacle, obstacleKind = 'component') { + const lxR = Math.round(lx) + const lyR = Math.round(ly) + const belowY = Math.round(obstacle.y + obstacle.height + 14) + const aboveY = Math.round(obstacle.y - 4) + return [ + ` label rect: ${formatRect(labelRect)}`, + ` ${obstacleKind} "${obstacle.id}" rect: ${formatRect(obstacle)}`, + ` Suggested fix: labelAt [${lxR}, ${belowY}] or labelDy ${formatDelta(belowY - lyR)} (below); or labelAt [${lxR}, ${aboveY}] or labelDy ${formatDelta(aboveY - lyR)} (above)` + ].join('\n') +} + +/** Hint when two edge labels collide. */ +export function suggestLabelPairFix(a, b) { + return [ + ` "${a.label}" ${formatRect(a)}; "${b.label}" ${formatRect(b)}`, + ' Suggested fix: add labelDy +24 on one edge, adjust labelDx, or remove one label' + ].join('\n') +} + +/** Hint when two components/nodes are too close. */ +export function suggestComponentSeparation(a, b, minGap = 8) { + const rightX = Math.round(a.x + a.width + minGap) + const belowY = Math.round(a.y + a.height + minGap) + return [ + ` "${a.id}" ${formatRect(a)}; "${b.id}" ${formatRect(b)}`, + ` Suggested fix: move "${b.id}" pos to [${rightX}, ${Math.round(b.y)}] (right of "${a.id}") or [${Math.round(b.x)}, ${belowY}] (below)` + ].join('\n') +} diff --git a/.agents/skills/archify/renderers/shared/layout-report.mjs b/.agents/skills/archify/renderers/shared/layout-report.mjs new file mode 100644 index 00000000..f81b023b --- /dev/null +++ b/.agents/skills/archify/renderers/shared/layout-report.mjs @@ -0,0 +1,40 @@ +/** Serialize computed layout for dry-run / inspect (#9). */ + +export function componentBox(c) { + return { + id: c.id, + type: c.type, + label: c.label, + x: Math.round(c.x), + y: Math.round(c.y), + width: c.width, + height: c.height, + ...(Number.isInteger(c.row) ? { row: c.row } : {}), + ...(Number.isInteger(c.col) ? { col: c.col } : {}), + ...(Array.isArray(c.pos) ? { pos: c.pos.map(Math.round) } : {}) + } +} + +export function boundaryBox(b) { + return { + kind: b.kind, + label: b.label, + x: Math.round(b.x), + y: Math.round(b.y), + width: Math.round(b.width), + height: Math.round(b.height), + wraps: b.wraps + } +} + +export function connectionPath(conn, routed, labelAt) { + return { + from: conn.from, + to: conn.to, + label: conn.label ?? null, + variant: conn.variant ?? 'default', + route: conn.route ?? 'auto', + points: routed.points.map(([x, y]) => [Math.round(x), Math.round(y)]), + ...(labelAt ? { labelAt: labelAt.map(Math.round) } : {}) + } +} diff --git a/.agents/skills/archify/renderers/shared/utils.mjs b/.agents/skills/archify/renderers/shared/utils.mjs new file mode 100644 index 00000000..c5d81ee4 --- /dev/null +++ b/.agents/skills/archify/renderers/shared/utils.mjs @@ -0,0 +1,92 @@ +const ESCAPE_MAP = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' } + +export function esc(value) { + return String(value ?? '').replace(/[&<>"']/g, (c) => ESCAPE_MAP[c]) +} + +export function renderDefinitions() { + return ` + + + + + + + + + + + + + + + + + ` +} + +export function renderCards(cards) { + const list = Array.isArray(cards) ? cards : [] + return ` +
+${list + .map( + (card) => `
+
+
+

${esc(card.title)}

+
+
    +${card.items.map((item) => `
  • • ${esc(item)}
  • `).join('\n')} +
+
` + ) + .join('\n\n')} +
` +} + +const SVG_SLOT_RE = / [\s\S]*? / +const CARDS_SLOT_RE = / [\s\S]*? / + +const TEMPLATE_PLACEHOLDERS = [ + '[PROJECT NAME] Architecture Diagram', + '

[PROJECT NAME] Architecture

', + '

[Subtitle description]

', + '[Project Name] • [Additional metadata]' +] + +// `footer` is injected as raw HTML so callers can embed hints; +// pass only trusted strings here, never user input. +export function applyTemplate(template, { title, subtitle, footer, svg, cards }) { + if (!SVG_SLOT_RE.test(template)) { + throw new Error('applyTemplate: template missing ARCHIFY:SVG_SLOT sentinel') + } + if (!CARDS_SLOT_RE.test(template)) { + throw new Error('applyTemplate: template missing ARCHIFY:CARDS_SLOT sentinel') + } + for (const ph of TEMPLATE_PLACEHOLDERS) { + if (!template.includes(ph)) { + throw new Error(`applyTemplate: template missing placeholder ${JSON.stringify(ph)}`) + } + } + // Function replacers: a literal `$&`, `$'`, `$\`` or `$$` in titles, labels, + // or rendered SVG must not be interpreted as a replacement pattern. + return template + .replace(TEMPLATE_PLACEHOLDERS[0], () => `${esc(title)} Diagram`) + .replace(TEMPLATE_PLACEHOLDERS[1], () => `

${esc(title)}

`) + .replace(TEMPLATE_PLACEHOLDERS[2], () => `

${esc(subtitle ?? '')}

`) + .replace(SVG_SLOT_RE, () => svg) + .replace(CARDS_SLOT_RE, () => cards) + .replace(TEMPLATE_PLACEHOLDERS[3], () => footer) +} + +// CJK and other fullwidth glyphs render at roughly twice the advance width of +// ASCII in the monospace stacks the template uses. Includes the supplementary +// CJK extensions and emoji, which also render double-width. +const FULLWIDTH_RE = /[ᄀ-ᅟ⺀-꓏가-힣豈-﫿︰-﹏＀-⦆¢-₩ -〿\u{1F000}-\u{1FAFF}\u{20000}-\u{3FFFD}]/u + +export function textUnits(text) { + let units = 0 + for (const ch of String(text ?? '')) units += FULLWIDTH_RE.test(ch) ? 2 : 1 + return units +} diff --git a/.agents/skills/archify/renderers/shared/validator.mjs b/.agents/skills/archify/renderers/shared/validator.mjs new file mode 100644 index 00000000..a229b243 --- /dev/null +++ b/.agents/skills/archify/renderers/shared/validator.mjs @@ -0,0 +1,38 @@ +import * as validators from './generated-validators.mjs' + +// "/nodes/3/label" reads much better as "/nodes/3 (id: "router") /label" for the +// LLM fixing the JSON; resolve the nearest enclosing element's id or label. +function annotatePath(instancePath, data) { + if (!instancePath) return '/' + let node = data + let hint = null + for (const seg of instancePath.split('/').slice(1)) { + if (node == null || typeof node !== 'object') break + node = node[/^\d+$/.test(seg) ? Number(seg) : seg] + if (node && typeof node === 'object' && !Array.isArray(node)) { + const tag = node.id ?? node.label + if (tag != null) hint = String(tag) + } + } + return hint != null ? `${instancePath} (id/label: ${JSON.stringify(hint)})` : instancePath +} + +function formatErrors(errors, data) { + return errors + .map((e) => { + const where = annotatePath(e.instancePath, data) + const detail = e.params && Object.keys(e.params).length ? ' ' + JSON.stringify(e.params) : '' + return ` ${where} ${e.message}${detail}` + }) + .join('\n') +} + +export function validateSchema(diagramType, data) { + const validate = validators[diagramType] + if (!validate) { + throw new Error(`validateSchema: unknown diagram type "${diagramType}"`) + } + if (!validate(data)) { + throw new Error(`${diagramType} schema validation failed:\n${formatErrors(validate.errors, data)}`) + } +} diff --git a/.agents/skills/archify/renderers/workflow/README.md b/.agents/skills/archify/renderers/workflow/README.md new file mode 100644 index 00000000..d463e5fc --- /dev/null +++ b/.agents/skills/archify/renderers/workflow/README.md @@ -0,0 +1,103 @@ +# Workflow Renderer + +Render `diagram_type: "workflow"` JSON files into the standard Archify HTML +template. + +```bash +node archify/renderers/workflow/render-workflow.mjs input.workflow.json output.html +``` + +The renderer validates input against `archify/schemas/workflow.schema.json` +with the bundled standalone validator. No dependency installation is required. + +If `output.html` is omitted, the renderer uses `meta.output` from the JSON file +or falls back to `workflow.html` in the current working directory. + +After rendering, run the artifact checker: + +```bash +node archify/scripts/check-render-output.mjs output.html +``` + +It catches final-SVG issues that are easiest to see in a browser: non-finite +SVG values, accidental two-point diagonal arrows, and arrows crossing the +legend. + +## Input + +Workflow JSON files must set: + +```json +{ + "schema_version": 1, + "diagram_type": "workflow", + "meta": { + "title": "Agent Tool Call Workflow", + "subtitle": "Renderer-driven workflow prototype" + }, + "lanes": [], + "phases": [], + "groups": [], + "mainPath": [], + "nodes": [], + "edges": [], + "cards": [] +} +``` + +Omit `meta.viewBox` for the common case: the width is fixed at 720 and the +height is derived from the lane count, so lanes and legend always fit. A +complete worked example lives at +`archify/examples/agent-tool-call.workflow.json`. + +The schema lives at: + +```text +archify/schemas/workflow.schema.json +``` + +## Layout budget + +| Constant | Value | +| -------------------------- | ----------------------------------------------------------------------------------------------- | +| viewBox | default `[720, auto]` — auto height = 52 + lanes×104 + (lanes−1)×20 + 124 | +| Lane frame | x 40, width 640, height 104, gap 20; first lane top at y 52 | +| Lane title strip | top 30px of each lane; node boxes must stay below it | +| Column centers (`col` 0–5) | x = 88, 220, 300, 430, 500, 625 | +| Phase headers | Optional `phases[]` render above the first lane, spanning `fromCol..toCol` | +| Lane groups | Optional `groups[]` frame parallel work or branch work inside one lane | +| Exception lanes | Set `lane.variant: "exception"` for retry, denial, fallback, or failure paths | +| Main path lint | Optional `mainPath[]` checks that happy-path steps have matching edges and do not move backward | +| Default node | 92×52 (height 68 when `tag` is set) | +| Node spacing | ≥8px between nodes in the same lane | +| Edge length | straight segments must span ≥28px | +| Legend row | y = lane bottom + 44; viewBox height must be ≥ legend y + 18 | + +Column-center gaps are 132 / 80 / 130 / 70 / 125 px: columns 1↔2 (80px) and +3↔4 (70px) cannot both hold default-width 92px nodes in the same lane — skip a +column or reduce `width`. + +## Design Rules + +- Use lanes for ownership or runtime boundaries. +- Use phase headers for high-level story beats such as Intake, Plan, Execute, and Report. +- Use groups for parallel checks, branch handling, or bounded work within a lane; every group must contain at least one node. +- Use `lane.variant: "exception"` for human wait, denial, retry, fallback, and failure lanes instead of mixing those paths into the happy path. +- Set `mainPath` when the diagram has a clear happy path; the renderer validates that consecutive ids have matching edges and move left-to-right. +- Place nodes with lane IDs and column indexes, not raw SVG coordinates. +- Leave short adjacent links unlabeled; the arrow is enough. +- Use labels for cross-lane decisions, approvals, async traces, and return paths. +- Prefer route presets — `drop` (bend between lanes; `bias` 0–1 picks where), + `outside-right`, `return-left`, `bottom-channel`, and `up-channel` — before + using raw `via` points. `straight` and the default `auto` cover the rest. +- Keep workflow examples compact enough to render well in narrow chat/browser + previews. + +Schema violations exit non-zero with path-prefixed messages annotated with the +element's id or label. The renderer additionally fails when it can detect +layout problems, including node overlap, nodes outside their lanes, invalid +phase/group column ranges, empty groups, broken `mainPath` steps, unknown edge +targets, labels colliding with nodes or other labels, labels wider than their +node, legends outside the viewBox, or straight arrows that are too short to +read cleanly. Text width is estimated CJK-aware: fullwidth glyphs count as two +units. diff --git a/.agents/skills/archify/renderers/workflow/render-workflow.mjs b/.agents/skills/archify/renderers/workflow/render-workflow.mjs new file mode 100644 index 00000000..6ea5c6bc --- /dev/null +++ b/.agents/skills/archify/renderers/workflow/render-workflow.mjs @@ -0,0 +1,546 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { esc, renderDefinitions, textUnits } from '../shared/utils.mjs' +import { animateAttr, loadDiagram, writeDiagram, svgRootAttrs } from '../shared/cli.mjs' +import { + asArray, + isFinitePoint, + rectsOverlap, + segmentIntersectsRect, + suggestLabelObstacleFix, + suggestLabelPairFix, + anchor, + defaultFromSide, + defaultToSide, + chosenSide, + polylinePath, + labelPoint, + componentFill, + componentText, + arrowClassMap, + variantAccent +} from '../shared/geometry.mjs' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const { + diagram: workflow, + template, + outPath +} = loadDiagram({ + rendererDir: __dirname, + diagramType: 'workflow', + defaultExample: 'agent-tool-call.workflow.json' +}) + +const layout = { + laneX: 40, + laneY: 52, + laneW: 640, + laneH: 104, + laneGap: 20, + laneTitleH: 30, + colXs: [88, 220, 300, 430, 500, 625], + nodeW: 92, + nodeH: 52 +} + +// Content is 680px wide (laneX + laneW); auto height fits the lanes plus legend. +const autoHeight = + layout.laneY + + (workflow.lanes?.length || 1) * layout.laneH + + ((workflow.lanes?.length || 1) - 1) * layout.laneGap + + 124 +const viewBox = workflow.meta?.viewBox || [720, autoHeight] + +const laneIndex = new Map(asArray(workflow.lanes).map((lane, index) => [lane.id, index])) + +function laneTop(id) { + return layout.laneY + laneIndex.get(id) * (layout.laneH + layout.laneGap) +} + +function lastLaneBottom() { + return layout.laneY + workflow.lanes.length * layout.laneH + (workflow.lanes.length - 1) * layout.laneGap +} + +function legendY() { + return lastLaneBottom() + 44 +} + +function measureNode(node) { + const width = node.width || layout.nodeW + const height = node.height || (node.tag ? 68 : layout.nodeH) + const cx = layout.colXs[node.col] + const contentH = layout.laneH - layout.laneTitleH + const y = laneTop(node.lane) + layout.laneTitleH + (contentH - height) / 2 + (node.yOffset || 0) + return { + ...node, + width, + height, + x: cx - width / 2, + y, + cx, + cy: y + height / 2 + } +} + +const nodes = new Map(asArray(workflow.nodes).map((node) => [node.id, measureNode(node)])) + +const mainPathSteps = new Map(asArray(workflow.mainPath).map((id, index) => [id, index])) +const edgeSteps = new Map( + asArray(workflow.edges).map((edge, index) => { + const fromStep = mainPathSteps.get(edge.from) + const toStep = mainPathSteps.get(edge.to) + const mainStep = Number.isInteger(fromStep) && toStep === fromStep + 1 ? fromStep : null + return [edge, mainStep ?? asArray(workflow.mainPath).length + index] + }) +) + +function nodeStep(node) { + return ( + mainPathSteps.get(node.id) ?? + asArray(workflow.mainPath).length + asArray(workflow.nodes).findIndex((item) => item.id === node.id) + ) +} + +function validateWorkflow() { + const problems = [] + if (workflow.schema_version !== 1) { + problems.push('Workflow files must set "schema_version": 1.') + } + if (workflow.diagram_type !== 'workflow') { + problems.push(`Unsupported diagram_type "${workflow.diagram_type}". Expected "workflow".`) + } + if (!workflow.meta || !workflow.meta.title) { + problems.push('Workflow files must include meta.title.') + } + if (!Array.isArray(workflow.lanes) || !workflow.lanes.length) { + problems.push('Workflow files must include at least one lane.') + } + if (!Array.isArray(workflow.nodes)) { + problems.push('Workflow files must include a nodes array.') + } + if (!Array.isArray(workflow.edges)) { + problems.push('Workflow files must include an edges array.') + } + if (workflow.phases !== undefined && !Array.isArray(workflow.phases)) { + problems.push('Workflow "phases" must be an array.') + } + if (workflow.groups !== undefined && !Array.isArray(workflow.groups)) { + problems.push('Workflow "groups" must be an array.') + } + if (workflow.mainPath !== undefined && !Array.isArray(workflow.mainPath)) { + problems.push('Workflow "mainPath" must be an array of node ids.') + } + if (workflow.cards !== undefined && !Array.isArray(workflow.cards)) { + problems.push('Workflow "cards" must be an array.') + } + if (problems.length) { + throw new Error(`Workflow layout validation failed:\n- ${problems.join('\n- ')}`) + } + + const laneIds = new Set(workflow.lanes.map((lane) => lane.id)) + if (laneIds.size !== workflow.lanes.length) { + problems.push('Lane ids must be unique.') + } + if (nodes.size !== workflow.nodes.length) { + problems.push('Node ids must be unique.') + } + const phaseIds = new Set(asArray(workflow.phases).map((phase) => phase.id)) + if (phaseIds.size !== asArray(workflow.phases).length) { + problems.push('Phase ids must be unique.') + } + const groupIds = new Set(asArray(workflow.groups).map((group) => group.id)) + if (groupIds.size !== asArray(workflow.groups).length) { + problems.push('Group ids must be unique.') + } + + for (const node of nodes.values()) { + if (!laneIds.has(node.lane)) { + problems.push(`Node "${node.id}" uses unknown lane "${node.lane}".`) + continue + } + if (!Number.isInteger(node.col) || node.col < 0 || node.col >= layout.colXs.length) { + problems.push( + `Node "${node.id}" uses column ${node.col}, but valid columns are integers 0..${layout.colXs.length - 1}.` + ) + continue + } + if (!isFinitePoint(node.x, node.y, node.cx, node.cy)) { + problems.push( + `Node "${node.id}" produced non-finite coordinates — check col, width, height, and yOffset are numbers.` + ) + continue + } + const estLabelW = textUnits(node.label) * 6.8 + if (estLabelW > node.width + 6) { + problems.push( + `Label "${node.label}" (~${Math.round(estLabelW)}px) is wider than node "${node.id}" (${node.width}px) — shorten the label, move detail to sublabel, or increase node.width.` + ) + } + + const top = laneTop(node.lane) + const contentTop = top + layout.laneTitleH + const laneRight = layout.laneX + layout.laneW + if (node.x < layout.laneX || node.x + node.width > laneRight) { + problems.push(`Node "${node.id}" exceeds the horizontal bounds of lane "${node.lane}".`) + } + if (node.y < contentTop || node.y + node.height > top + layout.laneH) { + problems.push(`Node "${node.id}" collides with the title or boundary of lane "${node.lane}".`) + } + } + + for (const phase of asArray(workflow.phases)) { + if (!Number.isInteger(phase.fromCol) || !Number.isInteger(phase.toCol)) { + problems.push(`Phase "${phase.id}" must use integer fromCol/toCol values.`) + continue + } + if (phase.fromCol < 0 || phase.toCol >= layout.colXs.length || phase.fromCol > phase.toCol) { + problems.push( + `Phase "${phase.id}" uses invalid columns ${phase.fromCol}..${phase.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.` + ) + } + const estLabelW = textUnits(phase.label) * 5.6 + const width = spanForCols(phase.fromCol, phase.toCol).width + if (estLabelW > width + 8) { + problems.push( + `Phase label "${phase.label}" (~${Math.round(estLabelW)}px) is wider than its ${Math.round(width)}px span — shorten the label or widen the phase range.` + ) + } + } + + for (const group of asArray(workflow.groups)) { + if (!laneIds.has(group.lane)) { + problems.push(`Group "${group.id}" uses unknown lane "${group.lane}".`) + continue + } + if (!Number.isInteger(group.fromCol) || !Number.isInteger(group.toCol)) { + problems.push(`Group "${group.id}" must use integer fromCol/toCol values.`) + continue + } + if (group.fromCol < 0 || group.toCol >= layout.colXs.length || group.fromCol > group.toCol) { + problems.push( + `Group "${group.id}" uses invalid columns ${group.fromCol}..${group.toCol}; use an ordered range within 0..${layout.colXs.length - 1}.` + ) + } + const contained = [...nodes.values()].some( + (node) => node.lane === group.lane && node.col >= group.fromCol && node.col <= group.toCol + ) + if (!contained) { + problems.push( + `Group "${group.id}" does not contain any nodes — align its lane/columns with the parallel or branch work it frames.` + ) + } + } + + const byLane = new Map() + for (const node of nodes.values()) { + byLane.set(node.lane, [...(byLane.get(node.lane) || []), node]) + } + for (const [lane, laneNodes] of byLane) { + for (let i = 0; i < laneNodes.length; i += 1) { + for (let j = i + 1; j < laneNodes.length; j += 1) { + if (rectsOverlap(laneNodes[i], laneNodes[j], 8)) { + problems.push( + `Nodes "${laneNodes[i].id}" and "${laneNodes[j].id}" are less than 8px apart in lane "${lane}" — move one to another col, adjust yOffset, or reduce width/height.` + ) + } + } + } + } + + for (const edge of workflow.edges) { + if (!nodes.has(edge.from)) + problems.push(`Edge "${edge.label || edge.from}" references unknown source "${edge.from}".`) + if (!nodes.has(edge.to)) problems.push(`Edge "${edge.label || edge.to}" references unknown target "${edge.to}".`) + if (nodes.has(edge.from) && nodes.has(edge.to)) { + const routed = pathFor(edge) + if (routed.points.length === 2) { + const [start, end] = routed.points + const segmentLength = Math.hypot(end[0] - start[0], end[1] - start[1]) + if (segmentLength < 28) { + problems.push( + `Edge "${edge.from}" -> "${edge.to}" is too short (${Math.round(segmentLength)}px; minimum 28px) — drop its label or route it through a channel.` + ) + } + } + const segments = [] + for (let i = 1; i < routed.points.length; i += 1) { + segments.push({ start: routed.points[i - 1], end: routed.points[i] }) + } + for (const node of nodes.values()) { + if (node.id === edge.from || node.id === edge.to) continue + if (segments.some((segment) => segmentIntersectsRect(segment, node, 2))) { + problems.push( + `Edge "${edge.from}" -> "${edge.to}" crosses node "${node.id}" — adjust fromSide/toSide, route it through a channel, or move one node to a clearer lane/column.` + ) + } + } + } + } + + if (Array.isArray(workflow.mainPath)) { + for (const id of workflow.mainPath) { + if (!nodes.has(id)) { + problems.push(`mainPath references unknown node "${id}".`) + } + } + for (let i = 0; i < workflow.mainPath.length - 1; i += 1) { + const fromId = workflow.mainPath[i] + const toId = workflow.mainPath[i + 1] + const from = nodes.get(fromId) + const to = nodes.get(toId) + if (!from || !to) continue + const linked = workflow.edges.some((edge) => edge.from === fromId && edge.to === toId) + if (!linked) { + problems.push( + `mainPath step "${fromId}" -> "${toId}" has no matching edge — add the edge or remove the pair from mainPath.` + ) + } + if (to.col < from.col) { + problems.push( + `mainPath step "${fromId}" -> "${toId}" moves backward from col ${from.col} to ${to.col} — use a return edge outside mainPath for loops.` + ) + } + } + } + + const labelRects = [] + for (const edge of workflow.edges) { + if (!edge.label || !nodes.has(edge.from) || !nodes.has(edge.to)) continue + const [lx, ly] = labelPoint(edge, pathFor(edge).points) + const width = Math.max(30, textUnits(edge.label) * 4.8 + 10) + labelRects.push({ label: edge.label, x: lx - width / 2, y: ly - 10, width, height: 14, lx, ly }) + } + for (const rect of labelRects) { + for (const node of nodes.values()) { + if (rectsOverlap(rect, node, -2)) { + problems.push( + `Label "${rect.label}" overlaps node "${node.id}" — adjust labelDx/labelDy/labelSegment or set labelAt.\n${suggestLabelObstacleFix(rect, rect.lx, rect.ly, node, 'node')}` + ) + } + } + } + for (let i = 0; i < labelRects.length; i += 1) { + for (let j = i + 1; j < labelRects.length; j += 1) { + if (rectsOverlap(labelRects[i], labelRects[j], -2)) { + problems.push( + `Labels "${labelRects[i].label}" and "${labelRects[j].label}" overlap — adjust labelDx/labelDy or remove one label.\n${suggestLabelPairFix(labelRects[i], labelRects[j])}` + ) + } + } + } + + if (viewBox[0] < layout.laneX + layout.laneW + 16) { + problems.push( + `viewBox width ${viewBox[0]} clips the ${layout.laneW}px lanes — set meta.viewBox[0] to at least ${layout.laneX + layout.laneW + 16}.` + ) + } + if (legendY() + 18 > viewBox[1]) { + problems.push(`Legend exceeds viewBox height ${viewBox[1]} — set meta.viewBox[1] to at least ${legendY() + 18}.`) + } + + if (problems.length) { + throw new Error(`Workflow layout validation failed:\n- ${problems.join('\n- ')}`) + } +} + +function gapYBetween(fromLane, toLane, bias = 0.5) { + const a = laneTop(fromLane) + layout.laneH + const b = laneTop(toLane) + return a + (b - a) * bias +} + +function spanForCols(fromCol, toCol, pad = 46) { + const start = layout.colXs[fromCol] - pad + const end = layout.colXs[toCol] + pad + return { x: start, width: end - start, cx: (start + end) / 2 } +} + +function sameLaneAutoVia(start, end) { + if (start[0] === end[0] || start[1] === end[1]) return [] + const midX = (start[0] + end[0]) / 2 + return [ + [midX, start[1]], + [midX, end[1]] + ] +} + +function routeVia(edge, from, to, start, end) { + if (edge.via) return edge.via + switch (edge.route || 'auto') { + case 'straight': + return [] + case 'drop': { + const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5) + return [ + [start[0], y], + [end[0], y] + ] + } + case 'outside-right': { + const x = edge.channelX ?? layout.laneX + layout.laneW + 12 + return [ + [x, start[1]], + [x, end[1]] + ] + } + case 'return-left': { + const x = edge.channelX ?? Math.min(from.x, to.x) - 28 + return [ + [x, start[1]], + [x, end[1]] + ] + } + case 'bottom-channel': { + const y = edge.channelY ?? Math.max(from.y + from.height, to.y + to.height) + 32 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'up-channel': { + const y = edge.channelY ?? Math.min(from.y, to.y) - 28 + return [ + [start[0], y], + [end[0], y] + ] + } + case 'auto': + default: { + if (from.lane === to.lane) return sameLaneAutoVia(start, end) + const y = gapYBetween(from.lane, to.lane, edge.bias ?? 0.5) + return [ + [start[0], y], + [end[0], y] + ] + } + } +} + +const pathCache = new Map() + +function pathFor(edge) { + if (pathCache.has(edge)) return pathCache.get(edge) + const from = nodes.get(edge.from) + const to = nodes.get(edge.to) + const start = anchor(from, chosenSide(edge.fromSide, defaultFromSide(from, to))) + const end = anchor(to, chosenSide(edge.toSide, defaultToSide(from, to))) + const points = [start, ...routeVia(edge, from, to, start, end), end] + const routed = { d: polylinePath(points), points } + pathCache.set(edge, routed) + return routed +} + +function renderLane(lane, index) { + const y = layout.laneY + index * (layout.laneH + layout.laneGap) + const exception = + lane.variant === 'exception' + ? `\n ` + : '' + const labelClass = lane.variant === 'exception' ? 't-security' : 't-dim' + const prefix = lane.variant === 'exception' ? 'EX' : String(index + 1).padStart(2, '0') + return ` ${exception} + ${prefix} / ${esc(lane.label)}` +} + +function renderPhase(phase) { + const span = spanForCols(phase.fromCol, phase.toCol, 46) + const accent = variantAccent(phase.variant) + const [lineClass] = arrowClassMap[phase.variant || 'default'] || arrowClassMap.default + return ` + + ${esc(phase.label)}` +} + +function renderGroup(group) { + const span = spanForCols(group.fromCol, group.toCol, 50) + const y = laneTop(group.lane) + layout.laneTitleH + 8 + const height = layout.laneH - layout.laneTitleH - 16 + const cls = group.variant === 'security' ? 'c-security-group' : 'c-lane' + const textClass = variantAccent(group.variant) + return ` + ${esc(group.label)}` +} + +function renderNode(node) { + const fill = componentFill[node.type] || 'c-external' + const accent = componentText[node.type] || 't-muted' + const tag = node.tag + ? `\n ${esc(node.tag)}` + : '' + return ` + + ${esc(node.label)} + ${esc(node.sublabel || '')}${tag}` +} + +function renderEdgePath(edge) { + const [cls, marker] = arrowClassMap[edge.variant || 'default'] || arrowClassMap.default + const routed = pathFor(edge) + const strokeWidth = edge.width || (edge.variant === 'emphasis' ? 1.8 : 1.4) + return ` ` +} + +function renderEdgeLabel(edge) { + if (!edge.label) return '' + const routed = pathFor(edge) + const [lx, ly] = labelPoint(edge, routed.points) + const labelW = Math.max(30, textUnits(edge.label) * 4.8 + 10) + return ` + ${esc(edge.label)}` +} + +function renderLegend() { + const y = legendY() + return ` Legend + + User UI + + Agent logic + + Policy + + Tool action + + Context / trace` +} + +function renderSvg() { + return ` +${renderDefinitions()} + + + + + +${workflow.lanes.map(renderLane).join('\n\n')} + + +${asArray(workflow.phases).map(renderPhase).join('\n')} + + +${asArray(workflow.groups).map(renderGroup).join('\n')} + + +${workflow.edges.map(renderEdgePath).join('\n')} + + +${[...nodes.values()].map(renderNode).join('\n\n')} + + +${workflow.edges.map(renderEdgeLabel).join('\n')} + + +${renderLegend()} + ` +} + +validateWorkflow() +writeDiagram({ + outPath, + template, + meta: workflow.meta, + footerLabel: 'Workflow diagram', + svg: renderSvg(), + cards: workflow.cards +}) diff --git a/.agents/skills/archify/schemas/README.md b/.agents/skills/archify/schemas/README.md new file mode 100644 index 00000000..a3df1969 --- /dev/null +++ b/.agents/skills/archify/schemas/README.md @@ -0,0 +1,72 @@ +# Archify JSON IR Schemas + +Each typed renderer consumes a JSON intermediate representation (IR) validated +against one of the schemas in this folder before any layout work happens. + +## Files + +| Schema | Governs | Structural arrays | +| -------------------------- | ------------------------------------------- | --------------------------------------------------------- | +| `workflow.schema.json` | `diagram_type: "workflow"` | `lanes`, `phases`, `groups`, `mainPath`, `nodes`, `edges` | +| `sequence.schema.json` | `diagram_type: "sequence"` | `participants`, `segments`, `messages`, `activations` | +| `dataflow.schema.json` | `diagram_type: "dataflow"` | `stages`, `nodes`, `flows` | +| `lifecycle.schema.json` | `diagram_type: "lifecycle"` | `lanes`, `states`, `transitions` | +| `architecture.schema.json` | `diagram_type: "architecture"` | `components`, `boundaries`, `connections` | +| `common.schema.json` | shared `$defs` only (no top-level document) | — | + +Every diagram schema requires `schema_version`, `diagram_type`, `meta` (with +`title`), and its structural arrays — except `segments`, `activations`, and +`cards`, which are optional — and sets `additionalProperties: false` at every +level, so unknown fields are rejected rather than silently ignored. + +Every `meta` object also accepts `animation: "trace"` for opt-in SVG/CSS motion +in generated HTML. Omit it, or set `"none"`, for the default static output. + +## schema_version policy + +`schema_version` is `"const": 1`. The constant pins the IR contract: a file +that validates today keeps rendering identically on every 2.x release. A +breaking change to any IR shape bumps the constant to `2`; renderers will then +reject version-1 files with a clear schema error instead of misrendering them. +Additive, backwards-compatible fields do not bump the version. + +## Shared definitions (common.schema.json) + +The five diagram schemas reference `common.schema.json#/$defs/...`: + +- `id` — element identifiers, pattern `^[a-zA-Z][a-zA-Z0-9_-]*$` +- `point` — an `[x, y]` pair of numbers (used by `via` and `labelAt`) +- `componentType` — `frontend`, `backend`, `database`, `cloud`, `security`, + `messagebus`, `external` +- `variant` — `default`, `emphasis`, `security`, `dashed` (sequence messages + extend this list locally with `return`) +- `cards` — the summary-card blocks rendered below the SVG + +Lifecycle state `type` is mode-specific (`start`/`active`/`waiting`/...) and +stays in `lifecycle.schema.json`. + +## Runtime validation + +At development time, `scripts/generate-validators.mjs` compiles all five +schemas with ajv's draft 2020-12 standalone generator using `strict: true` and +`allErrors: true`. The generated `renderers/shared/generated-validators.mjs` +is committed and shipped with the skill, so runtime validation has no npm or +network dependency. `renderers/shared/validator.mjs` applies the matching +standalone validator before the renderer's own layout checks. + +`npm test` runs the generator in check mode and fails when the committed +validators drift from their schemas. + +## Error format + +Schema violations exit non-zero. Each ajv error is reported on its own line as +the instance path — annotated with the nearest enclosing element's `id` or +`label` — followed by the message and parameters: + +```text +workflow schema validation failed: + /nodes/3 (id/label: "router") must NOT have additional properties {"additionalProperty":"colour"} +``` + +Schemas catch shape errors (types, enums, ranges, unknown fields); geometry +problems such as overlaps and label collisions are the renderers' job. diff --git a/.agents/skills/archify/schemas/architecture.schema.json b/.agents/skills/archify/schemas/architecture.schema.json new file mode 100644 index 00000000..5f9fa1d1 --- /dev/null +++ b/.agents/skills/archify/schemas/architecture.schema.json @@ -0,0 +1,121 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/architecture.schema.json", + "title": "Archify Architecture Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "components"], + "properties": { + "schema_version": { "const": 1 }, + "diagram_type": { "const": "architecture" }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { "type": "string", "minLength": 1 }, + "subtitle": { "type": "string" }, + "output": { "type": "string" }, + "animation": { "enum": ["trace", "none"] }, + "viewBox": { + "type": "array", + "prefixItems": [ + { "type": "number", "minimum": 320 }, + { "type": "number", "minimum": 240 } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "layout": { + "type": "object", + "additionalProperties": false, + "required": ["mode"], + "properties": { + "mode": { "enum": ["grid"] }, + "origin": { "$ref": "common.schema.json#/$defs/point" }, + "cols": { "type": "integer", "minimum": 1, "maximum": 12 }, + "gapX": { "type": "number", "minimum": 0 }, + "gapY": { "type": "number", "minimum": 0 }, + "cellW": { "type": "number", "minimum": 40 }, + "cellH": { "type": "number", "minimum": 24 } + } + }, + "components": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "label"], + "properties": { + "id": { "$ref": "common.schema.json#/$defs/id" }, + "type": { "$ref": "common.schema.json#/$defs/componentType" }, + "label": { "type": "string", "minLength": 1 }, + "sublabel": { "type": "string" }, + "tag": { "type": "string" }, + "row": { "type": "integer", "minimum": 0 }, + "col": { "type": "integer", "minimum": 0 }, + "pos": { "$ref": "common.schema.json#/$defs/point" }, + "size": { + "type": "array", + "prefixItems": [ + { "type": "number", "exclusiveMinimum": 0 }, + { "type": "number", "exclusiveMinimum": 0 } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + } + }, + "boundaries": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "label", "wraps"], + "properties": { + "kind": { "enum": ["region", "security-group"] }, + "label": { "type": "string", "minLength": 1 }, + "wraps": { + "type": "array", + "minItems": 1, + "items": { "$ref": "common.schema.json#/$defs/id" } + }, + "pad": { "type": "number", "minimum": 0 } + } + } + }, + "connections": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to"], + "properties": { + "from": { "$ref": "common.schema.json#/$defs/id" }, + "to": { "$ref": "common.schema.json#/$defs/id" }, + "label": { "type": "string" }, + "variant": { "$ref": "common.schema.json#/$defs/variant" }, + "fromSide": { "enum": ["left", "right", "top", "bottom"] }, + "toSide": { "enum": ["left", "right", "top", "bottom"] }, + "route": { "enum": ["auto", "straight", "orthogonal-h", "orthogonal-v"] }, + "via": { + "type": "array", + "items": { "$ref": "common.schema.json#/$defs/point" } + }, + "labelAt": { "$ref": "common.schema.json#/$defs/point" }, + "labelDx": { "type": "number" }, + "labelDy": { "type": "number" }, + "labelSegment": { "type": "integer", "minimum": 0 }, + "width": { "type": "number", "minimum": 0.5 } + } + } + }, + "cards": { "$ref": "common.schema.json#/$defs/cards" } + } +} diff --git a/.agents/skills/archify/schemas/common.schema.json b/.agents/skills/archify/schemas/common.schema.json new file mode 100644 index 00000000..df7052dc --- /dev/null +++ b/.agents/skills/archify/schemas/common.schema.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/common.schema.json", + "title": "Archify Shared Definitions", + "$defs": { + "id": { + "type": "string", + "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" + }, + "point": { + "type": "array", + "prefixItems": [{ "type": "number" }, { "type": "number" }], + "items": false, + "minItems": 2, + "maxItems": 2 + }, + "componentType": { + "enum": ["frontend", "backend", "database", "cloud", "security", "messagebus", "external"] + }, + "variant": { + "enum": ["default", "emphasis", "security", "dashed"] + }, + "cards": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["dot", "title", "items"], + "properties": { + "dot": { "enum": ["cyan", "emerald", "violet", "amber", "rose", "orange", "slate"] }, + "title": { "type": "string", "minLength": 1 }, + "items": { + "type": "array", + "items": { "type": "string" } + } + } + } + } + } +} diff --git a/.agents/skills/archify/schemas/dataflow.schema.json b/.agents/skills/archify/schemas/dataflow.schema.json new file mode 100644 index 00000000..9613df3f --- /dev/null +++ b/.agents/skills/archify/schemas/dataflow.schema.json @@ -0,0 +1,181 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/dataflow.schema.json", + "title": "Archify Data Flow Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "stages", "nodes", "flows"], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "dataflow" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "enum": ["trace", "none"] + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 360 + }, + { + "type": "number", + "minimum": 360 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "stages": { + "type": "array", + "minItems": 2, + "maxItems": 5, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["label"], + "properties": { + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "nodes": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "label", "stage", "row"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "stage": { + "type": "integer", + "minimum": 0 + }, + "row": { + "type": "integer", + "minimum": 0 + }, + "width": { + "type": "number", + "minimum": 48 + }, + "height": { + "type": "number", + "minimum": 36 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "flows": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to", "label"], + "properties": { + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "classification": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "route": { + "enum": ["auto", "straight", "vertical-channel", "bottom-channel", "top-channel"] + }, + "fromSide": { + "enum": ["left", "right", "top", "bottom"] + }, + "toSide": { + "enum": ["left", "right", "top", "bottom"] + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "width": { + "type": "number", + "minimum": 0.5 + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/.agents/skills/archify/schemas/lifecycle.schema.json b/.agents/skills/archify/schemas/lifecycle.schema.json new file mode 100644 index 00000000..1373547a --- /dev/null +++ b/.agents/skills/archify/schemas/lifecycle.schema.json @@ -0,0 +1,190 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/lifecycle.schema.json", + "title": "Archify Lifecycle Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "lanes", "states", "transitions"], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "lifecycle" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "enum": ["trace", "none"] + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 420 + }, + { + "type": "number", + "minimum": 566 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "lanes": { + "type": "array", + "minItems": 1, + "maxItems": 4, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "states": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "label", "lane", "col"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "enum": ["start", "active", "waiting", "decision", "success", "failure", "neutral", "external"] + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "step": { + "type": "string" + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "col": { + "type": "integer", + "minimum": 0, + "maximum": 4 + }, + "width": { + "type": "number", + "minimum": 48 + }, + "height": { + "type": "number", + "minimum": 36 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "transitions": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to"], + "properties": { + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string" + }, + "note": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "route": { + "enum": ["auto", "straight", "drop", "bottom-channel", "top-channel", "right-channel", "left-channel"] + }, + "fromSide": { + "enum": ["left", "right", "top", "bottom"] + }, + "toSide": { + "enum": ["left", "right", "top", "bottom"] + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "cornerRadius": { + "type": "number", + "minimum": 0 + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "width": { + "type": "number", + "minimum": 0.5 + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/.agents/skills/archify/schemas/sequence.schema.json b/.agents/skills/archify/schemas/sequence.schema.json new file mode 100644 index 00000000..a11034e6 --- /dev/null +++ b/.agents/skills/archify/schemas/sequence.schema.json @@ -0,0 +1,152 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/sequence.schema.json", + "title": "Archify Sequence Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "participants", "messages"], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "sequence" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "enum": ["trace", "none"] + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 480 + }, + { + "type": "number", + "minimum": 480 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "participants": { + "type": "array", + "minItems": 2, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "label"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + } + } + } + }, + "segments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to", "label"], + "properties": { + "from": { + "type": "number" + }, + "to": { + "type": "number" + }, + "label": { + "type": "string", + "minLength": 1 + } + } + } + }, + "messages": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to", "y", "label"], + "properties": { + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "y": { + "type": "number", + "minimum": 160 + }, + "label": { + "type": "string", + "minLength": 1 + }, + "variant": { + "enum": ["default", "emphasis", "security", "dashed", "return"] + }, + "note": { + "type": "string" + } + } + } + }, + "activations": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["participant", "from", "to"], + "properties": { + "participant": { + "$ref": "common.schema.json#/$defs/id" + }, + "from": { + "type": "number" + }, + "to": { + "type": "number" + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + } +} diff --git a/.agents/skills/archify/schemas/workflow.schema.json b/.agents/skills/archify/schemas/workflow.schema.json new file mode 100644 index 00000000..6c1c71a9 --- /dev/null +++ b/.agents/skills/archify/schemas/workflow.schema.json @@ -0,0 +1,265 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/tt-a1i/archify/schemas/workflow.schema.json", + "title": "Archify Workflow Diagram", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "diagram_type", "meta", "lanes", "nodes", "edges"], + "properties": { + "schema_version": { + "const": 1 + }, + "diagram_type": { + "const": "workflow" + }, + "meta": { + "type": "object", + "additionalProperties": false, + "required": ["title"], + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "subtitle": { + "type": "string" + }, + "output": { + "type": "string" + }, + "animation": { + "enum": ["trace", "none"] + }, + "viewBox": { + "type": "array", + "prefixItems": [ + { + "type": "number", + "minimum": 700 + }, + { + "type": "number", + "minimum": 240 + } + ], + "items": false, + "minItems": 2, + "maxItems": 2 + } + } + }, + "lanes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "variant": { + "enum": ["normal", "exception"] + } + } + } + }, + "phases": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label", "fromCol", "toCol"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "fromCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "toCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "variant": { + "enum": ["default", "emphasis", "security", "dashed"] + } + } + } + }, + "groups": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "label", "lane", "fromCol", "toCol"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "fromCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "toCol": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "variant": { + "enum": ["default", "emphasis", "security", "dashed"] + } + } + } + }, + "mainPath": { + "type": "array", + "minItems": 2, + "items": { + "$ref": "common.schema.json#/$defs/id" + } + }, + "nodes": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "lane", "col", "type", "label"], + "properties": { + "id": { + "$ref": "common.schema.json#/$defs/id" + }, + "lane": { + "$ref": "common.schema.json#/$defs/id" + }, + "col": { + "type": "integer", + "minimum": 0, + "maximum": 5 + }, + "type": { + "$ref": "common.schema.json#/$defs/componentType" + }, + "label": { + "type": "string", + "minLength": 1 + }, + "sublabel": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "width": { + "type": "number", + "minimum": 32 + }, + "height": { + "type": "number", + "minimum": 32 + }, + "yOffset": { + "type": "number" + } + } + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["from", "to"], + "properties": { + "from": { + "$ref": "common.schema.json#/$defs/id" + }, + "to": { + "$ref": "common.schema.json#/$defs/id" + }, + "label": { + "type": "string" + }, + "variant": { + "$ref": "common.schema.json#/$defs/variant" + }, + "role": { + "enum": ["main", "branch", "async", "return", "error"] + }, + "fromSide": { + "$ref": "#/$defs/side" + }, + "toSide": { + "$ref": "#/$defs/side" + }, + "route": { + "enum": ["auto", "straight", "drop", "outside-right", "return-left", "bottom-channel", "up-channel"] + }, + "via": { + "type": "array", + "items": { + "$ref": "common.schema.json#/$defs/point" + } + }, + "labelAt": { + "$ref": "common.schema.json#/$defs/point" + }, + "labelDx": { + "type": "number" + }, + "labelDy": { + "type": "number" + }, + "labelSegment": { + "type": "integer", + "minimum": 0 + }, + "channelX": { + "type": "number" + }, + "channelY": { + "type": "number" + }, + "bias": { + "type": "number", + "minimum": 0, + "maximum": 1 + }, + "width": { + "type": "number", + "minimum": 0.5 + } + } + } + }, + "cards": { + "$ref": "common.schema.json#/$defs/cards" + } + }, + "$defs": { + "side": { + "enum": ["left", "right", "top", "bottom"] + } + } +} diff --git a/.agents/skills/archify/scripts/check-render-output.mjs b/.agents/skills/archify/scripts/check-render-output.mjs new file mode 100644 index 00000000..a86697da --- /dev/null +++ b/.agents/skills/archify/scripts/check-render-output.mjs @@ -0,0 +1,315 @@ +#!/usr/bin/env node + +import fs from 'node:fs' +import path from 'node:path' + +const input = process.argv[2] + +if (!input || input === '-h' || input === '--help') { + console.error('Usage: node scripts/check-render-output.mjs ') + process.exit(input ? 0 : 2) +} + +const htmlPath = path.resolve(input) +let html +try { + html = fs.readFileSync(htmlPath, 'utf8') +} catch (err) { + console.error( + JSON.stringify( + { + ok: false, + file: htmlPath, + checks: [{ name: 'file_readable', ok: false, details: [err.message] }] + }, + null, + 2 + ) + ) + process.exit(1) +} + +const checks = [] + +function addCheck(name, ok, details = []) { + checks.push({ name, ok, details }) +} + +const svgMatches = [...html.matchAll(//gi)] +addCheck('single_svg', svgMatches.length === 1, [`found ${svgMatches.length} block(s)`]) + +if (svgMatches.length === 1) { + const svg = svgMatches[0][0] + addCheck('finite_svg', !/\b(?:NaN|undefined|Infinity|-Infinity)\b/.test(svg)) + const legendStart = svg.indexOf('') + const beforeLegend = legendStart >= 0 ? svg.slice(0, legendStart) : svg + const arrows = collectArrows(beforeLegend) + const diagonal = arrows.filter((arrow) => isTwoPointDiagonal(arrow)) + addCheck( + 'orthogonal_arrows', + diagonal.length === 0, + diagonal.map((arrow) => `${arrow.kind} ${arrow.index}: ${arrow.raw}`) + ) + + if (legendStart >= 0) { + const legendFragment = svg.slice(legendStart) + const legendBoxes = collectLegendBoxes(legendFragment) + const collisions = collectLegendCollisions(arrows, legendBoxes) + addCheck( + 'legend_clearance', + collisions.length === 0, + collisions.map((hit) => `${hit.arrow.kind} ${hit.arrow.index} crosses legend ${hit.box.label}`) + ) + } else { + addCheck('legend_clearance', true, ['no legend marker found']) + } +} + +const ok = checks.every((check) => check.ok) +console.log(JSON.stringify({ ok, file: htmlPath, checks }, null, 2)) +process.exit(ok ? 0 : 1) + +function collectArrows(fragment) { + const arrows = [] + let index = 0 + + for (const tag of fragment.matchAll(/<(path|line)\b[^>]*>/gi)) { + const raw = tag[0] + if (!/\bclass="[^"]*\ba-(?:default|emphasis|security|dashed)\b/.test(raw)) continue + if (!/\bmarker-end=/.test(raw)) continue + const attrs = parseAttrs(raw) + const segments = tag[1].toLowerCase() === 'line' ? lineSegments(attrs) : pathSegments(attrs.d || '') + arrows.push({ kind: tag[1].toLowerCase(), index: (index += 1), raw, segments }) + } + + return arrows +} + +function lineSegments(attrs) { + const start = [numberAttr(attrs, 'x1'), numberAttr(attrs, 'y1')] + const end = [numberAttr(attrs, 'x2'), numberAttr(attrs, 'y2')] + if (!isPoint(start) || !isPoint(end)) return [] + return [{ start, end }] +} + +function pathSegments(d) { + const points = pointsFromPath(d) + const segments = [] + for (let i = 1; i < points.length; i += 1) { + segments.push({ start: points[i - 1], end: points[i] }) + } + return segments +} + +function isTwoPointDiagonal(arrow) { + if (arrow.segments.length !== 1) return false + const { start, end } = arrow.segments[0] + return Math.abs(start[0] - end[0]) > 0.01 && Math.abs(start[1] - end[1]) > 0.01 +} + +function collectLegendBoxes(fragment) { + const boxes = [] + + for (const match of fragment.matchAll(/]*>/gi)) { + const attrs = parseAttrs(match[0]) + const x = numberAttr(attrs, 'x') + const y = numberAttr(attrs, 'y') + const width = numberAttr(attrs, 'width') + const height = numberAttr(attrs, 'height') + if ([x, y, width, height].every(Number.isFinite)) { + boxes.push({ x1: x, y1: y, x2: x + width, y2: y + height, label: `rect@${x},${y}` }) + } + } + + for (const match of fragment.matchAll(/]*)>([\s\S]*?)<\/text>/gi)) { + const attrs = parseAttrs(match[1]) + const box = textBox(attrs, stripTags(match[2]).trim()) + if (box) boxes.push(box) + } + + return boxes +} + +function collectLegendCollisions(arrows, boxes) { + const collisions = [] + for (const arrow of arrows) { + for (const segment of arrow.segments) { + for (const box of boxes) { + if (segmentIntersectsBox(segment, padBox(box, 2))) { + collisions.push({ arrow, box }) + } + } + } + } + return collisions +} + +function textBox(attrs, text) { + const x = numberAttr(attrs, 'x') + const y = numberAttr(attrs, 'y') + const fontSize = Number.parseFloat(attrs['font-size'] || '10') + if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(fontSize)) return null + const width = estimatedTextWidth(text, fontSize) + const anchor = attrs['text-anchor'] || 'start' + let x1 = x + if (anchor === 'middle') x1 = x - width / 2 + if (anchor === 'end') x1 = x - width + return { + x1, + y1: y - fontSize, + x2: x1 + width, + y2: y + fontSize * 0.25, + label: text || `text@${x},${y}` + } +} + +function estimatedTextWidth(text, fontSize) { + let units = 0 + for (const char of text) units += char.charCodeAt(0) > 255 ? 1.8 : 0.62 + return Math.max(fontSize, units * fontSize) +} + +function pointsFromPath(d) { + const tokens = d.match(/[MLHVZmlhvz]|[-+]?(?:\d*\.)?\d+(?:e[-+]?\d+)?/g) || [] + const points = [] + let i = 0 + let command = '' + let current = [0, 0] + let start = null + + while (i < tokens.length) { + if (isCommand(tokens[i])) command = tokens[i++] + if (!command) break + + const absolute = command === command.toUpperCase() + switch (command.toUpperCase()) { + case 'M': + case 'L': { + while (i + 1 < tokens.length && !isCommand(tokens[i])) { + const x = Number.parseFloat(tokens[i++]) + const y = Number.parseFloat(tokens[i++]) + if (!Number.isFinite(x) || !Number.isFinite(y)) break + current = absolute ? [x, y] : [current[0] + x, current[1] + y] + points.push(current) + if (!start) start = current + } + break + } + case 'H': { + while (i < tokens.length && !isCommand(tokens[i])) { + const x = Number.parseFloat(tokens[i++]) + if (!Number.isFinite(x)) break + current = absolute ? [x, current[1]] : [current[0] + x, current[1]] + points.push(current) + } + break + } + case 'V': { + while (i < tokens.length && !isCommand(tokens[i])) { + const y = Number.parseFloat(tokens[i++]) + if (!Number.isFinite(y)) break + current = absolute ? [current[0], y] : [current[0], current[1] + y] + points.push(current) + } + break + } + case 'Z': { + if (start) points.push(start) + break + } + default: + return [] + } + } + + return points.filter(isPoint) +} + +function segmentIntersectsBox(segment, box) { + const { start, end } = segment + if (pointInsideBox(start, box) || pointInsideBox(end, box)) return true + const edges = [ + [ + [box.x1, box.y1], + [box.x2, box.y1] + ], + [ + [box.x2, box.y1], + [box.x2, box.y2] + ], + [ + [box.x2, box.y2], + [box.x1, box.y2] + ], + [ + [box.x1, box.y2], + [box.x1, box.y1] + ] + ] + return edges.some(([a, b]) => segmentsIntersect(start, end, a, b)) +} + +function segmentsIntersect(a, b, c, d) { + const o1 = orientation(a, b, c) + const o2 = orientation(a, b, d) + const o3 = orientation(c, d, a) + const o4 = orientation(c, d, b) + + if (o1 !== o2 && o3 !== o4) return true + if (o1 === 0 && onSegment(a, c, b)) return true + if (o2 === 0 && onSegment(a, d, b)) return true + if (o3 === 0 && onSegment(c, a, d)) return true + if (o4 === 0 && onSegment(c, b, d)) return true + return false +} + +function orientation(a, b, c) { + const value = (b[1] - a[1]) * (c[0] - b[0]) - (b[0] - a[0]) * (c[1] - b[1]) + if (Math.abs(value) < 1e-9) return 0 + return value > 0 ? 1 : 2 +} + +function onSegment(a, b, c) { + return ( + b[0] <= Math.max(a[0], c[0]) + 1e-9 && + b[0] + 1e-9 >= Math.min(a[0], c[0]) && + b[1] <= Math.max(a[1], c[1]) + 1e-9 && + b[1] + 1e-9 >= Math.min(a[1], c[1]) + ) +} + +function pointInsideBox(point, box) { + return point[0] >= box.x1 && point[0] <= box.x2 && point[1] >= box.y1 && point[1] <= box.y2 +} + +function padBox(box, padding) { + return { + ...box, + x1: box.x1 - padding, + y1: box.y1 - padding, + x2: box.x2 + padding, + y2: box.y2 + padding + } +} + +function parseAttrs(tag) { + const attrs = {} + for (const match of tag.matchAll(/([\w:-]+)\s*=\s*"([^"]*)"/g)) attrs[match[1]] = match[2] + return attrs +} + +function numberAttr(attrs, name) { + return Number.parseFloat(attrs[name]) +} + +function isCommand(token) { + return /^[A-Za-z]$/.test(token) +} + +function isPoint(point) { + return Array.isArray(point) && point.length === 2 && point.every(Number.isFinite) +} + +function stripTags(value) { + return value.replace(/<[^>]*>/g, '') +} diff --git a/.agents/skills/archify/scripts/generate-validators.mjs b/.agents/skills/archify/scripts/generate-validators.mjs new file mode 100644 index 00000000..6eccc67e --- /dev/null +++ b/.agents/skills/archify/scripts/generate-validators.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import Ajv2020 from 'ajv/dist/2020.js' +import standaloneCode from 'ajv/dist/standalone/index.js' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const root = path.resolve(__dirname, '..') +const schemasDir = path.join(root, 'schemas') +const output = path.join(root, 'renderers/shared/generated-validators.mjs') +const diagramTypes = ['workflow', 'sequence', 'dataflow', 'lifecycle', 'architecture'] + +const ajv = new Ajv2020({ + allErrors: true, + strict: true, + code: { source: true, esm: true } +}) +ajv.addSchema(JSON.parse(fs.readFileSync(path.join(schemasDir, 'common.schema.json'), 'utf8'))) + +const schemaIds = {} +for (const type of diagramTypes) { + const schema = JSON.parse(fs.readFileSync(path.join(schemasDir, `${type}.schema.json`), 'utf8')) + ajv.addSchema(schema) + schemaIds[type] = schema.$id +} + +const banner = '/* oxlint-disable */\n// Generated by scripts/generate-validators.mjs. Do not edit by hand.\n' +const ajvUcs2Import = 'require("ajv/dist/runtime/ucs2length").default' +const inlineUcs2Length = `function ucs2length(str) { + const len = str.length; + let length = 0; + let pos = 0; + while (pos < len) { + length += 1; + const value = str.charCodeAt(pos++); + if (value >= 0xd800 && value <= 0xdbff && pos < len + && (str.charCodeAt(pos) & 0xfc00) === 0xdc00) pos += 1; + } + return length; +}` +let validatorCode = standaloneCode(ajv, schemaIds) +if (!validatorCode.includes(ajvUcs2Import)) { + throw new Error('AJV standalone output no longer contains the expected ucs2length helper') +} +validatorCode = validatorCode.replaceAll(ajvUcs2Import, inlineUcs2Length) +if (validatorCode.includes('require(')) { + throw new Error('AJV standalone output contains an unexpected runtime dependency') +} +const generated = `${banner}${validatorCode}\n` + +if (process.argv.includes('--check')) { + const current = fs.existsSync(output) ? fs.readFileSync(output, 'utf8').replace(/\r\n?/g, '\n') : '' + if (current !== generated) { + console.error('generated validators are stale — run npm run generate:validators') + process.exit(1) + } +} else { + const temporary = `${output}.${process.pid}.tmp` + fs.writeFileSync(temporary, generated) + fs.renameSync(temporary, output) + console.log(`generated ${path.relative(root, output)}`) +} diff --git a/.agents/skills/archify/scripts/render-examples.mjs b/.agents/skills/archify/scripts/render-examples.mjs new file mode 100644 index 00000000..161279d0 --- /dev/null +++ b/.agents/skills/archify/scripts/render-examples.mjs @@ -0,0 +1,30 @@ +// Re-render every bundled example from its JSON IR. Installed skills keep HTML +// beside the JSON examples; the development script passes the golden directory. + +import { execFileSync } from 'node:child_process' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') +const outputRoot = path.resolve(process.argv[2] || path.join(skillRoot, 'examples')) + +const TARGETS = [ + ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'], + ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'], + ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'], + ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'], + ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'] +] + +for (const [mode, input, output] of TARGETS) { + execFileSync( + process.execPath, + [ + path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), + path.join(skillRoot, 'examples', input), + path.join(outputRoot, output) + ], + { stdio: 'inherit' } + ) +} diff --git a/.agents/skills/archify/test/animation.test.mjs b/.agents/skills/archify/test/animation.test.mjs new file mode 100644 index 00000000..7696523e --- /dev/null +++ b/.agents/skills/archify/test/animation.test.mjs @@ -0,0 +1,53 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-animation-')) + +const CASES = { + architecture: 'web-app.architecture.json', + workflow: 'agent-tool-call.workflow.json', + sequence: 'cache-miss-request.sequence.json', + dataflow: 'product-analytics.dataflow.json', + lifecycle: 'agent-run.lifecycle.json' +} + +function render(mode, example, animation = 'trace') { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', example), 'utf8')) + if (animation) doc.meta = { ...doc.meta, animation } + const suffix = animation || 'static' + const input = path.join(tmp, `${mode}-${suffix}.json`) + const output = path.join(tmp, `${mode}-${suffix}.html`) + fs.writeFileSync(input, JSON.stringify(doc)) + execFileSync('node', [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), input, output], { + stdio: ['ignore', 'ignore', 'pipe'] + }) + return fs.readFileSync(output, 'utf8') +} + +function svgBlock(html) { + return html.match(//)?.[0] || '' +} + +test('static output omits animation attributes', () => { + const svg = svgBlock(render('workflow', CASES.workflow, null)) + assert.doesNotMatch(svg, /data-animation=/) + assert.doesNotMatch(svg, /data-animate=/) +}) + +for (const [mode, example] of Object.entries(CASES)) { + test(`${mode}: trace animation annotates svg, edges, and nodes`, () => { + const svg = svgBlock(render(mode, example)) + assert.match(svg, /]+data-animation="trace"/) + assert.match(svg, /data-animate="edge" style="--step:0"/) + assert.match(svg, /data-animate="node" style="--step:0"/) + }) +} + +process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true })) diff --git a/.agents/skills/archify/test/cli.test.mjs b/.agents/skills/archify/test/cli.test.mjs new file mode 100644 index 00000000..303397af --- /dev/null +++ b/.agents/skills/archify/test/cli.test.mjs @@ -0,0 +1,210 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-cli-')) +const cli = path.join(skillRoot, 'bin/archify.mjs') + +function run(args, options = {}) { + return spawnSync(process.execPath, [cli, ...args], { + cwd: options.cwd || skillRoot, + encoding: 'utf8', + env: options.env || process.env + }) +} + +function copyInstalledSkill(target) { + fs.cpSync(skillRoot, target, { + recursive: true, + filter(source) { + const rel = path.relative(skillRoot, source) + return ( + rel !== 'node_modules' && + !rel.startsWith(`node_modules${path.sep}`) && + rel !== 'test' && + !rel.startsWith(`test${path.sep}`) + ) + } + }) +} + +test('cli: help lists commands and diagram types', () => { + const result = run(['--help']) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, /archify render /) + assert.match(result.stdout, /archify doctor/) + assert.match(result.stdout, /archify demo \[output-directory\]/) + assert.match(result.stdout, /architecture, workflow, sequence, dataflow, lifecycle/) +}) + +test('cli: doctor reports a complete installation is ready', () => { + const result = run(['doctor']) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, /\[ok\] Node\.js v\d+/) + assert.match(result.stdout, /\[ok\] Core template/) + assert.match(result.stdout, /\[ok\] Example renderer/) + assert.match(result.stdout, /\[ok\] Standalone schema validators/) + assert.match(result.stdout, /\[ok\] architecture renderer, schema, and example/) + assert.match(result.stdout, /\[ok\] lifecycle renderer, schema, and example/) + assert.match(result.stdout, /Archify is ready\./) +}) + +test('cli: doctor identifies an incomplete installation', () => { + const incompleteRoot = path.join(tmp, 'incomplete-skill') + const incompleteBin = path.join(incompleteRoot, 'bin') + fs.mkdirSync(incompleteBin, { recursive: true }) + fs.copyFileSync(cli, path.join(incompleteBin, 'archify.mjs')) + + const result = spawnSync(process.execPath, [path.join(incompleteBin, 'archify.mjs'), 'doctor'], { + cwd: incompleteRoot, + encoding: 'utf8' + }) + + assert.equal(result.status, 1) + assert.match(result.stdout, /\[missing\] Core template/) + assert.match(result.stdout, /\[missing\] workflow renderer, schema, and example/) + assert.match(result.stderr, /Archify is not ready: \d+ required files? missing\./) +}) + +test('cli: doctor rejects a corrupt standalone validator', () => { + const corruptRoot = path.join(tmp, 'corrupt-skill') + copyInstalledSkill(corruptRoot) + fs.writeFileSync(path.join(corruptRoot, 'renderers/shared/generated-validators.mjs'), 'export const workflow = ;\n') + + const result = spawnSync(process.execPath, [path.join(corruptRoot, 'bin/archify.mjs'), 'doctor'], { + cwd: corruptRoot, + encoding: 'utf8' + }) + + assert.equal(result.status, 1) + assert.match(result.stdout, /\[invalid\] Standalone schema validators/) + assert.match(result.stderr, /Archify is not ready: 1 runtime check failed\./) +}) + +test('cli: examples renders from an installed skill', () => { + const installedRoot = path.join(tmp, 'installed-skill') + copyInstalledSkill(installedRoot) + + const result = spawnSync(process.execPath, [path.join(installedRoot, 'bin/archify.mjs'), 'examples'], { + cwd: installedRoot, + encoding: 'utf8' + }) + + assert.equal(result.status, 0, result.stderr) + for (const output of [ + 'workflow-agent-tool-call-rendered.html', + 'sequence-cache-miss-request.html', + 'dataflow-product-analytics.html', + 'lifecycle-agent-run.html', + 'web-app-rendered.html' + ]) { + assert.equal(fs.existsSync(path.join(installedRoot, 'examples', output)), true, output) + } +}) + +test('cli: demo creates a ready-to-open diagram in a chosen directory', () => { + const outputDirectory = path.join(tmp, 'my-demo') + const output = path.join(outputDirectory, 'archify-demo.html') + const result = run(['demo', outputDirectory]) + + assert.equal(result.status, 0, result.stderr) + assert.equal(fs.existsSync(output), true) + assert.match(fs.readFileSync(output, 'utf8'), /Sample Web App Diagram/) + assert.match(result.stdout, new RegExp(`Demo ready: ${output.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`)) + assert.match(result.stdout, /Next: open the HTML in your browser/) + assert.match(result.stdout, /archify render architecture/) +}) + +test('cli: demo defaults to the current directory', () => { + const workingDirectory = path.join(tmp, 'default-demo') + fs.mkdirSync(workingDirectory) + const result = run(['demo'], { cwd: workingDirectory }) + + assert.equal(result.status, 0, result.stderr) + assert.equal(fs.existsSync(path.join(workingDirectory, 'archify-demo.html')), true) +}) + +test('cli: render writes a diagram html file', () => { + const out = path.join(tmp, 'workflow.html') + const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json') + const result = run(['render', 'workflow', input, out]) + assert.equal(result.status, 0, result.stderr) + assert.equal(fs.existsSync(out), true) + assert.match(fs.readFileSync(out, 'utf8'), /Agent Tool Call Workflow/) +}) + +test('cli: check validates rendered html', () => { + const out = path.join(tmp, 'workflow-check.html') + const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json') + assert.equal(run(['render', 'workflow', input, out]).status, 0) + + const result = run(['check', out]) + assert.equal(result.status, 0, result.stderr) + assert.match(result.stdout, /"ok": true/) +}) + +test('cli: validate emits structured json without keeping html output', () => { + const input = path.join(skillRoot, 'examples/agent-tool-call.workflow.json') + const before = new Set(fs.readdirSync(tmp)) + const result = run(['validate', 'workflow', input, '--json']) + assert.equal(result.status, 0, result.stderr) + const parsed = JSON.parse(result.stdout) + assert.equal(parsed.ok, true) + assert.equal(parsed.type, 'workflow') + assert.equal(parsed.checks.length, 4) + assert.deepEqual(new Set(fs.readdirSync(tmp)), before) +}) + +test('cli: inspect emits architecture layout json', () => { + const input = path.join(skillRoot, 'examples/web-app.architecture.json') + const result = run(['inspect', 'architecture', input]) + assert.equal(result.status, 0, result.stderr) + const parsed = JSON.parse(result.stdout) + assert.equal(parsed.ok, true) + assert.equal(parsed.diagram_type, 'architecture') + assert.equal(parsed.layout.mode, 'free') + assert.ok(parsed.components.length >= 5) + assert.ok(parsed.connections.length >= 1) +}) + +test('cli: validate returns renderer errors for bad input', () => { + const input = path.join(tmp, 'bad.workflow.json') + const validateTmp = path.join(tmp, 'validate-failure-tmp') + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples/agent-tool-call.workflow.json'), 'utf8')) + doc.edges[0].to = 'ghost' + fs.writeFileSync(input, JSON.stringify(doc)) + fs.mkdirSync(validateTmp) + + const result = run(['validate', 'workflow', input], { + env: { ...process.env, TMPDIR: validateTmp } + }) + assert.notEqual(result.status, 0) + assert.match(result.stderr, /unknown target "ghost"/) + assert.deepEqual(fs.readdirSync(validateTmp), []) +}) + +test('cli: validate rejects an unknown type without leaking a temp directory', () => { + const validateTmp = path.join(tmp, 'validate-unknown-type-tmp') + fs.mkdirSync(validateTmp) + + const result = run(['validate', 'unknown', 'ignored.json'], { + env: { + ...process.env, + TMPDIR: validateTmp, + TMP: validateTmp, + TEMP: validateTmp + } + }) + + assert.equal(result.status, 2) + assert.match(result.stderr, /Unknown diagram type "unknown"/) + assert.deepEqual(fs.readdirSync(validateTmp), []) +}) + +process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true })) diff --git a/.agents/skills/archify/test/degraded.test.mjs b/.agents/skills/archify/test/degraded.test.mjs new file mode 100644 index 00000000..053f8731 --- /dev/null +++ b/.agents/skills/archify/test/degraded.test.mjs @@ -0,0 +1,166 @@ +// Installation contract: the shipped skill performs full JSON Schema +// validation without node_modules. AJV is a build-time dependency only; its +// standalone validators are committed and included in the distribution. +// +// A malformed-but-JSON-legal document must EXIT +// NON-ZERO with a friendly message — never crash (TypeError / is not a +// function) and never write NaN/undefined into the HTML. A random VALID +// perturbation of an example must still render (exit 0, no NaN). +// +// node --test test/*.test.mjs + +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-degraded-')) + +const EXAMPLES = { + workflow: 'agent-tool-call.workflow.json', + sequence: 'cache-miss-request.sequence.json', + dataflow: 'product-analytics.dataflow.json', + lifecycle: 'agent-run.lifecycle.json', + architecture: 'web-app.architecture.json' +} + +const installedRoot = path.join(tmp, 'installed-skill') +fs.cpSync(skillRoot, installedRoot, { + recursive: true, + filter(source) { + const rel = path.relative(skillRoot, source) + return ( + rel !== 'node_modules' && + !rel.startsWith(`node_modules${path.sep}`) && + rel !== 'test' && + !rel.startsWith(`test${path.sep}`) + ) + } +}) + +function render(mode, doc) { + const input = path.join(tmp, `in-${Math.random().toString(36).slice(2)}.json`) + const out = path.join(tmp, 'out.html') + fs.writeFileSync(input, JSON.stringify(doc)) + if (fs.existsSync(out)) fs.rmSync(out) + let code = 0 + let stderr = '' + try { + execFileSync('node', [path.join(installedRoot, `renderers/${mode}/render-${mode}.mjs`), input, out], { + stdio: ['ignore', 'ignore', 'pipe'] + }) + } catch (err) { + code = err.status ?? 1 + stderr = String(err.stderr || '') + } + const html = fs.existsSync(out) ? fs.readFileSync(out, 'utf8') : '' + return { code, stderr, html } +} + +function assertFriendlyFailure(mode, doc, label) { + const { code, stderr, html } = render(mode, doc) + assert.notEqual(code, 0, `${label}: expected non-zero exit`) + assert.doesNotMatch( + stderr, + /TypeError|RangeError|is not a function|Cannot read/, + `${label}: crashed instead of reporting friendly error:\n${stderr}` + ) + assert.doesNotMatch(html, /NaN|undefined/, `${label}: wrote NaN/undefined into HTML`) +} + +// ---- type-wrong-but-JSON-legal documents per mode ---- +const ARRAY_FIELDS = { + workflow: ['lanes', 'phases', 'groups', 'mainPath', 'nodes', 'edges', 'cards'], + sequence: ['participants', 'messages', 'segments', 'activations', 'cards'], + dataflow: ['stages', 'nodes', 'flows', 'cards'], + lifecycle: ['lanes', 'states', 'transitions', 'cards'], + architecture: ['components', 'boundaries', 'connections', 'cards'] +} + +for (const [mode, fields] of Object.entries(ARRAY_FIELDS)) { + for (const field of fields) { + test(`${mode}: ${field} as a string fails friendly`, () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8')) + if (!(field in doc)) return // optional field absent in this example + doc[field] = 'oops' + assertFriendlyFailure(mode, doc, `${mode}.${field}`) + }) + } + test(`${mode}: scalar meta fails friendly`, () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8')) + doc.meta = 42 + assertFriendlyFailure(mode, doc, `${mode}.meta`) + }) +} + +// ---- missing-coordinate fields must not yield NaN coordinates ---- +test('workflow: node missing col never writes NaN', () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.workflow), 'utf8')) + delete doc.nodes[0].col + assertFriendlyFailure('workflow', doc, 'workflow node no col') +}) +test('lifecycle: state missing col never writes NaN', () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.lifecycle), 'utf8')) + delete doc.states[0].col + assertFriendlyFailure('lifecycle', doc, 'lifecycle state no col') +}) + +// ---- property test: deterministic VALID perturbations always render ---- +// Seeded PRNG (no Math.random — keeps the test reproducible across runs). +function mulberry32(seed) { + return function next() { + seed |= 0 + seed = (seed + 0x6d2b79f5) | 0 + let t = Math.imul(seed ^ (seed >>> 15), 1 | seed) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +test('property: shuffling node/state order still renders (order-independence)', () => { + for (const mode of ['workflow', 'dataflow', 'lifecycle']) { + const arrKey = mode === 'lifecycle' ? 'states' : 'nodes' + for (let seed = 1; seed <= 8; seed += 1) { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8')) + const rng = mulberry32(seed) + // Fisher–Yates with the seeded PRNG. + const a = doc[arrKey] + for (let i = a.length - 1; i > 0; i -= 1) { + const j = Math.floor(rng() * (i + 1)) + ;[a[i], a[j]] = [a[j], a[i]] + } + const { code, html } = render(mode, doc) + assert.equal(code, 0, `${mode} seed ${seed}: valid shuffle should render (exit 0)`) + assert.doesNotMatch(html, /NaN|undefined>/, `${mode} seed ${seed}: NaN in output`) + } + } +}) + +test('installed skill rejects unknown fields without node_modules', () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES.workflow), 'utf8')) + doc.nodes[0].colour = 'cyan' + const { code, stderr } = render('workflow', doc) + assert.notEqual(code, 0) + assert.match(stderr, /workflow schema validation failed/) + assert.match(stderr, /\/nodes\/0 \(id\/label: "user"\) must NOT have additional properties/) + assert.match(stderr, /"additionalProperty":"colour"/) + assert.doesNotMatch(stderr, /ajv is not installed|skipping JSON-schema validation/) +}) + +for (const mode of Object.keys(EXAMPLES)) { + test(`installed skill retains full ${mode} schema without node_modules`, () => { + const doc = JSON.parse(fs.readFileSync(path.join(skillRoot, 'examples', EXAMPLES[mode]), 'utf8')) + doc.unknownField = true + const { code, stderr } = render(mode, doc) + assert.notEqual(code, 0) + assert.match(stderr, new RegExp(`${mode} schema validation failed`)) + assert.match(stderr, /"additionalProperty":"unknownField"/) + }) +} + +process.on('exit', () => fs.rmSync(tmp, { recursive: true, force: true })) diff --git a/.agents/skills/archify/test/generate-validators.test.mjs b/.agents/skills/archify/test/generate-validators.test.mjs new file mode 100644 index 00000000..6dd2981f --- /dev/null +++ b/.agents/skills/archify/test/generate-validators.test.mjs @@ -0,0 +1,36 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { spawnSync } from 'node:child_process' +import fs from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') + +test('validator freshness check accepts CRLF checkouts', () => { + const scratch = fs.mkdtempSync(path.join(skillRoot, 'node_modules', '.archify-validator-check-')) + try { + fs.mkdirSync(path.join(scratch, 'scripts')) + fs.mkdirSync(path.join(scratch, 'renderers', 'shared'), { recursive: true }) + fs.cpSync(path.join(skillRoot, 'schemas'), path.join(scratch, 'schemas'), { recursive: true }) + fs.copyFileSync( + path.join(skillRoot, 'scripts', 'generate-validators.mjs'), + path.join(scratch, 'scripts', 'generate-validators.mjs') + ) + + const validator = fs.readFileSync(path.join(skillRoot, 'renderers', 'shared', 'generated-validators.mjs'), 'utf8') + fs.writeFileSync( + path.join(scratch, 'renderers', 'shared', 'generated-validators.mjs'), + validator.replace(/\r\n?|\n/g, '\r\n') + ) + + const result = spawnSync(process.execPath, [path.join(scratch, 'scripts', 'generate-validators.mjs'), '--check'], { + encoding: 'utf8' + }) + + assert.equal(result.status, 0, result.stderr) + } finally { + fs.rmSync(scratch, { recursive: true, force: true }) + } +}) diff --git a/.agents/skills/archify/test/geometry.test.mjs b/.agents/skills/archify/test/geometry.test.mjs new file mode 100644 index 00000000..6ddb6f7b --- /dev/null +++ b/.agents/skills/archify/test/geometry.test.mjs @@ -0,0 +1,249 @@ +// Unit tests for the pure geometry/text helpers that every renderer leans on. +// These are exercised only transitively by the golden byte-compares, which +// can't distinguish a geometry regression from an intentional layout change — +// so they get a direct oracle here. Zero deps: node:test + node:assert. +// +// node --test test/*.test.mjs (or: npm test) + +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { + rectsOverlap, + segmentIntersectsRect, + asArray, + isFinitePoint, + anchor, + defaultFromSide, + defaultToSide, + chosenSide, + polylinePath, + roundedPath, + labelPoint, + suggestLabelObstacleFix, + suggestComponentSeparation +} from '../renderers/shared/geometry.mjs' +import { textUnits, applyTemplate } from '../renderers/shared/utils.mjs' + +const rect = (x, y, w, h) => ({ x, y, width: w, height: h, cx: x + w / 2, cy: y + h / 2 }) + +test('rectsOverlap: separated rects do not overlap', () => { + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(20, 0, 10, 10)), false) +}) + +test('rectsOverlap: clearly overlapping rects overlap', () => { + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(5, 5, 10, 10)), true) +}) + +test('rectsOverlap: edge-touching is NOT overlap at gap 0 (<= boundary)', () => { + // a ends at x=10, b starts at x=10 — exactly touching. + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(10, 0, 10, 10), 0), false) +}) + +test('rectsOverlap: positive gap flags rects within that gap as too close', () => { + // 8px apart, required gap 8 → touching the threshold counts as too close. + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(18, 0, 10, 10), 8), false) + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(17, 0, 10, 10), 8), true) +}) + +test('rectsOverlap: negative gap shrinks the hit box (label-collision convention)', () => { + // gap -2 means rects must overlap by MORE than 2px to count — a 1px sliver + // does not. This is the sign convention the label checks rely on. + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(9, 0, 10, 10), -2), false) + assert.equal(rectsOverlap(rect(0, 0, 10, 10), rect(7, 0, 10, 10), -2), true) +}) + +test('segmentIntersectsRect: detects an edge crossing a node box', () => { + assert.equal(segmentIntersectsRect({ start: [0, 5], end: [20, 5] }, rect(8, 0, 4, 10)), true) + assert.equal(segmentIntersectsRect({ start: [0, 20], end: [20, 20] }, rect(8, 0, 4, 10)), false) +}) + +test('asArray coerces non-arrays to [] (degraded-mode guard)', () => { + assert.deepEqual(asArray([1, 2]), [1, 2]) + assert.deepEqual(asArray('oops'), []) + assert.deepEqual(asArray(undefined), []) + assert.deepEqual(asArray(null), []) + assert.deepEqual(asArray({ length: 3 }), []) +}) + +test('isFinitePoint rejects NaN/undefined/Infinity', () => { + assert.equal(isFinitePoint(1, 2, 3, 4), true) + assert.equal(isFinitePoint(1, NaN), false) + assert.equal(isFinitePoint(1, undefined), false) + assert.equal(isFinitePoint(1, Infinity), false) +}) + +test('anchor returns the correct edge midpoint for each side', () => { + const r = rect(100, 100, 40, 20) // cx=120 cy=110 + assert.deepEqual(anchor(r, 'left'), [100, 110]) + assert.deepEqual(anchor(r, 'right'), [140, 110]) + assert.deepEqual(anchor(r, 'top'), [120, 100]) + assert.deepEqual(anchor(r, 'bottom'), [120, 120]) +}) + +test('anchor falls back to the right edge for unknown/auto sides', () => { + const r = rect(100, 100, 40, 20) + assert.deepEqual(anchor(r, 'auto'), [140, 110]) + assert.deepEqual(anchor(r, undefined), [140, 110]) +}) + +test('defaultFromSide / defaultToSide are mirror pairs', () => { + const a = { cx: 0, cy: 0 } + const right = { cx: 100, cy: 0 } + assert.equal(defaultFromSide(a, right), 'right') + assert.equal(defaultToSide(a, right), 'left') + const below = { cx: 0, cy: 100 } + assert.equal(defaultFromSide(a, below), 'bottom') + assert.equal(defaultToSide(a, below), 'top') +}) + +test('chosenSide treats explicit "auto" as "use the geometric fallback"', () => { + assert.equal(chosenSide('left', 'right'), 'left') + assert.equal(chosenSide('auto', 'right'), 'right') + assert.equal(chosenSide(undefined, 'right'), 'right') +}) + +test('polylinePath emits M then L commands', () => { + assert.equal( + polylinePath([ + [0, 0], + [10, 0], + [10, 10] + ]), + 'M 0 0 L 10 0 L 10 10' + ) +}) + +test('roundedPath degrades to a polyline for <3 points or radius<=0', () => { + assert.equal( + roundedPath( + [ + [0, 0], + [10, 0] + ], + 10 + ), + 'M 0 0 L 10 0' + ) + assert.equal( + roundedPath( + [ + [0, 0], + [10, 0], + [10, 10] + ], + 0 + ), + 'M 0 0 L 10 0 L 10 10' + ) +}) + +test('roundedPath inserts a quadratic corner and never emits NaN', () => { + const d = roundedPath( + [ + [0, 0], + [100, 0], + [100, 100] + ], + 10 + ) + assert.match(d, /Q 100 0/) // corner pivots on the bend point + assert.doesNotMatch(d, /NaN/) +}) + +test('roundedPath clamps radius to half the shorter adjacent segment', () => { + // 6px segments with radius 10 → r clamps to 3; no overshoot / NaN. + const d = roundedPath( + [ + [0, 0], + [6, 0], + [6, 6] + ], + 10 + ) + assert.doesNotMatch(d, /NaN/) + assert.match(d, /^M 0 0/) +}) + +test('labelPoint: 2-point path is the midpoint lifted 10px, plus offsets', () => { + assert.deepEqual( + labelPoint({}, [ + [0, 100], + [100, 100] + ]), + [50, 90] + ) + assert.deepEqual( + labelPoint({ labelDx: 5, labelDy: -4 }, [ + [0, 100], + [100, 100] + ]), + [55, 86] + ) +}) + +test('labelPoint: labelSegment selects a segment and clamps to range', () => { + const pts = [ + [0, 0], + [100, 0], + [100, 100], + [200, 100] + ] + // segment 0 → midpoint of pts[0],pts[1] = (50,0) lifted 10 + assert.deepEqual(labelPoint({ labelSegment: 0 }, pts), [50, -10]) + // segment 99 clamps to the last segment + assert.deepEqual(labelPoint({ labelSegment: 99 }, pts), [150, 90]) +}) + +test('labelPoint: explicit labelAt wins outright', () => { + assert.deepEqual( + labelPoint({ labelAt: [7, 8] }, [ + [0, 0], + [100, 0] + ]), + [7, 8] + ) +}) + +test('textUnits: ASCII=1, CJK=2, mixed sums, fullwidth supplementary=2', () => { + assert.equal(textUnits('abc'), 3) + assert.equal(textUnits('中文'), 4) + assert.equal(textUnits('a中'), 3) + assert.equal(textUnits(''), 0) + assert.equal(textUnits(null), 0) + assert.equal(textUnits('𠀀'), 2) // CJK Ext-B (supplementary plane) + assert.equal(textUnits('🚀'), 2) // emoji +}) + +test('suggestLabelObstacleFix includes rects and labelAt/labelDy hints', () => { + const labelRect = { x: 100, y: 180, width: 48, height: 14, label: '写入' } + const obstacle = { id: 'memtool', x: 30, y: 130, width: 230, height: 58 } + const hint = suggestLabelObstacleFix(labelRect, 124, 188, obstacle) + assert.match(hint, /label rect: \[100, 180, 48, 14\]/) + assert.match(hint, /component "memtool"/) + assert.match(hint, /Suggested fix: labelAt/) + assert.match(hint, /labelDy \+\d+/) +}) + +test('suggestComponentSeparation proposes nudged pos', () => { + const a = { id: 'api', x: 100, y: 200, width: 120, height: 60 } + const b = { id: 'db', x: 150, y: 200, width: 120, height: 60 } + const hint = suggestComponentSeparation(a, b, 8) + assert.match(hint, /move "db" pos to \[228, 200\]/) +}) + +test('applyTemplate preserves dollar sequences in titles', () => { + const template = `[PROJECT NAME] Architecture Diagram +

[PROJECT NAME] Architecture

+

[Subtitle description]

+ +
+[Project Name] • [Additional metadata]` + const html = applyTemplate(template, { + title: 'Plan $$50 tier', + subtitle: 'test', + footer: 'f', + svg: '', + cards: '' + }) + assert.match(html, /Plan \$\$50 tier/) +}) diff --git a/.agents/skills/archify/test/golden.mjs b/.agents/skills/archify/test/golden.mjs new file mode 100644 index 00000000..4b397e8e --- /dev/null +++ b/.agents/skills/archify/test/golden.mjs @@ -0,0 +1,215 @@ +// Golden-file harness for the archify renderers. No test framework needed: +// renderers are deterministic, so fresh renders must match the checked-in +// example HTML aside from platform checkout line endings. Also covers schema enforcement (negative cases), +// template freshness of the architecture-mode example, and version sync. +// +// Run from the skill folder: npm test + +import { execFileSync } from 'node:child_process' +import fs from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) +const skillRoot = path.resolve(__dirname, '..') +const examplesRoot = path.join(skillRoot, 'examples') +const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'archify-test-')) + +let failures = 0 + +function check(name, ok, detail) { + if (ok) { + console.log(` ok ${name}`) + } else { + failures += 1 + console.error(` FAIL ${name}${detail ? ` — ${detail}` : ''}`) + } +} + +function render(mode, inputPath, outPath) { + execFileSync('node', [path.join(skillRoot, `renderers/${mode}/render-${mode}.mjs`), inputPath, outPath], { + stdio: ['ignore', 'ignore', 'pipe'] + }) +} + +function normalizeNewlines(text) { + return text.replace(/\r\n?/g, '\n') +} + +// --------------------------------------------------------------------------- +console.log('golden renders (renderer output must match checked-in examples)') + +const GOLDEN = [ + ['workflow', 'agent-tool-call.workflow.json', 'workflow-agent-tool-call-rendered.html'], + ['sequence', 'cache-miss-request.sequence.json', 'sequence-cache-miss-request.html'], + ['dataflow', 'product-analytics.dataflow.json', 'dataflow-product-analytics.html'], + ['lifecycle', 'agent-run.lifecycle.json', 'lifecycle-agent-run.html'], + ['architecture', 'web-app.architecture.json', 'web-app-rendered.html'] +] + +for (const [mode, input, golden] of GOLDEN) { + const out = path.join(tmp, golden) + try { + render(mode, path.join(examplesRoot, input), out) + const fresh = fs.readFileSync(out, 'utf8') + const checked = fs.readFileSync(path.join(examplesRoot, golden), 'utf8') + check( + `${mode}: ${golden}`, + normalizeNewlines(fresh) === normalizeNewlines(checked), + `fresh render differs from examples/${golden}; if the change is intentional, re-render the examples and commit them` + ) + } catch (err) { + check(`${mode}: ${golden}`, false, String(err.stderr || err.message).slice(0, 300)) + } +} + +// --------------------------------------------------------------------------- +console.log('schema enforcement (invalid JSON must fail with a path-prefixed message)') + +function expectFailure(name, mode, mutate, expectInMessage) { + const base = JSON.parse( + fs.readFileSync(path.join(skillRoot, 'examples', GOLDEN.find(([m]) => m === mode)[1]), 'utf8') + ) + mutate(base) + const input = path.join(tmp, `neg-${name.replace(/[^a-z0-9]+/gi, '-')}.json`) + fs.writeFileSync(input, JSON.stringify(base)) + try { + render(mode, input, path.join(tmp, 'neg-out.html')) + check(name, false, 'renderer exited 0 on invalid input') + } catch (err) { + const message = String(err.stderr || err.message) + check(name, message.includes(expectInMessage), `expected "${expectInMessage}" in:\n${message.slice(0, 300)}`) + } +} + +expectFailure( + 'card dot outside enum', + 'workflow', + (d) => { + d.cards[0].dot = 'pink' + }, + '/cards/0/dot' +) +expectFailure( + 'node id starting with a digit', + 'workflow', + (d) => { + d.nodes[0].id = '1user' + }, + 'pattern' +) +expectFailure( + 'extra property rejected', + 'workflow', + (d) => { + d.nodes[0].colour = 'red' + }, + 'additional properties' +) +expectFailure( + 'column beyond layout maximum', + 'workflow', + (d) => { + d.nodes[0].col = 7 + }, + '<= 5' +) +expectFailure( + 'missing schema_version', + 'sequence', + (d) => { + delete d.schema_version + }, + 'schema_version' +) +expectFailure( + 'cross-lane state overlap', + 'lifecycle', + (d) => { + const approval = d.states.find((s) => s.id === 'approval') + const failed = d.states.find((s) => s.id === 'failed') + delete failed.yOffset + failed.col = approval.col + }, + 'less than 10px apart' +) +expectFailure( + 'zero component width rejected by schema', + 'architecture', + (d) => { + d.components[0].size = [0, 60] + }, + '/components/0/size/0' +) +expectFailure( + 'zero component height rejected by schema', + 'architecture', + (d) => { + d.components[0].size = [120, 0] + }, + '/components/0/size/1' +) +expectFailure( + 'negative component width rejected by schema', + 'architecture', + (d) => { + d.components[0].size = [-1, 60] + }, + '/components/0/size/0' +) + +// --------------------------------------------------------------------------- +console.log('template freshness (architecture example must carry the current template)') + +function blocks(html, tag) { + const re = new RegExp(`<${tag}[^>]*>[\\s\\S]*?<\\/${tag}>`, 'g') + return html.match(re) || [] +} + +const template = fs.readFileSync(path.join(skillRoot, 'assets/template.html'), 'utf8') +const webApp = fs.readFileSync(path.join(examplesRoot, 'web-app-rendered.html'), 'utf8') +// + + + + + +
+ +
+
+
+

WebChat Remesh DDD + CQRS Architecture

+
+

Exact ChatRoom port, seven Runtime owners, immutable peer wire, disjoint background service RPC

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + Page / Origin Contexts + + + Browser Extension Boundary + + + Headless Remesh DDD + CQRS Runtime + + + Strict Public Peer Contract + + + Private Provider Boundary + + + Disjoint Background Service RPC + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Web Pages + domain A / B / ... + + + + Content Chat UI + Query / Command / Event + + + + Extension Controls + options + reconnect site + + + + Application Domains + input + display read models + no Runtime write authority + + + + MessageStore Facade + insert / query / clear / watch + decode then filter + + + + Database<Schema> + typed tx + commit watch + sole persistence extern + + + + Default Backends + IndexedDB + Memory + one parity suite + + + + Notification Domain + first remote live insert + request-local failure + + + + Notification RPC + WEB_CHAT_NOTIFICATION_V1 + versioned namespace + + + + Notification Provider + OS notification side effect + + + + AppAction Domain + open extension options + + + + AppAction RPC + WEB_CHAT_APP_ACTION_V1 + versioned namespace + + + + AppAction Provider + options-page side effect + + + + ChatRoomExtern + exact eight application methods + only application port + + + + Stateless Runtime Client + event bridge + persist / ACK + no join / session cache + + + + Page History Provider + AbortSignal store query + one selected page + + + + Internal comctx + clean-cut Commands / Events + not peer protocol + + + + Background Coordinator + port liveness + health + host phase + + + + Host Owner + Offscreen / Background Page + one disposable host + + + + Server Composition + graph + Extern injection only + no network truth + + + + Lifecycle Domain + page leases + 5s grace + unique owner + + + + Connection Domain + join / leave / reconnect / generation + unique owner + + + + Session Domain + sessions + logical presence + HLC + unique owner + + + + Wire Domain + trusted room / source + queues + anti-corruption boundary + + + + World Domain + registry + presence snapshots + unique owner + + + + History Domain + sessions + cutoffs + cursors + batches + unique owner + + + + Delivery Domain + sequence + un-ACK buffer + replay + 512 events / 8MiB + + + + Strict Peer Protocol + frozen except presence lifecycle + Chat/history/World frozen + + + + Native Wire Codec + base64(deflate(UTF8(JSON))) + public reference + + + + RoomTransportExtern + private provider capability + Wire only + + + + Artico Provider Impl + replaceable WebRTC adapter + + + + Signaling Network + peer discovery + + + + Remote v2 Peers + isolated World / Chat rooms + + + + inject + + Q / C / E + + reconnect Command + + + + + + records + + scoped transactions + + same contract + + commit invalidation + + first insert + + push + + one request + + open options + + one request + + persist before ACK + + decoded Chat records + + supply settlement + + ensure host + + probe / replace + + single host + + + + + + validated live admit + + commit / recover + + start / cancel + + + binding Query + + full presence + + history frames + + strict schema / limits + + reference codec + + + impl + + discover + + encrypted P2P + + + Legend + + External + + Frontend + + Backend + + Database + + Message bus + + Security + + Cloud + +
+ + +
+
+
+
+

Application and persistence authority

+
+
    +
  • • UI reads Queries, issues Commands, and subscribes to Events; it never writes Runtime facts
  • +
  • • ChatRoomExtern is the only application port and remains exactly eight methods
  • +
  • • The Runtime client is state-free; it persists inbound facts before ACK and supplies history
  • +
  • • Application owns join input, MessageRecord, projection, reaction LWW, and (hlc,id) ordering
  • +
  • • Database<Schema> is the sole replaceable persistence Extern; MessageStore stays concrete
  • +
+
+ +
+
+
+

Seven unique Runtime owners

+
+
    +
  • • Lifecycle: domain leases/grace; Connection: join/leave/reconnect/generation
  • +
  • • Session: committed sessions/snapshot/id/HLC; World: presence
  • +
  • • History owns requesters, providers, cutoffs, cursors, and batches; Delivery owns sequence, un-ACK, and replay
  • +
  • • Session validates live Chat before Delivery; History queries Session binding
  • +
  • • Wire: trusted room/source, immutable protocol translation, queues, provider callbacks
  • +
  • • Coordinator owns physical port/host facts; Server only constructs the graph; no catch-all owner
  • +
+
+ +
+
+
+

CQRS and module rules

+
+
    +
  • • Command is intent; Query is pure read; Event is a past fact; Effect owns I/O
  • +
  • • Extern injects capability and never becomes a parallel cache or recovery authority
  • +
  • • A Remesh.module is real, reusable, configurable, and semantically isomorphic in 2+ hosts
  • +
  • • Shared authority comes from one Domain instance, not repeated module instances
  • +
  • • Migration moves one owner then deletes the old path: no alias, fallback, or dual write
  • +
+
+ +
+
+
+

Immutable peer wire and private provider

+
+
    +
  • • Current v2 message types, schemas, limits, codec, canonical JSON, and bytes stay unchanged
  • +
  • • Internal comctx may clean-cut change because it is not peer protocol
  • +
  • • Notification and AppAction use structurally disjoint versioned RPC namespaces
  • +
  • • Wire is the anti-corruption boundary: trusted source, strict validation, ordered queues
  • +
  • • RoomTransportExtern is Runtime-private and visible only to Wire
  • +
  • • Artico is one provider implementation; fake and Artico run the same contract suite
  • +
+
+
+ + + +
+ + + + diff --git a/docs/architecture/webchat-target.architecture.json b/docs/architecture/webchat-target.architecture.json new file mode 100644 index 00000000..81326642 --- /dev/null +++ b/docs/architecture/webchat-target.architecture.json @@ -0,0 +1,804 @@ +{ + "schema_version": 1, + "diagram_type": "architecture", + "meta": { + "title": "WebChat Remesh DDD + CQRS Architecture", + "subtitle": "Exact ChatRoom port, seven Runtime owners, immutable peer wire, disjoint background service RPC", + "output": "webchat-target-architecture.html", + "viewBox": [3000, 1320] + }, + "components": [ + { + "id": "web-pages", + "type": "external", + "label": "Web Pages", + "sublabel": "domain A / B / ...", + "pos": [20, 260], + "size": [130, 68] + }, + { + "id": "content-ui", + "type": "frontend", + "label": "Content Chat UI", + "sublabel": "Query / Command / Event", + "pos": [190, 100], + "size": [180, 72] + }, + { + "id": "controls-ui", + "type": "frontend", + "label": "Extension Controls", + "sublabel": "options + reconnect site", + "pos": [190, 310], + "size": [180, 72] + }, + { + "id": "page-domains", + "type": "backend", + "label": "Application Domains", + "sublabel": "input + display read models", + "pos": [420, 100], + "size": [205, 76], + "tag": "no Runtime write authority" + }, + { + "id": "origin-store", + "type": "backend", + "label": "MessageStore Facade", + "sublabel": "insert / query / clear / watch", + "pos": [430, 420], + "size": [185, 72], + "tag": "decode then filter" + }, + { + "id": "database", + "type": "database", + "label": "Database", + "sublabel": "typed tx + commit watch", + "pos": [430, 570], + "size": [185, 68], + "tag": "sole persistence extern" + }, + { + "id": "database-backends", + "type": "database", + "label": "Default Backends", + "sublabel": "IndexedDB + Memory", + "pos": [430, 720], + "size": [185, 68], + "tag": "one parity suite" + }, + { + "id": "notifications", + "type": "frontend", + "label": "Notification Domain", + "sublabel": "first remote live insert", + "pos": [430, 870], + "size": [185, 68], + "tag": "request-local failure" + }, + { + "id": "notification-rpc", + "type": "messagebus", + "label": "Notification RPC", + "sublabel": "WEB_CHAT_NOTIFICATION_V1", + "pos": [675, 870], + "size": [200, 68], + "tag": "versioned namespace" + }, + { + "id": "notification-provider", + "type": "backend", + "label": "Notification Provider", + "sublabel": "OS notification side effect", + "pos": [925, 870], + "size": [200, 68] + }, + { + "id": "app-action", + "type": "frontend", + "label": "AppAction Domain", + "sublabel": "open extension options", + "pos": [430, 1010], + "size": [185, 68] + }, + { + "id": "app-action-rpc", + "type": "messagebus", + "label": "AppAction RPC", + "sublabel": "WEB_CHAT_APP_ACTION_V1", + "pos": [675, 1010], + "size": [200, 68], + "tag": "versioned namespace" + }, + { + "id": "app-action-provider", + "type": "backend", + "label": "AppAction Provider", + "sublabel": "options-page side effect", + "pos": [925, 1010], + "size": [200, 68] + }, + { + "id": "chatroom-extern", + "type": "security", + "label": "ChatRoomExtern", + "sublabel": "exact eight application methods", + "pos": [675, 100], + "size": [200, 76], + "tag": "only application port" + }, + { + "id": "runtime-client", + "type": "backend", + "label": "Stateless Runtime Client", + "sublabel": "event bridge + persist / ACK", + "pos": [925, 100], + "size": [205, 76], + "tag": "no join / session cache" + }, + { + "id": "history-provider", + "type": "backend", + "label": "Page History Provider", + "sublabel": "AbortSignal store query", + "pos": [935, 440], + "size": [185, 72], + "tag": "one selected page" + }, + { + "id": "comctx", + "type": "messagebus", + "label": "Internal comctx", + "sublabel": "clean-cut Commands / Events", + "pos": [1175, 100], + "size": [190, 76], + "tag": "not peer protocol" + }, + { + "id": "coordinator", + "type": "backend", + "label": "Background Coordinator", + "sublabel": "port liveness + health + host phase", + "pos": [1175, 330], + "size": [190, 72] + }, + { + "id": "host-owner", + "type": "cloud", + "label": "Host Owner", + "sublabel": "Offscreen / Background Page", + "pos": [1175, 540], + "size": [190, 72], + "tag": "one disposable host" + }, + { + "id": "server", + "type": "backend", + "label": "Server Composition", + "sublabel": "graph + Extern injection only", + "pos": [1415, 100], + "size": [200, 76], + "tag": "no network truth" + }, + { + "id": "lifecycle-domain", + "type": "backend", + "label": "Lifecycle Domain", + "sublabel": "page leases + 5s grace", + "pos": [1415, 300], + "size": [200, 76], + "tag": "unique owner" + }, + { + "id": "connection-domain", + "type": "backend", + "label": "Connection Domain", + "sublabel": "join / leave / reconnect / generation", + "pos": [1655, 300], + "size": [205, 76], + "tag": "unique owner" + }, + { + "id": "session-domain", + "type": "backend", + "label": "Session Domain", + "sublabel": "sessions + logical presence + HLC", + "pos": [1900, 300], + "size": [205, 76], + "tag": "unique owner" + }, + { + "id": "wire-domain", + "type": "messagebus", + "label": "Wire Domain", + "sublabel": "trusted room / source + queues", + "pos": [2145, 300], + "size": [205, 76], + "tag": "anti-corruption boundary" + }, + { + "id": "world-domain", + "type": "backend", + "label": "World Domain", + "sublabel": "registry + presence snapshots", + "pos": [1900, 520], + "size": [205, 76], + "tag": "unique owner" + }, + { + "id": "history-domain", + "type": "backend", + "label": "History Domain", + "sublabel": "sessions + cutoffs + cursors + batches", + "pos": [1655, 720], + "size": [205, 76], + "tag": "unique owner" + }, + { + "id": "delivery-domain", + "type": "messagebus", + "label": "Delivery Domain", + "sublabel": "sequence + un-ACK buffer + replay", + "pos": [1415, 720], + "size": [205, 76], + "tag": "512 events / 8MiB" + }, + { + "id": "wire-protocol", + "type": "security", + "label": "Strict Peer Protocol", + "sublabel": "frozen except presence lifecycle", + "pos": [2145, 80], + "size": [205, 76], + "tag": "Chat/history/World frozen" + }, + { + "id": "wire-codec", + "type": "messagebus", + "label": "Native Wire Codec", + "sublabel": "base64(deflate(UTF8(JSON)))", + "pos": [2390, 80], + "size": [205, 76], + "tag": "public reference" + }, + { + "id": "transport-extern", + "type": "security", + "label": "RoomTransportExtern", + "sublabel": "private provider capability", + "pos": [2390, 300], + "size": [205, 76], + "tag": "Wire only" + }, + { + "id": "artico", + "type": "cloud", + "label": "Artico Provider Impl", + "sublabel": "replaceable WebRTC adapter", + "pos": [2630, 300], + "size": [205, 76] + }, + { + "id": "signaling", + "type": "external", + "label": "Signaling Network", + "sublabel": "peer discovery", + "pos": [2630, 20], + "size": [205, 68] + }, + { + "id": "remote-peers", + "type": "external", + "label": "Remote v2 Peers", + "sublabel": "isolated World / Chat rooms", + "pos": [2630, 710], + "size": [205, 72] + } + ], + "boundaries": [ + { + "kind": "region", + "label": "Page / Origin Contexts", + "wraps": [ + "content-ui", + "controls-ui", + "page-domains", + "origin-store", + "database", + "database-backends", + "notifications", + "app-action", + "chatroom-extern", + "runtime-client", + "history-provider" + ] + }, + { + "kind": "region", + "label": "Browser Extension Boundary", + "wraps": [ + "content-ui", + "controls-ui", + "page-domains", + "origin-store", + "database", + "database-backends", + "notifications", + "notification-rpc", + "notification-provider", + "app-action", + "app-action-rpc", + "app-action-provider", + "chatroom-extern", + "runtime-client", + "history-provider", + "comctx", + "coordinator", + "host-owner", + "server", + "lifecycle-domain", + "connection-domain", + "session-domain", + "world-domain", + "history-domain", + "delivery-domain", + "wire-domain", + "wire-protocol", + "wire-codec", + "transport-extern", + "artico" + ], + "pad": 44 + }, + { + "kind": "security-group", + "label": "Headless Remesh DDD + CQRS Runtime", + "wraps": [ + "lifecycle-domain", + "connection-domain", + "session-domain", + "world-domain", + "history-domain", + "delivery-domain", + "wire-domain" + ] + }, + { + "kind": "security-group", + "label": "Strict Public Peer Contract", + "wraps": ["wire-protocol", "wire-codec"] + }, + { + "kind": "security-group", + "label": "Private Provider Boundary", + "wraps": ["transport-extern", "artico"] + }, + { + "kind": "security-group", + "label": "Disjoint Background Service RPC", + "wraps": ["notification-rpc", "notification-provider", "app-action-rpc", "app-action-provider"] + } + ], + "connections": [ + { + "from": "web-pages", + "to": "content-ui", + "label": "inject", + "variant": "emphasis", + "fromSide": "right", + "toSide": "left", + "route": "orthogonal-h", + "via": [ + [170, 294], + [170, 136] + ], + "labelAt": [169, 216] + }, + { + "from": "content-ui", + "to": "page-domains", + "label": "Q / C / E", + "variant": "emphasis", + "fromSide": "right", + "toSide": "left", + "route": "orthogonal-h", + "via": [ + [395, 136], + [395, 138] + ] + }, + { + "from": "controls-ui", + "to": "page-domains", + "label": "reconnect Command", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [395, 346], + [395, 220], + [522, 220] + ], + "labelAt": [458, 213] + }, + { + "from": "page-domains", + "to": "chatroom-extern", + "variant": "emphasis" + }, + { + "from": "chatroom-extern", + "to": "runtime-client", + "variant": "emphasis" + }, + { + "from": "runtime-client", + "to": "comctx", + "variant": "emphasis" + }, + { + "from": "comctx", + "to": "server", + "variant": "emphasis" + }, + { + "from": "page-domains", + "to": "origin-store", + "label": "records", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [522, 300] + }, + { + "from": "origin-store", + "to": "database", + "label": "scoped transactions", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [522, 535] + }, + { + "from": "database", + "to": "database-backends", + "label": "same contract", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [522, 685] + }, + { + "from": "database", + "to": "page-domains", + "label": "commit invalidation", + "fromSide": "left", + "toSide": "left", + "route": "orthogonal-h", + "via": [ + [395, 604], + [395, 138] + ], + "labelAt": [393, 396] + }, + { + "from": "origin-store", + "to": "notifications", + "label": "first insert", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "top", + "route": "orthogonal-v", + "via": [ + [640, 530], + [640, 830], + [522, 830] + ], + "labelAt": [638, 806] + }, + { + "from": "notifications", + "to": "notification-rpc", + "label": "push", + "variant": "dashed" + }, + { + "from": "notification-rpc", + "to": "notification-provider", + "label": "one request", + "variant": "security", + "labelAt": [900, 952] + }, + { + "from": "app-action", + "to": "app-action-rpc", + "label": "open options", + "variant": "dashed", + "labelAt": [645, 1006] + }, + { + "from": "app-action-rpc", + "to": "app-action-provider", + "label": "one request", + "variant": "security", + "labelAt": [900, 1006] + }, + { + "from": "runtime-client", + "to": "origin-store", + "label": "persist before ACK", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "right", + "route": "orthogonal-h", + "via": [ + [900, 240], + [900, 456], + [650, 456] + ], + "labelAt": [775, 449] + }, + { + "from": "history-provider", + "to": "origin-store", + "label": "decoded Chat records", + "fromSide": "left", + "toSide": "right", + "labelAt": [775, 532] + }, + { + "from": "history-provider", + "to": "comctx", + "label": "supply settlement", + "variant": "dashed", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [1145, 476], + [1145, 230], + [1270, 230] + ], + "labelAt": [1205, 255] + }, + { + "from": "runtime-client", + "to": "coordinator", + "label": "ensure host", + "fromSide": "bottom", + "toSide": "top", + "route": "orthogonal-h", + "via": [ + [1150, 215], + [1270, 215], + [1270, 290] + ], + "labelAt": [1208, 208] + }, + { + "from": "coordinator", + "to": "host-owner", + "label": "probe / replace", + "variant": "security", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [1270, 475] + }, + { + "from": "host-owner", + "to": "server", + "label": "single host", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [1390, 576], + [1390, 220], + [1515, 220] + ], + "labelAt": [1453, 213] + }, + { + "from": "server", + "to": "lifecycle-domain", + "fromSide": "bottom", + "toSide": "top" + }, + { + "from": "lifecycle-domain", + "to": "connection-domain", + "variant": "emphasis" + }, + { + "from": "connection-domain", + "to": "session-domain", + "variant": "emphasis" + }, + { + "from": "session-domain", + "to": "wire-domain", + "variant": "emphasis" + }, + { + "from": "session-domain", + "to": "delivery-domain", + "label": "validated live admit", + "variant": "dashed", + "fromSide": "bottom", + "toSide": "top", + "route": "orthogonal-h", + "via": [ + [2002, 620], + [1518, 620] + ], + "labelAt": [1760, 613] + }, + { + "from": "connection-domain", + "to": "world-domain", + "label": "commit / recover", + "fromSide": "bottom", + "toSide": "left", + "route": "orthogonal-h", + "via": [ + [1880, 420], + [1880, 558] + ], + "labelAt": [1878, 487] + }, + { + "from": "connection-domain", + "to": "history-domain", + "label": "start / cancel", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [1758, 535] + }, + { + "from": "history-domain", + "to": "delivery-domain", + "fromSide": "left", + "toSide": "right" + }, + { + "from": "history-domain", + "to": "session-domain", + "label": "binding Query", + "variant": "dashed", + "fromSide": "top", + "toSide": "left", + "route": "orthogonal-h", + "via": [ + [1758, 460], + [1875, 460], + [1875, 338] + ], + "labelAt": [1816, 453] + }, + { + "from": "world-domain", + "to": "wire-domain", + "label": "full presence", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [2370, 558], + [2370, 420], + [2248, 420] + ], + "labelAt": [2308, 413] + }, + { + "from": "history-domain", + "to": "wire-domain", + "label": "history frames", + "variant": "dashed", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [2110, 758], + [2110, 430], + [2248, 430] + ], + "labelAt": [2178, 453] + }, + { + "from": "wire-domain", + "to": "wire-protocol", + "label": "strict schema / limits", + "variant": "security", + "fromSide": "top", + "toSide": "bottom" + }, + { + "from": "wire-domain", + "to": "wire-codec", + "label": "reference codec", + "fromSide": "right", + "toSide": "bottom", + "route": "orthogonal-h", + "via": [ + [2370, 338], + [2370, 205], + [2492, 205] + ], + "labelAt": [2430, 198] + }, + { + "from": "wire-domain", + "to": "transport-extern", + "variant": "emphasis" + }, + { + "from": "transport-extern", + "to": "artico", + "label": "impl", + "variant": "emphasis" + }, + { + "from": "artico", + "to": "signaling", + "label": "discover", + "variant": "dashed", + "fromSide": "top", + "toSide": "bottom" + }, + { + "from": "artico", + "to": "remote-peers", + "label": "encrypted P2P", + "variant": "emphasis", + "fromSide": "bottom", + "toSide": "top", + "labelAt": [2733, 540] + } + ], + "cards": [ + { + "dot": "cyan", + "title": "Application and persistence authority", + "items": [ + "UI reads Queries, issues Commands, and subscribes to Events; it never writes Runtime facts", + "ChatRoomExtern is the only application port and remains exactly eight methods", + "The Runtime client is state-free; it persists inbound facts before ACK and supplies history", + "Application owns join input, MessageRecord, projection, reaction LWW, and (hlc,id) ordering", + "Database is the sole replaceable persistence Extern; MessageStore stays concrete" + ] + }, + { + "dot": "emerald", + "title": "Seven unique Runtime owners", + "items": [ + "Lifecycle: domain leases/grace; Connection: join/leave/reconnect/generation", + "Session: committed sessions/snapshot/id/HLC; World: presence", + "History owns requesters, providers, cutoffs, cursors, and batches; Delivery owns sequence, un-ACK, and replay", + "Session validates live Chat before Delivery; History queries Session binding", + "Wire: trusted room/source, immutable protocol translation, queues, provider callbacks", + "Coordinator owns physical port/host facts; Server only constructs the graph; no catch-all owner" + ] + }, + { + "dot": "amber", + "title": "CQRS and module rules", + "items": [ + "Command is intent; Query is pure read; Event is a past fact; Effect owns I/O", + "Extern injects capability and never becomes a parallel cache or recovery authority", + "A Remesh.module is real, reusable, configurable, and semantically isomorphic in 2+ hosts", + "Shared authority comes from one Domain instance, not repeated module instances", + "Migration moves one owner then deletes the old path: no alias, fallback, or dual write" + ] + }, + { + "dot": "rose", + "title": "Immutable peer wire and private provider", + "items": [ + "Current v2 message types, schemas, limits, codec, canonical JSON, and bytes stay unchanged", + "Internal comctx may clean-cut change because it is not peer protocol", + "Notification and AppAction use structurally disjoint versioned RPC namespaces", + "Wire is the anti-corruption boundary: trusted source, strict validation, ordered queues", + "RoomTransportExtern is Runtime-private and visible only to Wire", + "Artico is one provider implementation; fake and Artico run the same contract suite" + ] + } + ] +} diff --git a/e2e/chrome-harness.test.ts b/e2e/chrome-harness.test.ts new file mode 100644 index 00000000..44d2963e --- /dev/null +++ b/e2e/chrome-harness.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it, vi } from 'vitest' +import { CdpClient, evaluateRuntimeMessage, terminateOwnedProcesses, withDeadline } from './chrome-harness.ts' + +type FakeEvent = Record +type FakeListener = (event: FakeEvent) => void + +class FakeWebSocket { + static instances: FakeWebSocket[] = [] + + listeners = new Map() + sent: string[] = [] + + constructor() { + FakeWebSocket.instances.push(this) + } + + addEventListener(type: string, listener: FakeListener): void { + const listeners = this.listeners.get(type) ?? [] + listeners.push(listener) + this.listeners.set(type, listeners) + } + + emit(type: string, event: FakeEvent = {}): void { + this.listeners.get(type)?.forEach((listener) => listener(event)) + } + + send(message: string): void { + this.sent.push(message) + } + + close(): void { + this.emit('close') + } +} + +describe('Chrome Runtime harness', () => { + it('propagates sender-visible Runtime message rejection', async () => { + const evaluate = vi.fn<(expression: string) => Promise>().mockRejectedValue(new Error('raw rejected')) + + await expect(evaluateRuntimeMessage(evaluate, null)).rejects.toThrow('raw rejected') + expect(evaluate).toHaveBeenCalledWith('chrome.runtime.sendMessage(null)') + }) + + it('rejects a CDP command that never settles and clears its pending request', async () => { + const client = new CdpClient('ws://test', { WebSocketImpl: FakeWebSocket, requestTimeoutMs: 10 }) + const socket = FakeWebSocket.instances.at(-1)! + const connected = client.connect() + socket.emit('open') + await connected + + await expect(client.send('Runtime.evaluate')).rejects.toThrow( + 'Timed out waiting for CDP Runtime.evaluate after 10ms' + ) + expect(client.pending.size).toBe(0) + client.close() + }) + + it('bounds the whole suite and invokes its targeted timeout hook', async () => { + const onTimeout = vi.fn() + await expect(withDeadline(new Promise(() => {}), 10, 'Chrome Runtime suite', onTimeout)).rejects.toThrow( + 'Timed out waiting for Chrome Runtime suite after 10ms' + ) + expect(onTimeout).toHaveBeenCalledOnce() + }) + + it('terminates a matching profile child even when the browser root exited first', async () => { + let residualProcesses = [{ pid: 202, command: 'chrome --user-data-dir=/tmp/owned-profile' }] + const signalProcessGroup = vi.fn() + const signalProcess = vi.fn((pid: number, signal: NodeJS.Signals) => { + if (pid === 202 && signal === 'SIGTERM') residualProcesses = [] + }) + + const result = await terminateOwnedProcesses({ + rootPid: 101, + isRootExited: () => true, + listOwnedProcesses: () => residualProcesses, + signalProcessGroup, + signalProcess, + sleep: async () => {} + }) + + expect(signalProcessGroup).toHaveBeenCalledWith(101, 'SIGTERM') + expect(signalProcess).toHaveBeenCalledWith(202, 'SIGTERM') + expect(signalProcessGroup).not.toHaveBeenCalledWith(101, 'SIGKILL') + expect(result).toEqual({ rootExited: true, residualProcesses: [] }) + }) + + it('escalates only the owned process group and matching profile PIDs after TERM', async () => { + let rootExited = false + let residualProcesses = [{ pid: 202, command: 'chrome --user-data-dir=/tmp/owned-profile' }] + const signalProcessGroup = vi.fn((_pid: number, signal: NodeJS.Signals) => { + if (signal === 'SIGKILL') rootExited = true + }) + const signalProcess = vi.fn((_pid: number, signal: NodeJS.Signals) => { + if (signal === 'SIGKILL') residualProcesses = [] + }) + + const result = await terminateOwnedProcesses({ + rootPid: 101, + isRootExited: () => rootExited, + listOwnedProcesses: () => residualProcesses, + signalProcessGroup, + signalProcess, + termTimeoutMs: 0, + sleep: async () => {} + }) + + expect(signalProcessGroup.mock.calls).toEqual([ + [101, 'SIGTERM'], + [101, 'SIGKILL'] + ]) + expect(signalProcess.mock.calls).toEqual([ + [202, 'SIGTERM'], + [202, 'SIGKILL'] + ]) + expect(result).toEqual({ rootExited: true, residualProcesses: [] }) + }) +}) diff --git a/e2e/chrome-harness.ts b/e2e/chrome-harness.ts new file mode 100644 index 00000000..54e10725 --- /dev/null +++ b/e2e/chrome-harness.ts @@ -0,0 +1,208 @@ +export type OwnedProcess = { + pid: number + command: string +} + +type WebSocketLike = { + addEventListener: (type: string, listener: (event: any) => void, options?: { once?: boolean }) => void + send: (message: string) => void + close: () => void +} + +type WebSocketConstructor = new (url: string) => WebSocketLike + +type CdpMessage = { + id?: number + method?: string + params: Record + sessionId: string + result?: any + error?: { code: number; message: string } +} + +type CdpWaiter = { + resolve: (value: any) => void + reject: (error: unknown) => void + timer: NodeJS.Timeout +} + +type CdpClientOptions = { + WebSocketImpl?: WebSocketConstructor + requestTimeoutMs?: number +} + +type CleanupState = { + rootExited: boolean + residualProcesses: OwnedProcess[] +} + +type CleanupOptions = { + rootPid?: number + isRootExited: () => boolean + listOwnedProcesses: () => OwnedProcess[] + signalProcessGroup: (pid: number, signal: NodeJS.Signals) => void + signalProcess: (pid: number, signal: NodeJS.Signals) => void + termTimeoutMs?: number + killTimeoutMs?: number + pollIntervalMs?: number + sleep?: (durationMs: number) => Promise +} + +export const delay = (durationMs: number): Promise => new Promise((resolve) => setTimeout(resolve, durationMs)) + +export const withDeadline = ( + promise: PromiseLike | T, + timeoutMs: number, + label: string, + onTimeout: () => void = () => {} +): Promise => + new Promise((resolve, reject) => { + let settled = false + const timer = setTimeout(() => { + if (settled) return + settled = true + try { + onTimeout() + } catch (error) { + reject(error) + return + } + reject(new Error(`Timed out waiting for ${label} after ${timeoutMs}ms`)) + }, timeoutMs) + + Promise.resolve(promise).then( + (value) => { + if (settled) return + settled = true + clearTimeout(timer) + resolve(value) + }, + (error) => { + if (settled) return + settled = true + clearTimeout(timer) + reject(error) + } + ) + }) + +export const evaluateRuntimeMessage = ( + evaluate: (expression: string) => Promise, + message: unknown +): Promise => { + const serialized = JSON.stringify(message) + if (serialized === undefined) throw new TypeError('Runtime message must be JSON-serializable') + return evaluate(`chrome.runtime.sendMessage(${serialized})`) +} + +export class CdpClient { + requestTimeoutMs: number + socket: WebSocketLike + nextId = 1 + pending = new Map() + handlers = new Set<(message: CdpMessage) => void>() + + constructor(url: string, options: CdpClientOptions = {}) { + const WebSocketImpl = options.WebSocketImpl ?? (WebSocket as unknown as WebSocketConstructor) + this.requestTimeoutMs = options.requestTimeoutMs ?? 5000 + this.socket = new WebSocketImpl(url) + this.socket.addEventListener('message', ({ data }) => { + const message = JSON.parse(String(data)) as CdpMessage + if (message.id) { + const waiter = this.pending.get(message.id) + if (!waiter) return + this.pending.delete(message.id) + clearTimeout(waiter.timer) + if (message.error) waiter.reject(new Error(`${message.error.code}: ${message.error.message}`)) + else waiter.resolve(message.result) + return + } + this.handlers.forEach((handler) => handler(message)) + }) + this.socket.addEventListener('close', () => { + this.pending.forEach(({ reject, timer }) => { + clearTimeout(timer) + reject(new Error('CDP connection closed')) + }) + this.pending.clear() + }) + } + + connect(): Promise { + return withDeadline( + new Promise((resolve, reject) => { + this.socket.addEventListener('open', resolve, { once: true }) + this.socket.addEventListener('error', reject, { once: true }) + }), + this.requestTimeoutMs, + 'CDP connection' + ) + } + + onEvent(handler: (message: CdpMessage) => void): () => boolean { + this.handlers.add(handler) + return () => this.handlers.delete(handler) + } + + send(method: string, params: Record = {}, sessionId?: string): Promise { + return new Promise((resolve, reject) => { + const id = this.nextId++ + const timer = setTimeout(() => { + if (!this.pending.delete(id)) return + reject(new Error(`Timed out waiting for CDP ${method} after ${this.requestTimeoutMs}ms`)) + }, this.requestTimeoutMs) + this.pending.set(id, { resolve, reject, timer }) + try { + this.socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })) + } catch (error) { + clearTimeout(timer) + this.pending.delete(id) + reject(error) + } + }) + } + + close(): void { + this.socket.close() + } +} + +export const terminateOwnedProcesses = async (options: CleanupOptions): Promise => { + const { + rootPid, + isRootExited, + listOwnedProcesses, + signalProcessGroup, + signalProcess, + termTimeoutMs = 3000, + killTimeoutMs = 2000, + pollIntervalMs = 50, + sleep = delay + } = options + + const snapshot = (): CleanupState => ({ rootExited: isRootExited(), residualProcesses: listOwnedProcesses() }) + const isSettled = (state: CleanupState): boolean => state.rootExited && state.residualProcesses.length === 0 + const waitUntilSettled = async (timeoutMs: number): Promise => { + const deadline = Date.now() + timeoutMs + let state = snapshot() + while (!isSettled(state) && Date.now() < deadline) { + await sleep(pollIntervalMs) + state = snapshot() + } + return state + } + const signalOwned = (signal: NodeJS.Signals, state: CleanupState): void => { + if (rootPid) signalProcessGroup(rootPid, signal) + state.residualProcesses.forEach(({ pid }) => { + if (pid !== rootPid) signalProcess(pid, signal) + }) + } + + let state = snapshot() + if (isSettled(state)) return state + signalOwned('SIGTERM', state) + state = await waitUntilSettled(termTimeoutMs) + if (isSettled(state)) return state + signalOwned('SIGKILL', state) + return waitUntilSettled(killTimeoutMs) +} diff --git a/e2e/chrome-runtime.ts b/e2e/chrome-runtime.ts new file mode 100644 index 00000000..a739225f --- /dev/null +++ b/e2e/chrome-runtime.ts @@ -0,0 +1,737 @@ +import { execFileSync, spawn } from 'node:child_process' +import { access, mkdtemp, readFile, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { basename, join, resolve } from 'node:path' +import { CdpClient, delay, evaluateRuntimeMessage, terminateOwnedProcesses, withDeadline } from './chrome-harness.ts' + +type ProcessEntry = { + pid: number + processGroupId: number + command: string +} + +type TargetInfo = { + targetId: string + type: string + title: string + url: string +} + +type ExecutionContext = { + id: number + name: string + origin: string +} + +type RuntimeEvent = { + target?: string + event: string + type?: string + args?: unknown[] + detail?: unknown + stack?: unknown +} + +type AppState = { + mounted: boolean + unavailable: boolean +} + +type PortAttack = { + disconnected: boolean + responses: unknown[] + error?: string +} + +type Evidence = { + browser: string | null + chromePid?: number + extensionPath: string + startupMs: number | null + targets: Array> + relayed: string[] + rejectedRelayWarnings: number + extensionErrors: RuntimeEvent[] + relayDiagnostics: RuntimeEvent[] + rawBoundaryMessages: { offscreen: number; content: number } + presenceSourceBoundary?: { + content: PortAttack + options: PortAttack + durableUnchanged: boolean + before: Record + after: Record + } + sandbox: string + cleanup: { rootExited: boolean; residualProcesses: string[]; profileRemoved: boolean } + appState?: AppState + failure?: string + events?: RuntimeEvent[] +} + +type TargetSession = [sessionId: string, target: TargetInfo] + +type RelayMessage = { + type: string + sender: { type: string } + id: string + path: string[] + meta: { tab: { id: number; url: string } } + namespace: string + timeStamp: number + data: unknown +} + +const errorMessage = (error: unknown): string => (error instanceof Error ? error.message : String(error)) +const errorStack = (error: unknown): string => (error instanceof Error ? (error.stack ?? error.message) : String(error)) + +const executable = process.env.WEBCHAT_CHROMIUM_EXECUTABLE +const extensionPath = resolve(process.env.WEBCHAT_CHROME_EXTENSION_PATH ?? '.output/chrome-mv3') +const manifestPath = join(extensionPath, 'manifest.json') +const readPositiveTimeout = (name: string, fallback: number): number => { + const value = Number(process.env[name] ?? fallback) + if (!Number.isSafeInteger(value) || value <= 0) throw new Error(`${name} must be a positive safe integer`) + return value +} +const startupTimeoutMs = readPositiveTimeout('WEBCHAT_E2E_STARTUP_TIMEOUT_MS', 20000) +const cdpRequestTimeoutMs = readPositiveTimeout('WEBCHAT_E2E_CDP_TIMEOUT_MS', 5000) +const suiteTimeoutMs = readPositiveTimeout('WEBCHAT_E2E_SUITE_TIMEOUT_MS', 60000) +const sandboxSetting = process.env.WEBCHAT_CHROMIUM_DISABLE_SANDBOX ?? 'false' +if (sandboxSetting !== 'true' && sandboxSetting !== 'false') { + throw new Error('WEBCHAT_CHROMIUM_DISABLE_SANDBOX must be exactly "true" or "false"') +} +const disableSandbox = sandboxSetting === 'true' +if (disableSandbox && (process.env.CI !== 'true' || process.env.GITHUB_ACTIONS !== 'true')) { + throw new Error('Chromium sandbox may only be disabled by the GitHub Actions CI gate') +} + +if (!executable) { + throw new Error( + 'WEBCHAT_CHROMIUM_EXECUTABLE is required and must point to side-load-capable Chrome for Testing or Chromium' + ) +} +await Promise.all([access(executable), access(manifestPath)]) +const extensionManifest: unknown = JSON.parse(await readFile(manifestPath, 'utf8')) +if ( + extensionManifest === null || + typeof extensionManifest !== 'object' || + !('name' in extensionManifest) || + typeof extensionManifest.name !== 'string' || + extensionManifest.name.trim().length === 0 +) { + throw new Error('Chrome extension manifest name must be a non-empty string') +} +const extensionName = extensionManifest.name + +const waitFor = async ( + check: () => T | null | undefined | false | Promise, + { timeoutMs, label, intervalMs = 100 }: { timeoutMs: number; label: string; intervalMs?: number } +): Promise => { + const startedAt = Date.now() + let lastError: unknown + while (Date.now() - startedAt < timeoutMs) { + try { + const value = await check() + if (value) return value + } catch (error) { + lastError = error + } + await delay(intervalMs) + } + throw new Error(`Timed out waiting for ${label}${lastError ? `: ${errorMessage(lastError)}` : ''}`) +} + +const remoteValue = (value: any): unknown => { + if (Object.hasOwn(value, 'value')) return value.value + if (value.unserializableValue) return value.unserializableValue + if (value.preview?.properties) { + return Object.fromEntries( + value.preview.properties.map((property: { name: string; value: unknown }) => [property.name, property.value]) + ) + } + return value.description ?? value.type +} + +const processEntries = (): ProcessEntry[] => { + if (process.platform === 'win32') return [] + return execFileSync('ps', ['-axo', 'pid=,pgid=,command='], { encoding: 'utf8' }) + .split('\n') + .flatMap((line) => { + const match = line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/) + return match ? [{ pid: Number(match[1]), processGroupId: Number(match[2]), command: match[3] }] : [] + }) +} + +const profilePath = await mkdtemp(join(tmpdir(), 'webchat-chrome-runtime-')) +const stderr: string[] = [] +const chrome = spawn( + executable, + [ + '--headless=new', + '--remote-debugging-port=0', + '--no-first-run', + '--no-default-browser-check', + '--disable-dev-shm-usage', + ...(disableSandbox ? ['--no-sandbox'] : []), + `--user-data-dir=${profilePath}`, + `--disable-extensions-except=${extensionPath}`, + `--load-extension=${extensionPath}`, + 'about:blank' + ], + { + detached: process.platform !== 'win32', + stdio: ['ignore', 'ignore', 'pipe'] + } +) +chrome.stderr.setEncoding('utf8') +chrome.stderr.on('data', (chunk: string | Buffer) => { + stderr.push(String(chunk)) + if (stderr.join('').length > 65536) stderr.shift() +}) + +let exited = chrome.exitCode !== null +const exitResult = new Promise((resolveExit) => { + chrome.once('exit', (code, signal) => { + exited = true + resolveExit({ code, signal }) + }) +}) + +let cdp: CdpClient | undefined +let runError: unknown +let cleanupError: Error | undefined +const runtimeEvents: RuntimeEvent[] = [] +const evidence: Evidence = { + browser: null, + chromePid: chrome.pid, + extensionPath, + startupMs: null, + targets: [], + relayed: [], + rejectedRelayWarnings: 0, + extensionErrors: [], + relayDiagnostics: [], + rawBoundaryMessages: { offscreen: 0, content: 0 }, + sandbox: disableSandbox ? 'disabled-in-github-actions' : 'enabled', + cleanup: { rootExited: false, residualProcesses: [], profileRemoved: false } +} + +try { + await withDeadline( + (async () => { + const activePortPath = join(profilePath, 'DevToolsActivePort') + const activePort = await waitFor( + async () => { + if (exited) throw new Error(`Chromium exited before CDP startup: ${stderr.join('')}`) + const value = await readFile(activePortPath, 'utf8').catch(() => '') + return value.trim() || null + }, + { timeoutMs: 10000, label: 'Chromium DevToolsActivePort' } + ) + const [port] = activePort.split('\n') + const endpoint = `http://127.0.0.1:${port}` + const version = await withDeadline<{ Browser: string; webSocketDebuggerUrl: string }>( + fetch(`${endpoint}/json/version`, { signal: AbortSignal.timeout(cdpRequestTimeoutMs) }).then( + async (response) => (await response.json()) as { Browser: string; webSocketDebuggerUrl: string } + ), + cdpRequestTimeoutMs, + 'Chromium version endpoint' + ) + evidence.browser = version.Browser + + const client = new CdpClient(version.webSocketDebuggerUrl, { requestTimeoutMs: cdpRequestTimeoutMs }) + cdp = client + await client.connect() + + const targets = new Map() + const sessions = new Map() + const contexts = new Map() + const enabledSessions = new Set() + + const enableSession = async (sessionId: string, targetInfo: TargetInfo): Promise => { + sessions.set(sessionId, targetInfo) + contexts.set(sessionId, []) + await Promise.all([ + client.send('Runtime.enable', {}, sessionId), + client.send('Log.enable', {}, sessionId), + targetInfo.type === 'page' ? client.send('Page.enable', {}, sessionId) : Promise.resolve() + ]) + enabledSessions.add(sessionId) + } + + client.onEvent((message) => { + if (message.method === 'Target.targetCreated' || message.method === 'Target.targetInfoChanged') { + targets.set(message.params.targetInfo.targetId, message.params.targetInfo) + return + } + if (message.method === 'Target.targetDestroyed') { + targets.delete(message.params.targetId) + return + } + if (message.method === 'Target.attachedToTarget') { + void enableSession(message.params.sessionId, message.params.targetInfo).catch((error) => { + runtimeEvents.push({ + target: message.params.targetInfo.url, + event: 'attach-error', + detail: errorMessage(error) + }) + }) + return + } + if (message.method === 'Target.detachedFromTarget') { + sessions.delete(message.params.sessionId) + contexts.delete(message.params.sessionId) + enabledSessions.delete(message.params.sessionId) + return + } + if (message.method === 'Runtime.executionContextCreated') { + contexts.get(message.sessionId)?.push(message.params.context) + return + } + if (message.method === 'Runtime.consoleAPICalled') { + runtimeEvents.push({ + target: sessions.get(message.sessionId)?.url, + event: 'console', + type: message.params.type, + args: message.params.args.map(remoteValue) + }) + return + } + if (message.method === 'Runtime.exceptionThrown') { + const detail = message.params.exceptionDetails + runtimeEvents.push({ + target: sessions.get(message.sessionId)?.url, + event: 'exception', + detail: detail.exception ? remoteValue(detail.exception) : detail.text, + stack: detail.stackTrace?.callFrames + }) + } + }) + + await client.send('Target.setDiscoverTargets', { discover: true }) + await client.send('Target.setAutoAttach', { + autoAttach: true, + waitForDebuggerOnStart: false, + flatten: true + }) + const { targetInfos } = await client.send<{ targetInfos: TargetInfo[] }>('Target.getTargets') + targetInfos.forEach((targetInfo) => targets.set(targetInfo.targetId, targetInfo)) + + const sessionForTarget = async (targetInfo: TargetInfo, label: string): Promise => { + let current = [...sessions.entries()].find( + ([sessionId, target]) => enabledSessions.has(sessionId) && target.targetId === targetInfo.targetId + ) + if (!current) { + await client.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true }) + current = await waitFor( + () => + [...sessions.entries()].find( + ([sessionId, target]) => enabledSessions.has(sessionId) && target.targetId === targetInfo.targetId + ), + { timeoutMs: 5000, label } + ) + } + return current + } + + const startupStartedAt = Date.now() + const { targetId: pageTargetId } = await client.send<{ targetId: string }>('Target.createTarget', { + url: 'https://example.com/' + }) + const pageSession = await waitFor( + () => + [...sessions.entries()].find( + ([sessionId, target]) => + enabledSessions.has(sessionId) && target.targetId === pageTargetId && target.url.includes('example.com') + ), + { timeoutMs: 10000, label: 'content page CDP session' } + ) + + const evaluate = async (sessionId: string, expression: string, contextId?: number): Promise => { + const result = await client.send<{ + result: { value: T } + exceptionDetails?: { exception?: { description?: string }; text: string } + }>( + 'Runtime.evaluate', + { + expression, + awaitPromise: true, + returnByValue: true, + ...(contextId ? { contextId } : {}) + }, + sessionId + ) + if (result.exceptionDetails) { + throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text) + } + return result.result.value + } + + const appState = await waitFor( + async () => { + const state = await evaluate( + pageSession[0], + `(() => { + const roots = [...document.querySelectorAll('*')].flatMap((element) => + element.shadowRoot ? [element.shadowRoot] : [] + ) + return { + mounted: roots.some((root) => root.querySelector('#root')), + unavailable: roots.some((root) => root.querySelector('#runtime-unavailable, [role="alert"]')) + } + })()` + ) + if (state?.unavailable) throw new Error('content mounted the Runtime unavailable UI') + return state?.mounted ? state : null + }, + { timeoutMs: startupTimeoutMs, label: 'shared Runtime content UI' } + ) + evidence.startupMs = Date.now() - startupStartedAt + + const contentContext = await waitFor( + () => contexts.get(pageSession[0])?.find((context) => context.name === extensionName), + { timeoutMs: 5000, label: 'content isolated execution context' } + ) + const extensionId = new URL(contentContext.origin).host + const extensionTargets = () => + [...targets.values()].filter((target) => target.url.startsWith('chrome-extension://')) + const offscreenTargets = extensionTargets().filter( + (target) => new URL(target.url).host === extensionId && target.url.endsWith('/offscreen.html') + ) + if (offscreenTargets.length !== 1) + throw new Error(`Expected one WebChat Offscreen target, received ${offscreenTargets.length}`) + const worker = extensionTargets().find( + (target) => target.type === 'service_worker' && new URL(target.url).host === extensionId + ) + if (!worker) throw new Error('WebChat Runtime service worker target is missing') + + const offscreenSession = await sessionForTarget(offscreenTargets[0], 'Offscreen CDP session') + const workerSession = await sessionForTarget(worker, 'Service Worker CDP session') + const presenceNamespace = `WEB_CHAT_RUNTIME_PRESENCE_STORE_V1:${extensionId}` + const forgedPresence = { + domain: 'https://forged-source.example', + lastJoinedAt: 1, + local: { + presenceId: 'forged-source-generation', + userId: 'forged-source-user', + joinedAt: 1, + status: 'active' + }, + observers: [] + } + const presenceRecords = (sessionId: string) => + evaluate>( + sessionId, + `chrome.storage.session.get(null).then((values) => Object.fromEntries(Object.entries(values).filter(([key]) => key.startsWith('WEB_CHAT_RUNTIME_PRESENCE_V1:'))))` + ) + const attackPresencePort = (label: string) => `new Promise((resolve) => { + const namespace = ${JSON.stringify(presenceNamespace)}; + const port = chrome.runtime.connect({ name: namespace }); + const responses = []; + let settled = false; + const finish = (value) => { + if (settled) return; + settled = true; + resolve(value); + }; + port.onMessage.addListener((message) => responses.push(message)); + port.onDisconnect.addListener(() => finish({ disconnected: true, responses })); + const apply = (id, path, args) => ({ + type: 'apply', + sender: { type: 'injector' }, + id, + path, + args, + meta: {}, + namespace, + timeStamp: Date.now() + }); + try { + port.postMessage(apply(${JSON.stringify(`${label}-load`)}, ['load'], [${JSON.stringify(forgedPresence.domain)}])); + port.postMessage(apply(${JSON.stringify(`${label}-save`)}, ['save'], [${JSON.stringify(forgedPresence)}])); + } catch (error) { + finish({ disconnected: true, responses, error: String(error) }); + } + setTimeout(() => finish({ disconnected: false, responses }), 2000); + })` + const beforePresence = await presenceRecords(workerSession[0]) + const { targetId: optionsTargetId } = await client.send<{ targetId: string }>('Target.createTarget', { + url: `chrome-extension://${extensionId}/options.html` + }) + const optionsTarget = await waitFor(() => targets.get(optionsTargetId), { + timeoutMs: 5000, + label: 'options target' + }) + const optionsSession = await sessionForTarget(optionsTarget, 'Options CDP session') + await waitFor( + async () => + (await evaluate(optionsSession[0], `typeof chrome !== 'undefined' && !!chrome.runtime?.connect`)) || + null, + { timeoutMs: 5000, label: 'options extension runtime' } + ) + const [contentAttack, optionsAttack] = await Promise.all([ + evaluate(pageSession[0], attackPresencePort('content'), contentContext.id), + evaluate(optionsSession[0], attackPresencePort('options')) + ]) + const forgedProvider = { + type: 'apply', + sender: { type: 'provider' }, + id: 'forged-presence-provider-response', + path: ['load'], + data: forgedPresence, + meta: {}, + namespace: presenceNamespace, + timeStamp: Date.now() + } + await Promise.all([ + evaluate(pageSession[0], `chrome.runtime.sendMessage(${JSON.stringify(forgedProvider)})`, contentContext.id), + evaluate(optionsSession[0], `chrome.runtime.sendMessage(${JSON.stringify(forgedProvider)})`) + ]) + await delay(100) + const afterPresence = await presenceRecords(workerSession[0]) + const durableUnchanged = JSON.stringify(afterPresence) === JSON.stringify(beforePresence) + if ( + !contentAttack.disconnected || + contentAttack.responses.length !== 0 || + !optionsAttack.disconnected || + optionsAttack.responses.length !== 0 || + !durableUnchanged + ) { + throw new Error( + `PresenceStore source boundary failed: ${JSON.stringify({ contentAttack, optionsAttack, beforePresence, afterPresence })}` + ) + } + evidence.presenceSourceBoundary = { + content: contentAttack, + options: optionsAttack, + durableUnchanged, + before: beforePresence, + after: afterPresence + } + await client.send('Target.closeTarget', { targetId: optionsTargetId }) + + await evaluate( + pageSession[0], + `globalThis.__webchatRelayMessages = []; + chrome.runtime.onMessage.addListener((message) => { + if (message?.id?.startsWith('relay-check-')) globalThis.__webchatRelayMessages.push(message.id) + }); + true`, + contentContext.id + ) + await evaluate( + offscreenSession[0], + `globalThis.__webchatTargetTab = null; + chrome.runtime.onMessage.addListener((message, sender) => { + if (message?.__webchatResolveTargetTab) { + globalThis.__webchatTargetTab = { id: sender.tab?.id, url: sender.tab?.url } + } + }); + true` + ) + await evaluate( + pageSession[0], + `chrome.runtime.sendMessage({ __webchatResolveTargetTab: true })`, + contentContext.id + ) + const targetTab = await waitFor( + () => evaluate<{ id?: number; url?: string } | null>(offscreenSession[0], 'globalThis.__webchatTargetTab'), + { + timeoutMs: 2000, + label: 'trusted content sender tab metadata' + } + ) + if ( + typeof targetTab?.id !== 'number' || + !Number.isSafeInteger(targetTab.id) || + targetTab.url !== 'https://example.com/' + ) { + throw new Error(`Could not resolve the exact target tab: ${JSON.stringify(targetTab)}`) + } + const exactTargetTab = { id: targetTab.id, url: targetTab.url } + + const message = (id: string, overrides: Partial = {}): RelayMessage => ({ + type: 'apply', + sender: { type: 'provider' }, + id, + path: ['getSnapshot'], + meta: { tab: exactTargetTab }, + namespace: `WEB_CHAT_RUNTIME_V2:${extensionId}`, + timeStamp: Date.now(), + data: {}, + ...overrides + }) + const relayEventStart = runtimeEvents.length + const validMessages = [ + message('relay-check-valid-apply'), + message('relay-check-valid-callback', { type: 'callback', path: ['onInbound'], data: [] }) + ] + for (const [index, item] of validMessages.entries()) { + await evaluate(offscreenSession[0], `chrome.runtime.sendMessage(${JSON.stringify(item)})`) + try { + await waitFor( + async () => { + const received = await evaluate(pageSession[0], 'globalThis.__webchatRelayMessages', contentContext.id) + return received.length > index ? received : null + }, + { timeoutMs: 2000, label: `${item.type} provider relay` } + ) + } catch (error) { + throw new Error(`${errorMessage(error)}; events: ${JSON.stringify(runtimeEvents.slice(-20))}`) + } + } + + const rawBoundaryMessages = [null, 'raw-runtime-message'] + for (const item of rawBoundaryMessages) { + await evaluateRuntimeMessage((expression) => evaluate(offscreenSession[0], expression), item) + await evaluateRuntimeMessage((expression) => evaluate(pageSession[0], expression, contentContext.id), item) + } + evidence.rawBoundaryMessages = { + offscreen: rawBoundaryMessages.length, + content: rawBoundaryMessages.length + } + + const rejectedMessages = [ + message('relay-check-wrong-namespace', { namespace: 'UNKNOWN_NAMESPACE' }), + message('relay-check-wrong-direction', { sender: { type: 'injector' } }), + message('relay-check-invalid-target', { + meta: { tab: { id: exactTargetTab.id, url: 'http://example.com/' } } + }) + ] + await evaluate( + offscreenSession[0], + `Promise.all(${JSON.stringify(rejectedMessages)}.map((item) => chrome.runtime.sendMessage(item)))` + ) + await evaluate( + pageSession[0], + `chrome.runtime.sendMessage(${JSON.stringify(message('relay-check-spoofed-content'))})`, + contentContext.id + ) + await waitFor( + () => + runtimeEvents.filter( + (event) => event.event === 'console' && event.args?.[0] === '[WebChat] Dropped Offscreen Runtime relay:' + ).length >= 6, + { timeoutMs: 2000, label: 'rejected relay logs' } + ) + + evidence.relayed = await evaluate(pageSession[0], 'globalThis.__webchatRelayMessages', contentContext.id) + const expectedRelay = ['relay-check-valid-apply', 'relay-check-valid-callback'] + if (JSON.stringify(evidence.relayed) !== JSON.stringify(expectedRelay)) { + throw new Error( + `Unexpected relayed messages: ${JSON.stringify(evidence.relayed)}; events: ${JSON.stringify(runtimeEvents.slice(-20))}` + ) + } + + evidence.rejectedRelayWarnings = runtimeEvents.filter( + (event) => event.event === 'console' && event.args?.[0] === '[WebChat] Dropped Offscreen Runtime relay:' + ).length + if (evidence.rejectedRelayWarnings !== 6) { + throw new Error(`Expected six rejected relay warnings, received ${evidence.rejectedRelayWarnings}`) + } + + evidence.extensionErrors = runtimeEvents + .slice(0, relayEventStart) + .filter( + (event) => + event.event === 'exception' || + (event.event === 'console' && + event.type === 'error' && + !String(event.args?.[0]).includes('Dropped v2 frame')) + ) + evidence.relayDiagnostics = runtimeEvents + .slice(relayEventStart) + .filter((event) => event.event === 'exception' || (event.event === 'console' && event.type === 'error')) + const unexpectedRelayDiagnostics = evidence.relayDiagnostics.filter( + (event) => !JSON.stringify(event).includes('Could not establish connection. Receiving end does not exist.') + ) + if (unexpectedRelayDiagnostics.length > 0) { + throw new Error(`Unexpected relay diagnostics: ${JSON.stringify(unexpectedRelayDiagnostics)}`) + } + if (evidence.extensionErrors.length > 0) { + throw new Error(`Extension runtime errors: ${JSON.stringify(evidence.extensionErrors)}`) + } + + evidence.targets = [...targets.values()].map(({ type, title, url }) => ({ type, title, url })) + evidence.appState = appState + })(), + suiteTimeoutMs, + 'Chrome Runtime suite', + () => { + try { + cdp?.close() + } catch {} + } + ) +} catch (error) { + runError = error + evidence.failure = errorStack(error) + evidence.events = runtimeEvents.slice(-100) +} finally { + if (cdp) { + await Promise.race([cdp.send('Browser.close').catch(() => {}), delay(1000)]) + try { + cdp.close() + } catch {} + } + + await Promise.race([exitResult, delay(5000)]) + const signalPid = (pid: number, signal: NodeJS.Signals): void => { + try { + process.kill(pid, signal) + } catch (error) { + if (!(error instanceof Error) || !('code' in error) || error.code !== 'ESRCH') throw error + } + } + const cleanupState = await terminateOwnedProcesses({ + rootPid: chrome.pid, + isRootExited: () => exited, + listOwnedProcesses: () => + processEntries().filter( + ({ processGroupId, command }) => processGroupId === chrome.pid || command.includes(profilePath) + ), + signalProcessGroup: (pid: number, signal: NodeJS.Signals) => { + if (process.platform === 'win32') { + if (!exited) chrome.kill(signal) + } else { + signalPid(-pid, signal) + } + }, + signalProcess: signalPid + }) + evidence.cleanup.rootExited = cleanupState.rootExited + evidence.cleanup.residualProcesses = cleanupState.residualProcesses.map(({ pid, command }) => `${pid} ${command}`) + await rm(profilePath, { recursive: true, force: true }) + evidence.cleanup.profileRemoved = await access(profilePath).then( + () => false, + () => true + ) + + if ( + !evidence.cleanup.rootExited || + evidence.cleanup.residualProcesses.length > 0 || + !evidence.cleanup.profileRemoved + ) { + cleanupError = new Error('Owned Chromium cleanup failed') + } +} + +if (cleanupError) { + console.error(JSON.stringify({ evidence, stderr: stderr.join('') }, null, 2)) + throw cleanupError +} +if (runError) { + console.error(JSON.stringify({ evidence, stderr: stderr.join('') }, null, 2)) + throw runError +} + +console.log( + JSON.stringify( + { + ...evidence, + executable: basename(executable), + stderr: stderr.join('').split('\n').filter(Boolean).slice(-20) + }, + null, + 2 + ) +) diff --git a/e2e/runtime-bundles.ts b/e2e/runtime-bundles.ts new file mode 100644 index 00000000..fac786b1 --- /dev/null +++ b/e2e/runtime-bundles.ts @@ -0,0 +1,114 @@ +import { readdir, readFile } from 'node:fs/promises' +import { join } from 'node:path' + +type ExtensionManifest = { + manifest_version?: number + background?: { + service_worker?: string + scripts?: string[] + } + content_scripts?: Array<{ + js?: string[] + }> +} + +const readJson = async (path: string): Promise => + JSON.parse(await readFile(path, 'utf8')) as ExtensionManifest +function assert(condition: unknown, message: string): asserts condition { + if (!condition) throw new Error(message) +} + +const chromeRoot = '.output/chrome-mv3' +const firefoxRoot = '.output/firefox-mv2' +const [chromeManifest, firefoxManifest, chromeChunks, firefoxBackground, wireCodecSource] = await Promise.all([ + readJson(join(chromeRoot, 'manifest.json')), + readJson(join(firefoxRoot, 'manifest.json')), + readdir(join(chromeRoot, 'chunks')), + readFile(join(firefoxRoot, 'background.js'), 'utf8'), + readFile('src/protocol/WireCodec.ts', 'utf8') +]) + +assert(chromeManifest.manifest_version === 3, 'Expected the production Chrome MV3 manifest') +assert(chromeManifest.background?.service_worker === 'background.js', 'Expected the Chrome service-worker host') +assert(firefoxManifest.manifest_version === 2, 'Expected the production Firefox MV2 manifest') +assert(firefoxManifest.background?.scripts?.includes('background.js'), 'Expected the Firefox background-page host') + +const chromeHostChunk = chromeChunks.find((file) => file.startsWith('host-') && file.endsWith('.js')) +assert(chromeHostChunk, 'Expected the production Chrome Offscreen host chunk') +const chromeContentEntries = chromeManifest.content_scripts?.flatMap((entry) => entry.js ?? []) ?? [] +const firefoxContentEntries = firefoxManifest.content_scripts?.flatMap((entry) => entry.js ?? []) ?? [] +assert(chromeContentEntries.length > 0, 'Expected at least one production Chrome content entry') +assert(firefoxContentEntries.length > 0, 'Expected at least one production Firefox content entry') +const [chromeHost, chromeContent, firefoxContent] = await Promise.all([ + readFile(join(chromeRoot, 'chunks', chromeHostChunk), 'utf8'), + Promise.all(chromeContentEntries.map((entry) => readFile(join(chromeRoot, entry), 'utf8'))), + Promise.all(firefoxContentEntries.map((entry) => readFile(join(firefoxRoot, entry), 'utf8'))) +]) +assert(!chromeHost.includes('tabs.query'), 'Chrome Offscreen host must not contain tabs.query') +assert(firefoxBackground.includes('tabs.query'), 'Firefox background provider must retain its tabs-capable route') +for (const relayMarker of ['Dropped Offscreen Runtime relay:', 'untrusted-source', 'target-mismatch']) { + assert( + !firefoxBackground.includes(relayMarker), + `Firefox background must not contain Chrome relay marker ${relayMarker}` + ) +} + +const expectedCoreJsImports = ['core-js/actual/typed-array/from-base64', 'core-js/actual/typed-array/to-base64'] +const coreJsImports = [...wireCodecSource.matchAll(/^import '([^']*core-js[^']*)'$/gm)].map((match) => match[1]) +assert( + JSON.stringify(coreJsImports) === JSON.stringify(expectedCoreJsImports), + `WireCodec must use only the scoped Base64 imports: ${coreJsImports.join(', ')}` +) +const codecPolyfillMarkers = [ + 'fromBase64', + 'toBase64', + 'lastChunkHandling', + 'setFromBase64', + 'fromHex', + 'toHex', + 'setFromHex' +] +for (const [target, bundles] of [ + ['Chrome content', chromeContent], + ['Firefox content', firefoxContent] +] as const) { + for (const [index, bundle] of bundles.entries()) { + for (const marker of codecPolyfillMarkers) { + assert(!bundle.includes(marker), `${target} entry ${index} must not contain codec polyfill marker ${marker}`) + } + } +} +for (const [target, bundle] of [ + ['Chrome host', chromeHost], + ['Firefox background', firefoxBackground] +] as const) { + for (const marker of ['fromBase64', 'toBase64', 'lastChunkHandling']) { + assert(bundle.includes(marker), `${target} must contain ${marker}`) + } + for (const residue of ['setFromBase64', 'fromHex', 'toHex', 'setFromHex', 'atob(', 'btoa(']) { + assert(!bundle.includes(residue), `${target} must not contain unrelated Base64/hex residue ${residue}`) + } +} + +console.log( + JSON.stringify({ + chrome: { + manifestVersion: 3, + contentBytes: chromeContent.reduce((total, bundle) => total + Buffer.byteLength(bundle), 0), + contentCodecPolyfill: false, + offscreenTabsQuery: false, + runtimeBytes: Buffer.byteLength(chromeHost), + uint8ArrayBase64: true + }, + firefox: { + manifestVersion: 2, + contentBytes: firefoxContent.reduce((total, bundle) => total + Buffer.byteLength(bundle), 0), + contentCodecPolyfill: false, + tabsProvider: true, + offscreenRelay: false, + runtimeBytes: Buffer.byteLength(firefoxBackground), + uint8ArrayBase64: true + }, + coreJsImports + }) +) diff --git a/eslint.config.ts b/eslint.config.ts deleted file mode 100644 index b28485ce..00000000 --- a/eslint.config.ts +++ /dev/null @@ -1,45 +0,0 @@ -// import type { Linter } from 'eslint' -import globals from 'globals' -import pluginJs from '@eslint/js' -import tseslint from 'typescript-eslint' -import reactPlugin from '@eslint-react/eslint-plugin' -// https://github.com/francoismassart/eslint-plugin-tailwindcss/issues/325 -// import tailwindPlugin from 'eslint-plugin-tailwindcss' -import prettierPlugin from 'eslint-plugin-prettier/recommended' -import * as tsParser from '@typescript-eslint/parser' - -export default [ - { files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'] }, - { - languageOptions: { globals: globals.browser } - }, - pluginJs.configs.recommended, - ...tseslint.configs.recommended, - // ...tailwindPlugin.configs['flat/recommended'], - prettierPlugin, - { - files: ['**/*.{ts,tsx}'], - ...reactPlugin.configs.recommended, - languageOptions: { - parser: tsParser - } - }, - { - ignores: ['**/.output/*', '**/.wxt/*', '**/ui/**', '**/magicui/**', '**/lib/**', '**/patches/**'] - }, - { - rules: { - '@typescript-eslint/no-unused-vars': 'warn', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-empty-object-type': 'off', - '@typescript-eslint/no-unused-expressions': 'off', - '@eslint-react/no-array-index-key': 'off', - '@eslint-react/hooks-extra/no-redundant-custom-hook': 'off', - '@eslint-react/dom/no-missing-button-type': 'off', - '@eslint-react/hooks-extra/prefer-use-state-lazy-initialization': 'off', - '@eslint-react/no-unstable-context-value': 'off', - '@typescript-eslint/consistent-type-imports': 'error' - } - } -] -// satisfies Linter.Config[] diff --git a/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/.openspec.yaml b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/.openspec.yaml new file mode 100644 index 00000000..0bd76e61 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-18 diff --git a/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/design.md b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/design.md new file mode 100644 index 00000000..05a88f4e --- /dev/null +++ b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/design.md @@ -0,0 +1,120 @@ +## Context + +WebChat currently uses ESLint 10, Prettier 3, `eslint-plugin-prettier`, TypeScript ESLint, React ESLint, Tailwind ESLint, and related configuration. `pnpm lint` invokes ESLint with `--fix --cache`; lint-staged invokes the same behavior for JavaScript and TypeScript. CI/CD install dependencies and run project gates through GitHub Actions. + +The worktree at `develop@9c58d6b2` already contains user-owned dependency-upgrade edits in `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml`. In particular, the dirty ESLint config is internally incomplete because a Prettier import is commented while its configuration reference remains. These edits must not be reset or overwritten. This migration uses them as explicit input and produces one auditable reconciled candidate. + +The change is repository-wide and may rewrite source, but it is a quality-tool migration rather than a product change. Browser behavior, WebRTC protocols, storage, permissions, and UI remain outside scope. + +## Goals / Non-Goals + +**Goals:** + +- Make oxfmt and oxlint the sole formatting and linting tools. +- Give developers simple fix commands plus explicit read-only checks. +- Use the same correction contract in staged files and CI/CD. +- Fail CI/CD when generated corrections were not committed, without granting write-back behavior. +- Preserve TypeScript, commit validation, and Chrome/Firefox build gates. +- Reconcile rather than discard the current dirty dependency-upgrade work. + +**Non-Goals:** + +- Type-aware oxlint. +- Automated CI commits or pushes. +- Rule-for-rule emulation of every historical ESLint plugin if oxlint has no equivalent. +- Product, protocol, permission, storage, or UI changes. +- Unrelated refactors discovered while formatting or linting. + +## Decisions + +### 1. Replace the old toolchain atomically + +Remove ESLint and Prettier together with their direct plugins/configuration and replace them with oxfmt and oxlint in one candidate. Keeping both toolchains during a transition creates two authorities for formatting and lint decisions and leaves scripts/workflows ambiguous. + +Alternative rejected: gradual coexistence. The owner explicitly chose complete replacement. + +### 2. Separate fix commands from diagnostic check commands + +`format` runs oxfmt in write mode; `format:check` performs a read-only format check. `lint` runs oxlint with safe fixes enabled; `lint:check` performs read-only lint validation. Exact flags must be selected from the installed package versions and verified rather than assumed. + +The developer-default commands can correct source as requested. Explicit check commands remain useful for editors, diagnosis, and proving idempotence. + +Alternative rejected: make `lint` read-only. The owner requires the normal workflow to correct source. + +### 3. CI/CD run fixes, verify generated diff, and report tests independently + +GitHub workflows run the same format and lint correction commands used locally, followed by `git diff --exit-code`. A correction therefore makes the workflow fail and exposes the required patch; it is not silently accepted. + +The CI workflow exposes `linter` and `tests` as separate named jobs/checks. Both depend only on the shared `setup` job and may run in parallel. `linter` retains format/lint correction, cleanliness verification, and the existing WXT/TypeScript check. Because GitHub Actions jobs use isolated workspaces, `tests` generates WXT types in its own job before it runs `pnpm run test`; this prerequisite does not give `tests` lint, format, type-check, or build ownership. Neither job depends on or absorbs the quality responsibility of the other, so a failure remains attributable and the other result still reaches an independent terminal state. The existing `build` job and its browser boundaries remain unchanged. + +Alternative rejected: CI bot commits. That would require write permissions, complicate fork/third-party PR security, create recursive workflow risk, and make author provenance less clear. + +Alternative rejected: run fixes without diff verification. Such a workflow can report success while the branch remains nonconforming. + +Alternative rejected: keep unit tests as the final step inside `linter`. That collapses two different failure domains into one Actions result and prevents an earlier linter failure from producing an independent test result. + +Alternative rejected: rely on `linter` to generate WXT types for `tests`. Sibling Actions jobs do not share a workspace, so the test job must establish its own generated-type prerequisite without depending on linter completion. + +The Chrome Runtime control treats the selected built extension directory as the artifact authority. Before matching the content isolated execution context, the harness reads and structurally parses that directory's `manifest.json`, requires a non-empty top-level `name`, and matches the context against that exact artifact value. Product display metadata may change without requiring a second hardcoded E2E name. A missing, malformed, or invalid manifest name fails explicitly rather than degrading into an unrelated context timeout. + +Alternative rejected: keep `WebChat` or replace it with the current longer display text in the E2E source. Either value couples release verification to mutable product copy and will drift again. + +### 4. Keep lint-staged as the staged-file orchestrator + +Preserve Husky and lint-staged, replacing only the commands and supported file mapping. Staged supported source is formatted and lint-fixed before commit. Commitlint remains independent. + +Alternative rejected: replace Husky/lint-staged. It is unrelated to the requested tool migration and adds unnecessary workflow change. + +### 5. Use non-type-aware oxlint plus TypeScript + +Configure oxlint without type-aware analysis and retain `tsc --noEmit`. The lint migration should not simultaneously change the repository's type-analysis semantics. + +Alternative rejected: enable type-aware oxlint immediately. Rule support and performance need a separate evaluation after the base migration is stable. + +### 6. Prefer Oxc-native rules and document intentional coverage differences + +Translate existing quality intent into supported oxlint categories/plugins and explicit configuration. Do not preserve ESLint plugins solely for compatibility. Any material lost rule coverage must be listed in the implementation handoff, with TypeScript or another retained gate identified where it supplies equivalent protection. + +Alternative rejected: force exact rule parity through a mixed ESLint/Oxc stack. That violates complete replacement and retains maintenance complexity. + +### 7. Reconcile the dirty worktree with explicit provenance + +Before editing, capture base HEAD, staged and unstaged status, and diffs for `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml`. Build from the shared worktree state or a provenance-preserving worktree prepared by Planner. Compatible package upgrades remain; obsolete ESLint/Prettier dependencies and config are removed; Oxc entries and resulting lockfile changes are added. + +Do not use reset, checkout, or any restoration that drops the user's edits. Handoff must account for each original dirty file. + +### 8. Accept source rewrites but block unrelated behavior changes + +Run the Oxc correction commands across their configured repository scope. Deterministic format output and safe lint fixes may change source. Review the non-mechanical remainder separately; any product behavior or unrelated refactor is removed or specified in another change. + +The formatting pass does not need its own isolated commit. The final history may group configuration, dependency, and corrected-source work pragmatically, but provenance must remain auditable. + +## Risks / Trade-offs + +- [Oxfmt output creates a large diff] -> Separate config/dependency review from source-output review and verify idempotence with a second fix pass. +- [Oxlint does not match an ESLint plugin rule] -> Inventory old rules, map supported equivalents, document meaningful gaps, and retain TypeScript/build protection. +- [Fix mode changes behavior] -> Review non-format source edits, run type/build gates, and smoke-test both browser outputs. +- [CI modifies generated or ignored artifacts] -> Define explicit Oxc scopes/ignore patterns and confirm a clean second pass. +- [Dirty dependency work is lost or misattributed] -> Capture pre-edit staged/unstaged patches and include an ownership reconciliation in handoff evidence. +- [CI diff check includes build output] -> Run fix-and-diff cleanliness before build steps or ensure generated output is ignored and not part of the check. +- [Lint and unit-test failures are reported as one result] -> Keep `linter` and `tests` as sibling jobs that depend only on shared setup, and require both before merge. +- [Independent tests cannot resolve generated WXT types] -> Generate WXT types inside the `tests` workspace before invoking the canonical unit-test command; do not create a dependency on `linter`. +- [Product display-name changes break Chrome Runtime discovery] -> Parse and validate the selected build artifact's manifest name, then use that exact value to identify the isolated execution context; keep product copy out of the harness. +- [Different Oxc versions expose different flags/defaults] -> Pin compatible versions in the lockfile and derive commands from those installed CLIs. + +## Migration Plan + +1. Record exact base HEAD, worktree status, and staged/unstaged patches for the three existing dirty files. +2. Inventory all ESLint/Prettier dependencies, configs, ignores, scripts, lint-staged entries, Husky hooks, editor settings, documentation, and CI/CD references. +3. Add pinned-compatible oxfmt/oxlint dependencies and minimal repository configuration; update package scripts and lint-staged. +4. Remove superseded tool dependencies/configuration and reconcile the pre-existing package/lock changes. +5. Update CI/CD to run format/lint fixes, `git diff --exit-code`, and TypeScript in `linter`; generate WXT types and run unit tests in an independent sibling `tests` job; make the Chrome Runtime control derive its isolated-context identity from the selected built manifest; retain existing browser gates. +6. Apply Oxc corrections across the configured scope, review non-mechanical changes, and rerun to prove idempotence. +7. Verify frozen install, format/lint fixes, read-only checks, staged-file behavior, TypeScript, Chrome/Firefox builds, OpenSpec strict validation, and browser smoke behavior. +8. Freeze an exact candidate and route independent review/quality verification before push. Merge requires owner acceptance and explicit authorization under the team workflow. + +Rollback is code-only: revert the migration candidate as one logical change while preserving a patch of the original dirty dependency-upgrade inputs. No user data or extension storage migration is involved. + +## Open Questions + +None. The owner confirmed complete replacement, dirty-work reconciliation, source correction, CI fix-plus-diff behavior, and non-type-aware oxlint on 2026-07-18. diff --git a/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/proposal.md b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/proposal.md new file mode 100644 index 00000000..a3a7513a --- /dev/null +++ b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/proposal.md @@ -0,0 +1,50 @@ +## Why + +WebChat currently maintains overlapping ESLint and Prettier dependencies and configuration, and its `lint` command both checks and rewrites source through a relatively heavy plugin stack. Migrating fully to oxfmt and oxlint gives the repository one explicit formatter, one explicit linter, and consistent fix-and-verify behavior across local development, staged files, CI, and release workflows. + +## What Changes + +- **BREAKING** Replace Prettier and ESLint completely with oxfmt and oxlint; remove their configuration, dependencies, caches, scripts, and workflow references. +- Make oxfmt the only repository formatter and oxlint the only repository linter. +- Allow normal local format and lint commands to fix source files. +- Add explicit read-only check commands for diagnosing formatting and lint violations. +- Run the fixing commands in CI/CD and then fail with `git diff --exit-code` if those commands produced uncommitted changes; CI does not commit or push fixes. +- Report `linter` and `tests` as separate GitHub Actions jobs/checks. Both depend only on shared setup, run independently, and must pass before merge; unit tests are not a step inside `linter`, and `tests` generates its own WXT types before running them. +- Keep the Chrome Runtime build control bound to the extension artifact by deriving its isolated execution-context name from the selected built manifest instead of a hardcoded product display name. +- Update lint-staged and Husky integration so staged source is formatted and lint-fixed by the new tools. +- Keep Conventional Commit validation and `tsc --noEmit` as separate gates. +- Use non-type-aware oxlint for this migration; TypeScript remains the type-analysis authority. +- Apply the new formatter and linter to the repository source as part of the migration, including safe automatic source corrections. +- Treat the existing uncommitted `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml` dependency-upgrade work as migration input: preserve compatible upgrades, remove superseded tooling changes, and do not discard user work. + +## Capabilities + +### New Capabilities + +- `source-quality-tooling`: The repository-wide formatter, linter, local command, staged-file, CI/CD, and source-correction contract. + +### Modified Capabilities + +- None. This repository was initialized with OpenSpec for this change and has no archived stable capability specs yet. + +## Impact + +- Root `package.json`, lockfile, ESLint/Prettier and new Oxc configuration files, lint-staged/Husky behavior, ignore files, editor recommendations where present, CI/CD workflows, and the Chrome Runtime CI harness. +- All source and supported configuration/documentation files in the oxfmt/oxlint scope may receive deterministic formatting or safe lint fixes. +- Existing Chrome/Firefox build and package behavior must remain unchanged. +- No product behavior, browser-extension permissions, protocol, storage, WebRTC, or user-interface requirement changes are intended. + +## Non-Goals + +- No type-aware oxlint migration in this change. +- No automatic CI commit or push. +- No unrelated refactor or product behavior change. +- No removal of commitlint, TypeScript checks, Chrome/Firefox builds, or browser smoke-test responsibilities. + +## Acceptance Criteria + +- No active ESLint or Prettier dependency, config, script, cache, lint-staged, Husky, CI, CD, or documentation reference remains, except migration history explaining their removal. +- Local fix commands produce a clean source tree when rerun; check commands pass without modifying files. +- CI/CD run the same fix contract and `git diff --exit-code`; GitHub Actions reports independent `linter` and `tests` checks, the Chrome Runtime harness discovers the isolated context using the selected build artifact's manifest name, and lint, unit tests, type checking, and required Chrome/Firefox builds all pass before merge. +- The migration candidate preserves and accounts for the pre-existing dirty dependency-upgrade work. +- The final candidate passes OpenSpec strict validation and all repository quality/build gates without changing extension product behavior. diff --git a/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/specs/source-quality-tooling/spec.md b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/specs/source-quality-tooling/spec.md new file mode 100644 index 00000000..2f4431e6 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/specs/source-quality-tooling/spec.md @@ -0,0 +1,176 @@ +## ADDED Requirements + +### Requirement: Oxc tools are the sole formatter and linter + +The repository SHALL use oxfmt as its only source formatter and oxlint as its only source linter. Active ESLint and Prettier dependencies, configuration, scripts, caches, editor integration, staged-file integration, and workflow integration MUST be removed. + +#### Scenario: Search active tooling references + +- **WHEN** the migration candidate is inspected outside immutable package-manager history and migration documentation +- **THEN** active quality-tool references SHALL resolve to oxfmt and oxlint and SHALL NOT invoke ESLint or Prettier + +#### Scenario: Install frozen dependencies + +- **WHEN** dependencies are installed from the committed manifest and lockfile +- **THEN** oxfmt and oxlint SHALL be available and removed ESLint/Prettier packages SHALL NOT be direct project dependencies + +### Requirement: Local commands can correct source + +The repository SHALL provide explicit local commands that allow oxfmt and oxlint to apply supported corrections to source files. It SHALL also provide read-only check commands that report violations without modifying files. + +#### Scenario: Run formatter fix + +- **WHEN** a developer runs the format command on nonconforming supported files +- **THEN** oxfmt SHALL rewrite those files to the repository format contract + +#### Scenario: Run linter fix + +- **WHEN** a developer runs the lint command on safely fixable violations +- **THEN** oxlint SHALL apply supported fixes and report remaining violations + +#### Scenario: Run read-only checks + +- **WHEN** a developer runs the format-check and lint-check commands on a clean tree +- **THEN** both commands SHALL exit successfully without changing files + +#### Scenario: Rerun fixes + +- **WHEN** the format and lint fix commands are rerun after a successful correction pass +- **THEN** they SHALL produce no additional source diff + +### Requirement: Type checking remains a separate gate + +The migration SHALL use non-type-aware oxlint and SHALL preserve TypeScript `tsc --noEmit` as the repository's type-analysis gate. + +#### Scenario: Run type checking + +- **WHEN** the source contains a TypeScript type error that non-type-aware oxlint does not detect +- **THEN** the TypeScript check command SHALL fail independently + +#### Scenario: Inspect oxlint configuration + +- **WHEN** the oxlint command and configuration are inspected +- **THEN** they SHALL NOT enable type-aware linting in this migration + +### Requirement: Staged files use Oxc corrections + +The repository's staged-file workflow SHALL run oxfmt and oxlint fixes for the supported staged source set and SHALL stage the corrected content through the existing lint-staged/Husky flow. + +#### Scenario: Commit a nonconforming staged source file + +- **WHEN** a developer commits a supported staged source file with fixable format or lint violations +- **THEN** the staged workflow SHALL apply oxfmt and oxlint corrections before commit validation completes + +#### Scenario: Commit an unfixable staged violation + +- **WHEN** oxlint reports an unfixable staged violation +- **THEN** the commit SHALL be blocked and the violation SHALL remain visible to the developer + +### Requirement: CI and CD fix then verify repository cleanliness + +CI and CD SHALL run the repository format and lint fix commands and then execute `git diff --exit-code` before accepting the quality gate. CI/CD MUST NOT automatically commit or push corrections. + +#### Scenario: CI encounters clean source + +- **WHEN** the committed source already conforms to oxfmt and oxlint +- **THEN** fix commands SHALL leave no diff and the cleanliness check SHALL pass + +#### Scenario: CI corrects committed source + +- **WHEN** a fix command changes committed source in CI or CD +- **THEN** `git diff --exit-code` SHALL fail the workflow and expose the uncommitted correction + +#### Scenario: CI correction behavior + +- **WHEN** CI or CD generates a source correction +- **THEN** the workflow SHALL NOT commit that correction, push it to the branch, or recursively trigger another run + +### Requirement: Existing quality and build gates remain + +The migration SHALL preserve Conventional Commit validation, TypeScript checking, and required Chrome and Firefox build behavior. The migration MUST NOT change extension permissions, runtime product behavior, protocols, storage contracts, or browser feature scope. + +#### Scenario: Validate migration candidate + +- **WHEN** the final migration candidate is verified +- **THEN** commit validation, TypeScript checking, Chrome build, and Firefox build SHALL pass under the updated quality workflow + +#### Scenario: Compare extension behavior + +- **WHEN** built Chrome and Firefox extensions are smoke-tested on the migration candidate +- **THEN** extension loading and core chat entry behavior SHALL match the pre-migration product contract + +### Requirement: CI reports linter and unit tests independently + +The GitHub Actions CI workflow SHALL expose `linter` and `tests` as separate named jobs/checks. Both jobs SHALL depend only on the shared setup job and SHALL be eligible to run independently after setup. `linter` SHALL retain the existing format/lint correction, repository-cleanliness verification, and WXT/TypeScript check. Because each job has an isolated workspace, `tests` SHALL generate WXT types in its own job before running the canonical `pnpm run test` command; it SHALL NOT depend on `linter` or absorb format, lint, TypeScript, or build responsibilities. The existing `build` job, workflow triggers, package scripts, application behavior, and browser scope SHALL remain unchanged. Merge acceptance SHALL require successful terminal results for `linter`, `tests`, and `build` on the same final exact. + +#### Scenario: Inspect the CI job graph + +- **WHEN** the final workflow is inspected after shared setup +- **THEN** `linter`, `tests`, and `build` SHALL be sibling jobs, `tests` SHALL generate WXT types before `pnpm run test`, `pnpm run test` SHALL appear only in `tests`, and neither `linter` nor `tests` SHALL depend on the other + +#### Scenario: Run tests in an isolated job workspace + +- **WHEN** the `tests` job starts from a clean checkout after shared setup +- **THEN** it SHALL generate the WXT types required by the repository TypeScript configuration before invoking `pnpm run test`, without relying on files generated by `linter` + +#### Scenario: Linter fails independently + +- **WHEN** setup succeeds and `linter` fails +- **THEN** the `linter` check SHALL report that failure while `tests` remains independently eligible to run and report its own terminal result + +#### Scenario: Unit tests fail independently + +- **WHEN** setup succeeds and `pnpm run test` fails +- **THEN** the `tests` check SHALL report that failure without relabeling it as a linter failure or suppressing the independent `linter` result + +#### Scenario: Accept the final pull request exact + +- **WHEN** the final exact is considered for merge +- **THEN** its separate `linter`, `tests`, and `build` checks SHALL all have successful terminal results + +### Requirement: Chrome Runtime control follows built manifest identity + +The Chrome Runtime CI harness SHALL derive the content isolated execution-context name from the `manifest.json` inside the selected built Chrome extension directory. It SHALL structurally parse that artifact, require a non-empty top-level string `name`, and match the execution context against that exact value. It SHALL NOT hardcode either a historical or current product display name. A missing, malformed, or invalid built manifest name SHALL fail explicitly. The extension display name, WXT configuration, Runtime behavior, workflow, browser scope, and existing Runtime-control assertions SHALL remain unchanged. + +#### Scenario: Product display name changes + +- **WHEN** a valid product display-name change is emitted as the selected Chrome artifact's manifest `name` +- **THEN** the Runtime control SHALL discover the content isolated execution context using that artifact value without requiring a harness-copy update + +#### Scenario: Built manifest identity is unavailable + +- **WHEN** the selected Chrome artifact's manifest is missing, malformed, or has no non-empty string `name` +- **THEN** the Runtime control SHALL fail with an explicit manifest-identity error instead of waiting for a hardcoded context name to time out + +#### Scenario: Preserve the existing Runtime boundary + +- **WHEN** the harness resolves the artifact-derived isolated context +- **THEN** all existing extension-target, Offscreen, service-worker, authenticated relay, presence, diagnostic, timeout, and cleanup controls SHALL continue unchanged + +### Requirement: Existing dependency-upgrade work is preserved and reconciled + +The existing uncommitted changes in `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml` SHALL be treated as input to the migration. Compatible dependency upgrades SHALL be preserved, superseded ESLint/Prettier work SHALL be replaced, and user changes MUST NOT be silently discarded. + +#### Scenario: Establish migration provenance + +- **WHEN** implementation begins +- **THEN** the implementer SHALL record the base HEAD and the pre-existing diff for all three dirty files before editing + +#### Scenario: Review final dependency diff + +- **WHEN** the final candidate is reviewed +- **THEN** the handoff SHALL distinguish preserved pre-existing upgrades, removed superseded tooling, newly introduced Oxc dependencies, and lockfile consequences + +### Requirement: Source correction scope remains tooling-only + +Oxfmt and oxlint corrections MAY modify repository source and supported configuration files, but the migration SHALL NOT intentionally change product behavior or perform unrelated refactors. + +#### Scenario: Review changed source + +- **WHEN** a source change is not a deterministic formatter result or a safe oxlint fix +- **THEN** it SHALL be justified as necessary for the tooling migration or removed from the candidate + +#### Scenario: Detect product behavior change + +- **WHEN** verification finds a change to extension permissions, room behavior, message protocol, persistence, or visible user workflow +- **THEN** the migration candidate SHALL be blocked until that change is removed or separately specified through OpenSpec diff --git a/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/tasks.md b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/tasks.md new file mode 100644 index 00000000..786f6bb5 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-migrate-to-oxfmt-and-oxlint/tasks.md @@ -0,0 +1,68 @@ +> **Completion status (2026-07-28):** All implementation, review, CI, publication, and merge tasks are complete. Final accepted exact `5a7bb4a12c301bd4e51b4904253739e41091a4f8` passed fresh Actions run `30298778315` with independent `linter`, `tests`, and `build` checks, including Chrome MV3 and Firefox MV2 verification. PR #69 merged through `6627e8fc1b27aa7754e79ca1c40ae77321fd7fc0`; `develop` later received Owner commit `fff7da8e4e6f320a86d45670e108cd97451955e5` (`feat!: release WebChat 2.0`). Master publication then merged `develop` through `3cad760b9988041995234c4cee762c3ea5598b66`; semantic-release completed `v2.0.0` at `fcd1c372e107629c442751015d5e61847cecf09e` and created tag `v2.0.0`. These remain separate history points outside this archived change. Runs `30293583612` and `30297016430` remain immutable fail-before evidence; completed task markers do not reinterpret either failed run as PASS. + +## 1. Provenance And Inventory + +- [x] 1.1 Record base HEAD, branch, full worktree status, and separate staged/unstaged patches for the pre-existing `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml` changes before implementation. +- [x] 1.2 Inventory every active ESLint/Prettier dependency, config, ignore, cache, package script, lint-staged entry, Husky hook, editor setting, documentation reference, and CI/CD invocation. +- [x] 1.3 Inventory current ESLint rules/plugins and map each material quality intent to oxlint, TypeScript, build validation, an explicit accepted gap, or removal as obsolete formatting behavior. +- [x] 1.4 Confirm installed oxfmt/oxlint CLI flags and supported config schema from the pinned candidate versions before writing commands. + +## 2. Oxc Toolchain + +- [x] 2.1 Add compatible pinned oxfmt and oxlint development dependencies and create minimal repository configuration and ignore scope. +- [x] 2.2 Add fixing `format` and `lint` scripts plus read-only `format:check` and `lint:check` scripts using verified CLI flags. +- [x] 2.3 Remove ESLint, Prettier, and their direct plugins/config packages, configuration files, cache behavior, and active references without discarding compatible pre-existing dependency upgrades. +- [x] 2.4 Regenerate and review `pnpm-lock.yaml`, distinguishing preserved upgrades, removed tooling, new Oxc packages, and transitive consequences. + +## 3. Developer Workflow + +- [x] 3.1 Update lint-staged so supported staged files receive oxfmt and oxlint fixes while unfixable violations block commit. +- [x] 3.2 Preserve Husky and Conventional Commit validation while removing obsolete ESLint/Prettier hook behavior. +- [x] 3.3 Update repository guidance and editor recommendations so developers use Oxc fix/check commands and no active document instructs them to run ESLint or Prettier. + +## 4. CI And CD + +- [x] 4.1 Update each applicable CI workflow to run format and lint fixes followed immediately by `git diff --exit-code`, then retain TypeScript and required build gates. +- [x] 4.2 Update each applicable CD/release workflow to enforce the same fix-plus-diff cleanliness contract without commit/push permissions or recursive write-back behavior. +- [x] 4.3 Verify workflow ordering prevents build/package outputs from contaminating the source-cleanliness diff check. + +## 5. Source Correction + +- [x] 5.1 Run oxfmt across its configured repository scope and retain deterministic corrections to supported source/configuration/documentation files. +- [x] 5.2 Run oxlint fix across its configured source scope, review every non-format correction, and remove or separately specify any behavior change or unrelated refactor. +- [x] 5.3 Rerun both fixing commands and prove idempotence with a clean `git diff` relative to the post-fix candidate. +- [x] 5.4 Search for active ESLint/Prettier remnants and either remove them or document the narrow historical/non-executable reason they remain. + +## 6. Verification And Handoff + +- [x] 6.1 Verify frozen dependency installation, fixing commands, read-only format/lint checks, and `git diff --check` on the exact candidate. +- [x] 6.2 Exercise lint-staged/Husky with one fixable staged fixture and one unfixable lint fixture without committing fixture changes. +- [x] 6.3 Run TypeScript checking and production Chrome and Firefox builds; record exact commands, exit status, and output artifacts. +- [x] 6.4 Smoke-test loading and core chat entry in built Chrome and Firefox extensions to confirm no intended product behavior change. +- [x] 6.5 Run OpenSpec status, strict change validation, repository-wide strict validation, and doctor; resolve all errors. +- [x] 6.6 Freeze an exact clean candidate with parent/tree/SHA and a handoff that accounts for every pre-existing dirty-file change, rule-coverage difference, source correction, and known limitation. +- [x] 6.7 Route Planner evidence review, Reviewer, and QA; UX/Tester browser acceptance is required only if verification finds visible or runtime behavior changes beyond deterministic tooling output. + +## 7. Independent CI Linter And Test Checks + +- [x] 7.1 From Owner-local exact `2b143a23aa8f07baec7db800b4b8505ad7740e03`, preserve the existing `package.json` display-name change and create a fresh docs-authority child followed by a workflow-only child. In `.github/workflows/ci.yml`, remove `pnpm run test` from `linter`; add a sibling `tests` job that depends only on `setup`, reuses the existing checkout/Node/pnpm/cache/install pattern, and runs only `pnpm run test`. Retain `linter` format/lint/diff/WXT-TypeScript behavior, the existing `build` job, triggers, scripts, lockfile, source, Runtime, protocol, persistence, and browser scope. +- [x] 7.2 Route a fresh no-gate Reviewer over both the direct workflow diff and the cumulative delta from `b13a5e29421d4ef01e21ac87398d3007615c5495`. The cumulative delta SHALL contain only the Owner `package.json` display-name commit, the PM OpenSpec authority, and `.github/workflows/ci.yml`; prior evidence SHALL NOT certify those additions. +- [x] 7.3 After Reviewer PASS under the Owner's standing current-release authorization, publish the immutable final exact, update the PR body using product goals/outcomes and verification rather than implementation details, and require same-exact successful terminal `linter`, `tests`, and `build` Actions checks. No additional browser/QA route is required. Only then may the Owner-authorized PR merge proceed. + +## 8. Repair Independent CI Failures + +This section supersedes task 7.1's literal `tests`-runs-only wording, task 7.2's prior cumulative scope, and task 7.3's first published exact. WXT type generation is a test-workspace prerequisite, not a new test-job quality responsibility; job independence and the three-check merge requirement remain unchanged. + +- [x] 8.1 Treat published run `30293583612` on exact `c31f7d376cb9a203b45c40c695b88011e19ebe7d` as a fail-after, not release evidence: `tests` failed before collection because its isolated workspace lacked generated WXT types; `linter` failed because oxfmt produced one uncommitted `src/domain/AppFeedback.ts` correction; `build` failed while the Chrome Runtime control waited for an isolated execution context. Keep PR body update and merge blocked. +- [x] 8.2 Create a docs-only child of `c31f7d376cb9a203b45c40c695b88011e19ebe7d`, then a fresh implementation child whose direct scope is exactly `.github/workflows/ci.yml` and `src/domain/AppFeedback.ts`. In `tests`, generate WXT types after dependency setup and before `pnpm run test`, while keeping `needs: [setup]` and no linter dependency. Commit only the deterministic oxfmt result in `AppFeedback.ts`; any semantic, import, identifier, command, event, descriptor, message, or behavior change is forbidden. Preserve linter/build/triggers/scripts/lockfile and all other source, Runtime, protocol, persistence, and browser boundaries. +- [x] 8.3 Route a fresh no-gate Reviewer over the direct two-file implementation diff and the cumulative delta from `b13a5e29421d4ef01e21ac87398d3007615c5495`. The cumulative delta SHALL contain only the Owner `package.json` display-name change, the four CI OpenSpec artifacts, `.github/workflows/ci.yml`, and the format-only `src/domain/AppFeedback.ts` correction. Prior Review, test, smoke, or Actions evidence SHALL NOT certify the replacement exact. +- [x] 8.4 Publish the replacement exact by normal fast-forward only after Reviewer PASS, then require a fresh same-exact Actions run in which separate `linter`, `tests`, and `build` checks all reach successful terminal states. Do not waive or pre-classify the prior Chrome Runtime timeout as flaky; the fresh `build` result determines whether a separate diagnosis is required. Only after all three pass may the product-level PR body update and Owner-authorized merge resume. No additional local or agent browser/QA route is required. + +## 9. Repair Artifact-Named Chrome Context Discovery + +This section supersedes task 8.4's expectation that exact `a03e2d168b5bd65476a516226f2b48a862c58a30` could close the release. The successful independent `linter` and `tests` checks remain exact-local evidence only; the failed `build` keeps that published exact non-releaseable. + +- [x] 9.1 Treat Actions run `30297016430` on published exact `a03e2d168b5bd65476a516226f2b48a862c58a30` as a second Chrome build fail-after: `setup`, `linter`, and `tests` passed, but `build` again timed out while matching the content isolated execution context. The recent successful run `30210802257` and both failures used Chrome `151.0.7922.47`; the failed run mounted the content application in about 329ms. The blocker is the harness's hardcoded `WebChat` context name after the Owner's valid `package.json` display-name change flowed through WXT to the built manifest, not a browser upgrade or missing content script. Keep PR body update and merge blocked. +- [x] 9.2 Create a docs-only child of `a03e2d168b5bd65476a516226f2b48a862c58a30`, then a fresh implementation child whose direct scope is exactly `e2e/chrome-runtime.ts`. Use the existing selected Chrome extension directory, structurally parse its built `manifest.json`, require a non-empty top-level string `name`, and match the content isolated execution context against that exact artifact value. Missing, malformed, or invalid manifest identity SHALL fail explicitly. Do not hardcode either display-name string or add a fallback. Preserve the Owner display name, `package.json`, WXT config, workflow, dependencies, lockfile, timeouts, Runtime assertions, application source, Runtime, protocol, persistence, permissions, and browser scope. +- [x] 9.3 Route a fresh independent no-gate Review over the direct one-file harness diff and the cumulative delta from `b13a5e29421d4ef01e21ac87398d3007615c5495`. The cumulative delta SHALL contain only the Owner `package.json` display-name change, the four CI OpenSpec artifacts, `.github/workflows/ci.yml`, the format-only `src/domain/AppFeedback.ts` correction, and `e2e/chrome-runtime.ts`. Prior Review, test, smoke, or Actions evidence SHALL NOT certify the replacement exact. +- [x] 9.4 Publish the replacement exact by normal fast-forward only after Review PASS, then require a fresh same-exact Actions run in which separate `linter`, `tests`, and `build` checks all reach successful terminal states. The Chrome control must pass using artifact-derived identity; a rerun, timeout extension, hardcoded rename, or waiver is not acceptance. Only after all three pass may the product-level PR body update and Owner-authorized merge resume. No additional local or agent browser/QA route is required. diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/design.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/design.md new file mode 100644 index 00000000..30883783 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/design.md @@ -0,0 +1,306 @@ +## Context + +Today every content-script page owns its WebRTC stack: `Peer` (Artico singleton per page), `ChatRoom`/`WorldRoom` impls, connection lifecycle, and presence all live and die with the tab. Duplicated connections per tab, refresh-killed sessions, per-tab WorldRoom joins, and scattered concurrency are the direct consequences. + +The v1 wire protocol also carries defects that cannot be fixed compatibly: page-scoped `peerId` presence keys (a single-Runtime browser cannot express multiple domains), full-page-URL and unconsumed metadata on the wire, toggle-on-receipt reactions (replay-corruptible), HLC-only history cursors (partition holes), untrusted history upsert (overwrites local bodies), and unbounded future-HLC values (ordering poison). + +The Owner authorized a one-shot architecture AND protocol rebuild with no backward-compatibility constraint and no old-data migration (final ruling `ddbbca7c`). A governing principle applies to the whole effort: **use Remesh DDD + CQRS throughout** — state and coordination are uniquely owned Remesh Domains with explicit Commands, Queries, Events, Effects, and injected Externs, not ad-hoc singletons or locks. + +Source exact `9c90bb0...` is the frozen functional baseline, but it is not the final architecture. Its page Domain, stateful Runtime `ChatRoom` implementation, and oversized `NetworkDomain` distribute command, session, recovery, and history authority. Its imperative Wire/provider path also does not express the private provider capability as a Runtime Extern, and some `*Module` names do not represent real `Remesh.module` instances. The Owner requires these boundaries to be corrected before PR #69 may merge. The already-published v2 peer wire remains an immutable anti-corruption boundary except for the later Owner-authorized logical-presence exception: SESSION now requires one opaque `presenceId` and a strict ordered/idempotent SESSION_END fact may close that generation. `ChatMessage`, history, World, codec behavior, limits, and all unrelated canonical bytes remain unchanged; the internal page-to-Runtime comctx control contract may change cleanly if the eight-method `ChatRoomExtern` and product behavior remain unchanged. + +## Goals / Non-Goals + +**Goals:** + +- One shared headless Remesh Runtime per browser owns all network state (connections, sessions, presence, routing, central id/HLC clock). +- Pages become pure-UI comctx consumers; no page-owned WebRTC remains and no fallback exists. +- Same-domain tabs share one connection per remote peer; domains stay isolated. +- One unified 5-second grace aligns ChatRoom connection, domain state, un-ACK buffer, and WorldRoom presence; F5 inside the window is seamless. +- v2 wire protocol: transport-trusted context, strict closed unions, World full-snapshot presence, Chat session + immutable events, reaction LWW, bounded recent-first history pull, strict HLC validation, explicit byte limits. +- A typed transactional `Database` becomes the only host-replaceable persistence extern; one internal concrete MessageStore preserves atomic first-value-wins/conflict behavior without exposing storage primitives to Chat/UI. +- `MessageRecord = ChatMessageRecord | SystemNoticeRecord` becomes the local truth with no delivery status or outbox. Local sends complete transport before local insertion and intentionally have no crash-window retry. +- Runtime truth is split by one-fact/one-owner DDD boundaries: Lifecycle, Connection, Session, World, History, Delivery, and Wire, all with explicit CQRS surfaces and fake-deterministic tests. +- `ChatRoomExtern` remains the exact eight-method application port; its Runtime implementation is state-free and contains no second join/session/recovery authority. +- Artico is replaceable only through a Runtime-private `RoomTransportExtern` implemented at the provider/composition boundary. +- Remesh modules encapsulate genuinely reusable, semantically isomorphic capabilities; they never create a second shared-state authority. + +**Non-Goals:** + +- No v1 compatibility, bridging, or translation; v1 and v2 are isolated by room namespace. +- No import, migration, or retention of old local history or old reaction state in the canonical Database-backed record store; old unstorage remains physically inert, unread, unconverted, and uncleared. +- No history replica or read model inside the Runtime. +- No dual-architecture intermediate release. +- No cryptographic identity/signatures, message editing, or standalone attachment protocol. +- No `host/hostname/href` on the wire, ever (derivable or privacy-risky); presence sites carry only `origin` and optional `title`, `icon`, `description`. +- No agent-run `pnpm dev` is release evidence. Exact-bound production artifacts are exercised by repository automation and the Owner's local test environment; the Owner records the final cross-browser product verdict. + +## Decisions + +### 1. Headless Runtime core behind injectable externs + +The Runtime core is a headless Remesh domain graph. It MUST NOT depend on React, DOM, content-script globals, Offscreen structure, `chrome.*`/`browser.*`, Worker globals, or UI components, and MUST NOT read `window/document`, operate UI state, or scatter browser host APIs into WorldRoom/ChatRoom domains. External capabilities are injected through their owning boundary: `RoomTransportExtern` for provider I/O, `PagePortExtern` for page delivery/history supply, and existing Clock/Identity Externs; browser host composition adapts Chrome/Edge Offscreen and Firefox Background lifecycle without entering Domain logic. UI talks to the Runtime only through the exact ChatRoom port and application Query/Command/Event contracts. Fake Externs provide deterministic race tests. The dependency direction is one-way: Runtime and Domain code may import the public peer contract from `src/protocol/index.ts`; protocol code MUST NOT import Runtime, Domain, storage, UI, service, comctx, or browser-extension code. + +### 2. Remesh DDD + CQRS defines every state authority + +The architecture uses DDD to assign one bounded owner to each fact and CQRS to constrain how that owner is used. This is not a directory or naming convention: + +- A **Command** expresses intent. It may trigger a state transition and Effects, but it is never proof that an external operation already succeeded. +- A **Query** is a pure synchronous read or derivation from Domain State. It performs no I/O, emits no Event, issues no Command, and writes no State. +- An **Event** is an immutable fact that already occurred after a transition or external outcome. An Event cannot be used as a disguised command. +- An **Effect** contains asynchronous/external I/O. It cannot mutate another Domain's State directly; it feeds a result back through the owning Domain's Command/Event path. +- An **Extern** injects an external capability. It does not own business/network State and cannot become a parallel recovery or cache authority. +- A **State** value has exactly one writable Domain owner. Other Domains use that owner's Query, Command, or Event surface through the same obtained Domain instance. + +The bounded contexts are: application/page projection, the exact `ChatRoomExtern` application port, the stateless Runtime client/comctx boundary, the shared Runtime Domain graph, `WireDomain` as protocol anti-corruption boundary, the private provider capability, the immutable public peer protocol, and application-owned persistence. Application/UI code may read Queries, issue Commands, and subscribe to Events only. It may retain user join input and display read models, but it cannot retain a writable copy of a Runtime network fact or restore Runtime state from that copy. + +The following owner matrix freezes responsibilities. Runtime-directory filenames may omit the redundant `Domain` suffix, but Planner may not merge owners or move writable facts between rows without a requirements update. + +| Domain owner | State | Queries | Commands | Events | Effects | Externs | +| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| Application/page Chat Domain | Retained join input, draft/UI status, projected session/message/error read models | User/session/message/readiness projections | Join, leave, send, reconnect-this-site, UI intents | Returned local send, remote live message, session snapshot/join/leave, readiness/error facts | Invoke the exact ChatRoom port; persist/project application facts | `ChatRoomExtern`, `WorldRoomExtern`, `Database` and existing UI capability Externs | +| Lifecycle Domain | Page leases, per-domain ref-count, grace deadline/phase, release identity | Active leases, grace/release status | Attach page, detach page, cancel/expire grace, release domain | Page attached/detached, grace started/cancelled/expired, domain released | Schedule/cancel the five-second global timer | None; global timers remain platform globals, not a timer Extern | +| Connection Domain | Desired membership, join/leave/reconnect/recovery attempt, current generation, physical acceptance phase | Current connection phase, active attempt/generation | Join, leave, reconnect, provider-ready/closed outcome, cancel/supersede attempt | Attempt started/accepted/failed/superseded, connection joined/left/recovered | Coordinate Wire, Session, World, and History Commands; never write their State | None; it consumes Runtime Domains rather than provider implementations | +| Session Domain | Committed local/remote Chat sessions, logical generation/observer ledger, current full snapshot, session incarnation, central id/HLC state | Session snapshot, local session, bound user/generation, next causal identity view | Commit/replace/remove session, accept trusted generation/end fact, allocate message identity/HLC | Snapshot replaced, logical session joined/left, message identity allocated | Publish trusted Chat facts through Wire; persist private lifecycle state; admit validated inbound live Chat through Delivery | `ClockExtern`, `IdentityExtern`, private `PresenceStoreExtern` | +| World Domain | Active-domain registry, local World session/snapshot, remote per-source snapshots, derived presence | Current World snapshot and per-domain presence projection | Register/release domain, replace/remove trusted source snapshot, publish current presence | Registry changed, presence published/replaced/removed | Send current full presence through Wire after Connection acceptance | None; provider and codec access terminate in Wire | +| History Domain | Requester/provider sessions, frozen cutoffs, cursors, budgets, supply ids, batches, timeout identities | Active history sessions, cursor/budget/progress | Start/cancel sync, accept trusted response, request/resolve/reject supply, settle timeout/batch | Sync started/completed/failed, response accepted, supply selected/released, batch ready/settled | Validate Session binding, request page supply, send through Wire, and admit through Delivery | `ClockExtern`, `PagePortExtern` | +| Delivery Domain | Per-domain inbound sequence, un-ACK buffer, bytes, history-batch membership | Replay snapshot, buffer usage, ACK state | Admit one/batch, ACK, replay, release domain | Inbound admitted/rejected, item/batch ACKed, replayed, released | Deliver admitted facts to attached pages and await durable ACK | `PagePortExtern` | +| Wire Domain | Trusted rooms/sources, provider-ready membership, per-room send tails, per-source decode queues, bounded drop diagnostics | Trusted room/source readiness and queue state | Join/leave room, send typed message, accept provider callback, dispose | Trusted peer/room ready/left/closed, typed message accepted, provider error/drop | Encode/decode/strict-validate immutable protocol values and invoke provider I/O | Runtime-private `RoomTransportExtern` | + +The Runtime dependency graph must be acyclic. The intended direction is application -> `ChatRoomExtern` -> stateless client/comctx -> Server composition; inside Runtime, Connection may consume Lifecycle, Wire, Session, World, and History; Session consumes Wire and Delivery; World consumes Wire; History consumes Wire, Delivery, and Session; Delivery consumes Lifecycle; Wire consumes only the public protocol and private provider Extern. This gives the strict acyclic chain `Connection -> History -> Session -> Delivery -> Lifecycle`, with Connection/History/Session/World also allowed to point toward Wire. Session uses Delivery only to admit a live Chat value after source/user binding validation; History uses Session only to query the current trusted source/session binding before its own requester/provider transition. A Domain consumes another Domain through `domain.getDomain(...)` and its public CQRS surface, never by importing or mutating State definitions. Server/comctx code only constructs the graph and adapts request/reply/subscription registration. + +Pure schemas, protocol types, ordering/validation functions, and stateless utilities are not forced into Domains. Mutable service singletons, shared maps, generic lock classes, UI-driven network State, and direct cross-Domain State writes are prohibited. + +### 2.1 Replaceable application ports terminate Runtime detail + +`RuntimeServer`/`PagePort` remain internal page-host contracts. Their trusted source/peer identity, local/remote provenance, host gating, snapshots, replay, transient operation state, history supply, and dead-callback cleanup terminate in `impls`. The split `onLocalSession`/`onSession`/`onSessionLeave` application-adapter input is replaced by one private Runtime session-event path capable of carrying local convergence, remote live changes, and replacement snapshots with private provenance. That path is never exported by an extern. + +`src/domain/impls/runtime/ChatRoom.ts` is a state-free Runtime client proxy, event bridge, persistence-before-ACK boundary, and history-supply boundary. It may retain only infrastructure needed to settle one in-flight request or dispose one active subscription; such transient handles cannot be read as a snapshot, used to replay application state, or used to recover a join. It contains no retained join command, session/local-session/published-session map, Runtime generation, recovery queue, or writable network fact. Runtime snapshots and Events flow through immediately to the application Domain, which may project a display read model but may not write that model back as Runtime authority. Host recovery is driven by current application intent through the public Commands and Runtime authority, never by a hidden adapter cache. + +The application `ChatRoom` extern directly type-imports `ChatUser`, `ChatSession`, `ChatSite`, `ChatMessage`, and the Owner-frozen mention value from `@/protocol`. This is the exact complete contract: + +```ts +interface JoinRoomCommand { + user: ChatUser + site: ChatSite +} +interface SendTextCommand { + type: 'text' + body: string + mentions: MentionedUser[] +} +interface SendReactionCommand { + type: 'reaction' + targetId: string + reaction: 'like' | 'hate' + active: boolean +} +type SendMessageCommand = SendTextCommand | SendReactionCommand +type Unsubscribe = () => void +interface ChatRoom { + joinRoom(command: JoinRoomCommand): Promise + leaveRoom(): Promise + sendMessage(command: SendMessageCommand): Promise + onMessage(listener: (message: ChatMessage) => void): Unsubscribe + onJoinRoom(listener: (session: ChatSession) => void): Unsubscribe + onLeaveRoom(listener: (session: ChatSession) => void): Unsubscribe + onSessions(listener: (sessions: readonly ChatSession[]) => void): Unsubscribe + onError(listener: (error: Error) => void): Unsubscribe +} +``` + +No alias, compatibility field, extra method, overload, generic Runtime type, or peer/source/joinedAt/timer/IDB/host/page/lease/retry field may extend this contract without explicit Owner intervention. `joinRoom` accepts the caller-known user and site; `impls` create `sessionId`. `leaveRoom` operates on the currently joined instance. `sendMessage` accepts only business input; `impls` create `id`, `hlc`, and `userId`, complete transport acceptance, then call `MessageStore.insert`. Only after that operation successfully settles does it return the exact allocated-and-transported `ChatMessage`. It never returns the insert result, same-id existing winner, `MessageRecord`, delivery status, or Runtime provenance. Rejection before or during local insertion returns no message and produces no local success projection. + +`onSessions` is the only application session-state truth. A normal accepted live change publishes the updated `onSessions` snapshot before the corresponding `onJoinRoom` or `onLeaveRoom` fact. Initialization, hydration, refresh, reconnect, host replacement, and replay publish snapshots only. The application keeps its frozen `leaveRoom()` then `joinRoom(command)` reconnect composition, but the Runtime ChatRoom adapter maps that first call to `RuntimeServer.reconnectDomain`: it rebuilds the current physical Chat session within the active `presenceId`, never invokes final logical release, never emits a synthetic empty snapshot or logical delta, and never leaves/rebuilds World. Final release remains an internal Lifecycle/Connection operation. `onMessage` publishes only the first durably accepted remote live `ChatMessage`; it excludes protocol control messages, history responses/replay, initial IDB reads, local sends, duplicates, conflicts, and store-watch replay. Chat and World session scopes remain distinct; same-origin pages share one Runtime logical Chat view, while different browsers keep separate views that converge. + +Reconnect execution and reconnect feedback are separate application concerns; neither is Runtime state. The actions-menu control derives availability only from `joined && !reconnecting`. One enabled activation creates one authoritative request identity and pending fact, immediately invokes the retained asynchronous `leaveRoom()` then `joinRoom(command)` composition, disables the Refresh button, and spins that button's own icon. It preserves the current `panelOpen` value and never waits for Toast subscription, mount, paint, or dwell. The button has exactly an ordinary eligible icon and a pending disabled/spinning icon; it has no Ready label, result region, or independent success/failure state. Every completion callback validates the request identity so stale work cannot clear a newer spin or feedback. Operation outcome remains independent from feedback, and the request reaches its bounded terminal after that outcome plus any feedback outcome settle. This browser-independent rule applies identically to Chrome and Firefox. + +Toast presentation remains one generic application capability. The existing generic Toast Domain/Extern/implementation contracts carry reconnect, Runtime readiness, and unrelated application feedback into the same original direct `AppMain -> ` surface. Reconnect and Readiness are business event sources only: they do not own a presenter, renderer, Toaster, React hook, or parallel Toast lifecycle. Delete `src/app/content/components/reconnect-toast.tsx`, `useReconnectToast`, and all reconnect-specific presentation naming. Any retained presentation adapter consumes only generic Toast descriptors, IDs, and presentation acknowledgements; it has no direct ChatRoom, Readiness, Runtime, network, or panel business authority. Business-to-Toast mapping stays in application Domain effects, while the reconnect request remains the sole owner of operation outcome, duplicate rejection, button pending state, and stale-request fencing. Generic Toast presentation may acknowledge visible paint, bounded absence, and request-correlated cleanup, but it cannot become a second reconnect pending or network truth. + +`AppMain` supplies the existing Motion translate containing mechanism and renders the only direct Toaster. There is no wrapper, reconnect-specific Toaster, panel-external launcher, host-page global overlay, second renderer, or source-specific visual layer. The Toaster retains `richColors`, current `themeMode`, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and the existing `dark:bg-slate-950 border dark:border-slate-600` Toast classes. No reconnect or readiness source introduces geometry, content-fit, pointer, opacity-tracking, pseudo-element, width, or placement styles. While mounted, generic Toast entries may represent reconnect loading/result, Runtime connecting loading, Runtime unavailable error, and unrelated feedback without one source dismissing another. Runtime ready is a continuing application state rather than an actionable success notification: a genuine transition to ready or mounted-surface ready snapshot only dismisses the stable `webchat-runtime-readiness` entry by ID and never publishes a success entry. Equal ready inputs are suppressed by the owning Readiness transition before application feedback observes them. A visibly painted reconnect loading entry contributes the accepted 300ms minimum dwell and request-local ordering. While App/Toaster is absent, presentation absence settles boundedly and never delays bootstrap, Runtime, reconnect, or button settlement. Active reconnect may enter a newly mounted Toaster once; terminal reconnect does not replay later. Matching cleanup addresses only the correlated Toast ID, and unscoped/global dismissal is prohibited. + +Remove both independent status surfaces: `App.tsx` no longer queries readiness for a fixed `WebChat connecting`/`WebChat unavailable` output, and `content/index.tsx` no longer creates the pre-App `WebChat unavailable`/Retry fallback when `initClient()` fails. Runtime lifecycle still owns immediate-replay `connecting | ready | unavailable` state for ChatRoom recovery and send preconditions; only its independent DOM presentation is removed. ReadinessDomain's State and Query remain the sole application readiness truth, and its SetState transition compares every mapped extern input to current State. Equal input performs no State write and emits no `StateChangedEvent`; a different value updates State and emits exactly one Event. Immediate current-state access remains Query/replay behavior, not a synthetic transition Event. + +ClientLease retains the five-second heartbeat that refreshes each page lease before the 15-second TTL, probes host availability, compares coordinator generation/host identity/page attachment, and initiates real recovery after replacement or loss. Its host-phase callback may continue carrying infrastructure snapshots; neither ClientLease nor the Readiness adapter adds a second equal-state filter or application truth. This keeps crash cleanup and MV3 host-replacement recovery while preventing repeated healthy ready samples from retriggering ChatRoom recovery or Toast feedback. + +While the surface exists, normal Runtime `connecting` may publish the stable loading entry and `unavailable` may publish the stable error entry. A genuine transition to `ready`, or a newly mounted surface reading the current ready Query, issues only an ID-scoped dismiss for `webchat-runtime-readiness`. It publishes no `Ready to chat` success. Dismiss, automatic Toast close, acknowledgement, and settlement do not feed back into readiness publication. This removes the visible approximately five-second success loop without changing heartbeat cadence, lease TTL, Runtime recovery, or manual reconnect success. When App/Toaster cannot exist, no alternate loading, unavailable, Retry, status, or result view replaces it. Owner task #228 supersedes the normal Runtime ready success mapping and equal-state Readiness transition behavior in round-27 exact `f4d9c401...`; that source and all exact-local Reviewer/test/smoke evidence cannot certify its replacement. + +`WorldRoomExtern` remains a separate projected application dependency with `getState`, `onState`, and `onError`; it aggregates private `(source, origin)` contributions into exact-origin multisets without exporting source identity. Application readiness continues to expose immediate-replay `connecting | ready | unavailable`. Only explicit composition roots wire concrete `impls`; Domain modules do not import `impls` or Runtime contracts. + +Persistence converges on a public typed `Database` extern with schema-linked store/key/value/index types, readonly item snapshots, scoped read/write transactions, primary/index range queries, atomic insert returning the exact existing winner, generic get/scan/count/put/delete/clear, transaction-level cancellation, successful-commit watch, and close. IndexedDB and Memory implement the same behavioral contract; a future backend is compatible only if it passes that suite. One internal concrete `MessageStore` facade exposes `insert`, `query`, `clear`, and `watch`; it is not a Remesh extern, public-barrel export, or host replacement point. `query()` returns every canonical record, while an optional readonly query object may contain only outer record `type` and `signal`. It owns strict `MessageRecord` decode/key invariants, same-id replay versus conflict classification, and application query-result invalidation, while id/range/order/limit and history `syncId`, cursor, cutoff, response `messages`, and `done` remain outside it. Remove `list`, compatibility aliases, status/mark/outbox/retry, and test-only conflict-count APIs. Production code uses `globalThis.setTimeout` and `globalThis.clearTimeout`; no naked timer dependency/extern is injected. Vitest fake timers provide deterministic timing while attempt/generation ownership remains explicit Runtime state. + +### 2.2 A logical join is provisional until physical room acceptance + +A Wire join Command may record desired/logical membership before the provider is ready enough to create its physical room. It is therefore not evidence that a subsequent send is accepted. `ConnectionDomain` SHALL keep the Runtime-owned, observable per-domain join attempt whose identity is fenced by the current host and domain lifecycle generation. The attempt becomes accepted only after the required physical Chat and World rooms can accept the initial `session` and complete World snapshot send. Only then may Connection command Session/World commit and complete the join operation. + +If a room is transiently unavailable, the Runtime may use a bounded readiness wait or bounded automatic retry, or atomically roll back the provisional attempt before surfacing a retryable failure. It SHALL not retain a terminal half-joined domain that receives remote sessions/presences while its pages have no local session. It SHALL not require a page reload, a manual reconnect, or a Firefox-specific branch to recover. Retries must preserve one logical join's session identity and cannot emit more than one initial local session, one physical room per `(host, roomId)`, or duplicate World presence for that attempt. + +Last-page release, explicit leave/reconnect, host replacement, and a late provider/room-open callback cancel or supersede the prior attempt. Every late completion must validate its host/domain generation before mutating Runtime state or emitting a page projection. The Runtime owns this coordination; pages continue to consume only projected Chat/World state and cannot maintain fallback queues. + +Physical acceptance and committed projection remain separate during every provisional window, including reconnect and room recovery. A peer-ready notification that arrives after the accepted initial room broadcast but before commit belongs to that attempt. After commit, the Runtime sends that peer exactly one targeted Chat session and one targeted current World snapshot for whichever initial broadcasts could not include it; a rolled-back or superseded attempt sends no catch-up, and a peer already covered by an initial broadcast receives no duplicate. Remote Chat sessions arriving through a provisional reconnect belong to the active attempt, remain invisible to pages before commit, and are included exactly once in the replacement snapshot after commit instead of entering the retained previous runtime or being overwritten by an empty attempt snapshot. + +A room-recovery timeout cannot merely stop awaiting an underlying join. If the timeout is a terminal failure for that attempt, the Runtime invalidates its generation and leaves or otherwise fences the pending join so a late physical completion cannot register a trusted room. If the Runtime instead retains ownership of the attempt as an automatic retry, it remains provisional and cannot report committed/trusted World membership until current World presence has been accepted. In either form, no settled state may expose a physical/trusted World room or `joined` snapshot without the corresponding current presence publication; a subsequent retry must publish the full current snapshot before commit. + +### 2.3 Physical sends isolate each provider target + +Artico Room `join`/`leave` events are not an atomic view of a call's data-channel state. A call may emit `join`, become sendable, then enter `closing` before its close callback produces Room `leave`. The Runtime transport's remembered-ready set is therefore only an advisory source of untargeted recipients, not proof that a later batch call can still send to every member. + +For an untargeted send, the transport snapshots the currently remembered peer ids. For an explicit string or array, it uses the supplied ids. In both cases it de-duplicates the snapshot while preserving order and invokes the pinned provider separately for each target. A target-local synchronous provider rejection is contained to that target; the transport continues every later distinct target exactly once and settles only after all target attempts finish. The rejected target receives no fact from that attempt and may participate again only through a later provider-ready/join transition. The no-target case remains a successful no-op, matching physical Room semantics. + +The transport SHALL NOT reject a whole Chat/World send solely because an individual call crossed the ready-to-closing window. This is essential after any earlier target has accepted the fact: turning that partial external publication into `AbortJoinAttempt` or `AbortWorldRecovery` would create remote state whose local provisional authority was rolled back. Room absence, stale/unjoined trusted-room selection, and pre-provider codec/validation failure remain operation-level rejections. Target isolation is still best-effort physical acceptance; it is not remote delivery acknowledgement and introduces no retry queue, outbox, delivery status, fallback, protocol field, public ChatRoom method, or persistence path. + +The exact-bound regression matrix covers the adapter and actual pinned `@rtco/client` transition, then binds it to initial Chat session, initial World snapshot, committed World presence, and World recovery publication. It must include both untargeted and explicit multi-target flows, prove later sendable peers are not skipped, and prove a peer-local miss does not duplicate already accepted targets. + +### 2.4 WireDomain is the anti-corruption boundary and provider injection point + +`WireDomain` is the only owner allowed to translate between provider callbacks/raw frames and trusted Runtime facts. Inbound flow is `RoomTransportExtern callback -> trusted room/source check -> immutable protocol decode/schema/size validation -> source-local ordered queue -> Wire Event`. Outbound flow is `Runtime Domain Command/Effect -> typed protocol value -> strict encode/validation -> RoomTransportExtern`. Business Domains consume only typed Wire Queries/Commands/Events and never raw bytes, provider rooms, Artico callbacks, or provider identities. + +`RoomTransportExtern` is private to Runtime composition and expresses only provider capabilities: stable local peer identity, room join/leave, targeted send, transport-confirmed inbound source, peer ready/leave, room close/error, and deterministic dispose. `RoomTransport` may remain as the concrete implementation shape behind that Extern; it is not a second application API and is not exported to UI, application Domains, or `src/protocol`. Artico implements this Extern in one provider adapter and may be imported only there and in the explicit host composition root. Replacing Artico requires a new provider implementation passing the same contract suite, not a rewrite of ChatRoom, Runtime Domains, protocol, persistence, or UI. + +Every immutable peer value terminates in one explicit mapping; protocol DTOs never become a shared mutable model: + +| Peer input/output | Inbound decode -> owner transition | Outbound owner intent -> existing encoding | +| ---------------------------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | +| `SessionMessage` | Wire accepted-message Event -> Session validates trusted source/generation -> Session commit/replace Command | Accepted Connection generation -> Session publication Effect -> Wire send Command | +| `SessionEndMessage` | Wire accepted-message Event -> Session validates source/generation -> idempotent logical-end Command | Final Session release after durable lease retirement -> Wire send Command before leave | +| `TextMessage` / `ReactionMessage` | Wire accepted-message Event -> Session validates bound user/session -> Delivery admit Command | Application send intent -> Session id/HLC allocation -> Wire send Command | +| `HistoryRequestMessage` | Wire accepted-message Event -> History request Command | History requester State -> Wire request send Command | +| `HistoryResponseMessage` | Wire accepted-message Event -> History response Command -> Delivery atomic batch admission | History provider State/page supply -> Wire response send Command | +| `WorldRoomMessage` | Wire accepted-message Event -> World replace-source Command | World current full snapshot after accepted Connection generation -> Wire send Command | +| peer ready/leave, room close/error | RoomTransport callback -> Wire fact Event -> Connection transition; Session/World cleanup through their Commands | Connection join/leave/recovery Effect -> Wire room Command -> RoomTransport Extern | + +The former imperative `WireExtern` route is removed in the clean cut. No Runtime Domain may call a concrete transport or provider callback directly. `WireCodec` and the strict peer schema remain immutable public protocol dependencies selected at composition; they do not own Runtime State. + +### 2.5 Remesh modules encode reuse, not shared authority + +A `Remesh.module` is permitted only when at least two host Domains instantiate a configurable capability with the same State shape, Commands, Queries, Events, transitions, cancellation, and failure semantics. Each module instance belongs to its host Domain. Cross-Domain shared authority still comes only from obtaining the same Domain instance through `domain.getDomain(...)`; instantiating a module twice creates two independent capabilities and cannot be used to share a network fact. + +Existing Status-like capabilities that use a `*Module` name must either become real `Remesh.module` definitions or lose the `Module` name. An Attempt/generation capability becomes a module only after Planner demonstrates that at least two flows are semantically isomorphic, including terminal failure, supersession, cancellation, and late-completion fencing. Delivery remains a direct unique Domain while it has no second isomorphic consumer. Codec/parser logic, ordering, size checks, and provider callback adaptation remain pure functions or Extern implementations rather than forced modules. + +### 2.6 Clean-cut owner migration and structural fail-before + +The Runtime replacement starts from clean baseline `9c90bb0...`; the requirements commit is authority-only and must not become a source ancestor. Before production repair, candidate-sensitive structural tests must fail on that baseline and bind each failure to the earliest wrong owner. Migration then moves one owner at a time, immediately deletes the old State/path, and never introduces a dual read, dual write, alias, fallback, compatibility adapter, or old `WireExtern` route. The final candidate is delivered only after all production code, tests, comments, tasks, architecture artifacts, and residue cleanup are complete. + +| Fail-before subject on `9c90bb0...` | Baseline failure that must be captured | Final pass condition | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| ChatRoom implementation authority | Retained command/session/local-session/published-session/recovery State exists outside Runtime Domains | Adapter has no writable network authority and only bounded in-flight/disposer infrastructure | +| Monolithic network ownership | One `NetworkDomain` owns multiple Connection/Session/World/History fact classes | Each fact is writable in exactly one matrix owner and the old catch-all owner/path is removed | +| Provider boundary | Imperative Wire/RoomTransport composition bypasses a private Runtime Extern | Only `WireDomain` obtains `RoomTransportExtern`; Artico imports are provider/composition-only | +| CQRS semantics | Query/Command/Event/Effect responsibilities are not structurally enforced per owner | Domain surface inventory and tests reject Query I/O/write, Event-as-command, Effect direct State write, and cross-owner mutation | +| Module integrity | Status-like `*Module` factories are not real `Remesh.module` instances | Each surviving `*Module` is real and reusable/isomorphic, or is renamed as a non-module helper | +| Immutable protocol | Internal refactor could accidentally alter protocol source/goldens | Every protected protocol file and canonical encoded-byte fixture is byte-identical to baseline | +| Acyclic graph | Catch-all/import paths obscure or reverse dependencies | Dependency test proves the documented Domain/Extern direction and no Runtime cycle | +| Artifact synchronization | Existing diagram shows one `NetworkDomain` and direct Artico transport | Architecture JSON/HTML show the seven owners, exact application port, private provider Extern, and immutable protocol boundary | + +For the current task #208 replacement, final acceptance is role-separated and follows exactly Coder -> Reviewer -> Owner. Coder implements, freezes one immutable exact, and reports identity and scope without running or claiming test gates. Fresh Reviewer inspects only code logic, Diff/scope, and architecture boundaries and runs no automated, static, build, or browser tests. Only a Reviewer logic PASS releases that exact to the Owner's local test environment. No QA task, QA gate, or QA verdict is required for this replacement; the Owner's local smoke is the sole current product-acceptance path. Existing structural, provider-contract, protected-input, automation, static/build, and production-browser acceptance controls remain candidate obligations; removing the QA route neither marks them complete nor transfers evidence. Merge remains separately authorized. Historical QA records remain immutable facts for their own exacts and transfer no evidence to the replacement, and a pass on baseline `9c90bb0...`, tooling exact `167e8817...`, or any earlier candidate transfers to none of the replacement's changed inputs. + +### 3. Background coordinates physical ports and host recovery; Lifecycle owns leases + +The extension background is the only host coordinator. Chrome/Edge keeps physical page-port liveness and host-phase observations in the Service Worker outside the Offscreen host; those platform facts let it recreate a missing host and replay idempotent attach Commands into the replacement `LifecycleDomain`, which remains the unique owner of domain leases, ref-count, and grace. Firefox keeps the coordinator and Runtime host in the persistent Background Page, where supported recovery is an in-context `HostOwner` replacement. Firefox process restart is recovered when the browser recreates the Background Page and a restored page idempotently reattaches. Direct `backgroundView.close()` is outside the supported recoverable lifecycle. Pages never create hosts. Concurrent creation requests await one ready result. While at least one physical page port is online, the coordinator's health sweep probes the Runtime provider and its `hostId`; for Chrome/Edge, an Offscreen document that still exists but has no responding provider is marked destroyed, closed, recreated, re-probed, and reattached without relying on a page watchdog; for Firefox, the sweep detects an in-context provider loss and drives the supported `HostOwner` replacement path rather than claiming that direct persistent-page closure is recoverable. Chrome/Edge uses a DOM-free background injector for this probe; it never reads content-page location. Firefox probes the directly owned in-context Server and replacement disposes the old comctx provider listener, Remesh store, rooms, and Artico peer before installing one new host. The coordinator SHALL NOT retain a second domain lease/ref-count/grace map. A page watchdog MAY supplement but is never the only controller. Host creation failure and steady-state loss surface through Lifecycle-backed `connecting/ready/unavailable` snapshot state — never a silent page-owned fallback and never a fixed-1s uncaught heartbeat error (startup readiness waits and steady-state heartbeat are separate thresholds). + +Host-recovery acceptance is platform-equivalent rather than artificially identical. Chrome MV3 directly destroys the Offscreen document while the Service Worker coordinator retains an active physical page port, then proves automatic host rebuild, idempotent Lifecycle reattach, and Runtime re-readiness. Firefox MV2 uses a test-owned process termination/restart with the same isolated profile and restored target tab, then proves one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection. If Firefox removes a temporary XPI on process exit, the harness MAY reinstall that same exact XPI as setup only; it SHALL separately prove profile/tab continuity and SHALL NOT describe setup as product auto-reinstall. Direct `backgroundView.close()` is negative evidence of the persistent-page platform boundary, not a product failure. No event page, reload watchdog, or business fallback is added; deterministic `HostOwner` dispose/swap coverage remains required. + +### 3.1 Background UI-service RPC is routed by service identity + +Notification and AppAction remain separate background capabilities even though both use the extension runtime message bus. Each service owns one versioned namespace prefix combined with the current runtime id; its provider and injector import the same proxy definition, while the other service's namespace is structurally disjoint. Heartbeat therefore proves the requested service is present rather than merely finding any unrelated provider on the bus. One request reaches one provider, yields one response, performs one side effect, and releases its request listeners back to the live-provider baseline. + +`NotificationDomain` preserves its existing synchronous `PushEvent` and starts the same OS-notification RPC exactly once. The otherwise-floating Promise is terminated by a request-local rejection handler that records one scoped console diagnostic. That failure cannot become an unhandled page error or terminate later notification requests, and it does not remove or duplicate the chat list, barrage, input, OS notification, or AppAction behavior. No comctx dependency fork, public method, shared failure state, retry, fallback, product copy, or Runtime/PresenceStore/peer-wire change is introduced. + +### 4. One connection per remote peer per domain + +`ConnectionDomain` keys connections by `(domain, remotePeer)`. Any number of local pages on that domain share it. Presence, session State, and buffered events remain in their named owners and are isolated across domains. Page teardown issues a detach Command to `LifecycleDomain`; Connection releases Chat only after the Lifecycle grace-expired Event. + +### 5. Immutable public protocol and WireDomain; one unified compressed string frame + +`src/protocol/index.ts` is the third-party-facing code-level public module, not a separate package. It exports only the Owner-frozen `ChatUser`, `ChatSession`, `HLC`, `MentionedUser`, `SessionMessage`, `SessionEndMessage`, `TextMessage`, `ReactionMessage`, `ChatMessage`, `HistoryCursor`, `HistoryRequestMessage`, `HistoryResponseMessage`, `ChatRoomMessage`, `ChatSite`, and `WorldRoomMessage` structures, their strict schemas/pure parse/check/validation, public limits/constants, and the public codec surface (`WireCodec` interface, `NativeWireCodec` reference implementation, `WireCodecError`). No alias, compatibility key, or structural extension is permitted without Owner intervention. Pure validation covers closed-union/unknown-key rejection, field and resource limits, mention body ranges, user/message size, origin-only and uniqueness rules, history-response reference completeness, and explicit-`now` HLC rules. The `NativeWireCodec` implementation owns the fixed security/interop algorithm; the public protocol contains no local records, UI models, projections, LWW/order implementation, Runtime RPC, WirePipeline queues, drop/apply/flush policy, or application orchestration. + +`RoomTransportExtern` delivers `(roomId, sourcePeerId, rawPayload)` callbacks to `WireDomain`. Routing and session binding trust only that context, never payload-reported identity. Every peer message uses one fixed frame: `base64(deflate(UTF8(JSON)))` — standard zlib deflate (RFC 1950, natively available via `CompressionStream`) plus Base64 to fit the string-only Room API. The public `NativeWireCodec` uses the standard Web codec APIs (`CompressionStream`, `DecompressionStream`, `Blob`, `ReadableStream`, `TextEncoder`, and `TextDecoder`) plus exactly two scoped `core-js` global-patch imports: `core-js/actual/typed-array/from-base64` and `core-js/actual/typed-array/to-base64`; no whole-package entry, pure export, or hand-written bytes/string conversion is allowed. Business code calls the standard `Uint8Array.fromBase64(value, {alphabet: 'base64', lastChunkHandling: 'strict'})` and `bytes.toBase64()` methods. Decode additionally requires `bytes.toBase64() === value`, so whitespace, alternate alphabets, padding errors, and non-canonical padding bits are source-local drops. UTF-8 decoding is fatal and decompression uses bounded `readLimited`. The JSONR wire codec and its interop adapter are eliminated; MessagePack is rejected (no gain after deflate). No framing header: the v2 room namespace carries the protocol version. + +`WireDomain` composes the public `WireCodec` contract and validators; production/default wiring may use `NativeWireCodec`. Its State and Effects own trusted transport context, ordered per-room send, parser selection, per-source decode/validate/apply, source-local drop logging, and queue bounds. Each source's pending decode queue is bounded (≤8 frames and ≤256KiB wire); malformed frames and queue overflow are dropped only for that source with rate-limited logging and without room reconnect. Delivery admission and the volatile 512-record/8MiB durable-ACK buffer belong to `DeliveryDomain`; a history response batch is accepted atomically or rejected without cursor advance or a subsequent request on overflow. These operational policies and scheduling types belong to their named Runtime Domains, not `src/protocol`. + +Chat messages are a strict closed discriminated union keyed by `type`; World has one strict payload schema selected by the trusted World `roomId`, so it carries no payload discriminator. No universal envelope: `room`, `sender peerId`, `version`, `sentAt`, `receivedAt` never appear on wire. Those forbidden envelope/context fields and any unknown keys are strict-parse failures for the entire source-local frame, not fields to strip or ignore. `receivedAt` is receiver-local metadata. Runtime Domains issue a typed Wire send Command and subscribe to typed accepted-message/peer/room/error Events; they never receive `decoded: unknown`, encoded strings, provider callbacks, or Artico values. This internal CQRS surface preserves the existing wire semantics without becoming a public protocol API. + +### 6. World presence as full peer snapshot + +World v2 has exactly `WorldRoomMessage extends ChatSession {sites: ChatSite[]}`, where `ChatSession = {sessionId, user: ChatUser}` and `ChatSite = {origin, title?, icon?, description?}`. It has no payload `type`; the trusted World `roomId` selects this strict parser. World and Chat share these structures while maintaining distinct session instances and room protocols. `WorldDomain` maintains the browser's active-domain registry and publishes a fresh complete snapshot on every registry change; it atomically replaces each trusted source snapshot and deletes it on peer leave. There are no composite keys, TTL, withdraw types, or reconnect-republish cycles. `host/hostname/href` never go on the wire. + +### 7. Chat as session-bound immutable events + +Chat v2 is exactly `ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage`, with `ChatMessage = TextMessage | ReactionMessage`. `SessionMessage extends ChatSession` and adds exactly `type:'session'` plus required opaque `presenceId`; `SessionEndMessage` is exactly `{type:'session-end', presenceId}`. `MentionedUser extends ChatUser` and adds exactly `ranges: [number, number][]`. Text and reaction messages are immutable; live `userId` must match the bound session user. Reactions carry `active: boolean` and apply LWW by `(targetId, userId, reaction)` over `(hlc, id)`. One logical paginated history sync uses one `syncId`; the request is `{type:'history-request', syncId, before?}` and the response is `{type:'history-response', syncId, users:ChatUser[], messages:ChatMessage[], done}`. The historical v2 migrations from predecessor `requestId`, mention `positions`, and history response `events` to `syncId`, `ranges`, and `messages` are an intentional clean-cut wire break already present in baseline `9c90bb0...`: strict schemas reject old-only, old+new, alias, and unknown-key frames; rejected affected frames produce no projection/store write. The Remesh DDD + CQRS replacement preserves the resulting `NativeWireCodec` framing and canonical history bytes exactly. The application send-preparation path retains its draft/image/message-size rules and public limits; system notices remain local-only. + +### 7.1 Logical presence generation lifecycle + +Session owns logical presence independently of physical transport membership. Its private durable record contains exactly one local active lease, unsettled `inflightEnd`, retryable `pendingEnd`, or observer-accepted `settledEnd` cleanup identity (if any), plus monotonically stable generation event time and a bounded observer ledger. The composition root implements this private `PresenceStoreExtern` with `browser.storage.session`; deterministic tests inject memory storage. Chrome MV3 constructs the concrete store in the Service Worker, where session storage is available, and the Offscreen Runtime injects it through a dedicated comctx adapter over a point-to-point Runtime Port. The Port name and comctx namespace are routing values, never credentials. Background accepts the Port only when transport `sender.id` is its own runtime id, `sender.url` is the exact Offscreen document URL, and no tab is present; content, options, and other extension contexts are disconnected before any comctx message is delivered. Each provider response is correlated to the exact Port binding that delivered its request; if that binding has detached, the response is dropped and never falls back to the current active or replacement Port. Offscreen accepts a response only while the same request remains pending on that exact binding. Injector response registration reserves the current transport generation in request order without opening a Port; comctx's local heartbeat registration unregisters before the actual `apply`, which consumes the oldest remaining reservation. A generation that terminates after request-ID registration but before pending insertion therefore rejects that original operation before any replacement connect or post and removes its adapter-owned one-shot response entry. Port disconnect, synchronous connect/send failure, and adapter disposal release the response entries for every terminal pending or not-yet-sent operation without replay, so stale or late responses traverse no retained operation closure; only a later new application request may open a replacement Port. The response registry remains iterable because authenticated provider and local heartbeat messages require dispatch; it cannot be replaced by `WeakSet`. This deterministic one-shot cleanup does not alter provider-owned long-lived callback handles, whose refresh/re-registration lifetime remains unchanged. Because Port send/disconnect is the dedicated transport liveness signal, the adapter satisfies comctx heartbeat preflight locally and transmits only the actual one-shot `load`/`save` operation. Offscreen registers no broadcast `runtime.onMessage` listener for PresenceStore and cannot accept a forged provider envelope. Firefox MV2 passes the same concrete store directly from its persistent Background Page into the shared host. `startHost` receives the two-method dependency and never reads a browser storage global. RPC and storage failures propagate to Session's existing request-local durability fences; there is no memory fallback, skipped write, second state owner, broadcast response, cross-generation delivery, automatic operation replay, or page relay. This is not MessageStore/database history, does not widen `RuntimeServer`, the eight-method `ChatRoomExtern`, or `ChatMessage`, and does not make the Extern a second State owner: Session alone classifies and writes the record. + +The complete six-timepoint actual-stack control uses independent Runtime Server/Session/Wire stacks connected by deterministic in-repo transport: + +| Time | Actor/fact | Required classification | +| ---- | ------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| T0 | A is online before B prepares | A is preparation baseline membership for B; B records no historical join for A | +| T1 | B publishes a new `presenceId` | B persists one immediate generation-scoped self join; A persists one observer join | +| T2 | B's SESSION is duplicated and C publishes an additional physical session for B's generation | snapshots converge; neither view adds a join or leave | +| T3 | B loses transport and D replaces its Runtime host from the same session storage | D republishes the same generation; neither view adds a join or leave | +| T4 | D performs final release | Session durably retires with `inflightEnd`, settles SESSION_END, durably records `settledEnd`, clears that cleanup marker, then Connection leaves; A persists exactly one observer leave | +| T5 | B later returns | a fresh generation and later event time produce one fresh self join and one fresh observer join | + +`PeerLeft` is provisional transport loss and produces snapshot convergence only. Session accepts final leave only from a valid generation end bound to its source, applies duplicate end facts idempotently, rejects a later SESSION for the ended generation, and suppresses logical changes while another active generation for that user remains. No timer, debounce, wall-clock expiry, source peer id, physical session id, or encoded-id convention classifies presence. The departing local page is not required to persist its own leave because shutdown cannot guarantee that local database write. + +Final release is a fenced durable transaction. Session first records a pending request without changing its committed active lease, then asks the private PresenceStore to atomically replace that lease with the same generation's `inflightEnd`. Only successful retirement persistence may replace Session's in-memory lease, release the domain from World's desired registry, and publish SESSION_END. The durable `inflightEnd` remains throughout the unsettled send. Explicit send rejection transitions it to `pendingEnd` before surfacing the request-local failure; same-host retry atomically transitions `pendingEnd` back to `inflightEnd` before resending the same idempotent end. Successful send settlement first replaces the unsettled marker with `settledEnd`, which owns cleanup but no active presence, and then removes that marker; only successful marker removal may release History/Session state and physically leave Chat plus the last World room. A failed retirement retains the active lease. A failed retry transition retains `pendingEnd`; a failed rejection or settlement transition retains the prior unsettled identity; marker-removal failure retains `settledEnd`; every failure retains physical membership where the current host still owns it and remains retryable. A same-user replacement with `inflightEnd` or `pendingEnd` may physically rebind only to continue the same END transaction, cannot expose a successful active join or carry live messages, and then clears all markers. A replacement with `settledEnd` assumes cleanup ownership without joining Chat/World or publishing SESSION/SESSION_END. Session exposes one private authoritative finalization Query backed by every pending release phase or a hydrated final-end marker. Server preflight and Session's allocation/send Commands both consult that Query, so `retiring`, `retrying`, `publishing`, `pending`, `settling`, `settlement-failed`, `cleaning`, and `cleanup-failed` reject text/reaction allocation and live send before any wire frame. Successful marker removal retains the pending fence until physical `ReleaseDomain` completes. Initial retirement persistence rejection instead removes the pending release while preserving `local`, so the still-active generation keeps allocation/send authority. Another user consumes none of these markers. A later explicit join after cleanup allocates a new generation. Thus every incomplete attempt remains recoverable, cleanup completion retains no retry fact, and no failure may publish a false local lifecycle fact or physically depart. + +Self-notice ownership stays at the Runtime/application boundary: only a Runtime snapshot with private `provenance:'join'` grants one pending local generation candidate, and `joinRoom` consumes that candidate after successful local join. Its deterministic notice identity includes user plus stable generation event time, so reconnect/recovery is idempotent while a later generation remains distinct. Observer notices are likewise local application records and never enter peer history. + +After canonical record projection and `(hlc,id)` sorting, the UI groups each maximal adjacent run of SystemNotice messages. A singleton renders unchanged. A run of two or more initially renders its latest notice and an expand/collapse icon control without a numeric count; expansion reveals every original notice in canonical order through a height/opacity transition, and reduced-motion preference removes the transition without changing content. Any non-notice message splits runs. Streaming history can insert Chat messages between notices or reposition them by event time, so grouping is recomputed from the complete latest sorted projection rather than retained as canonical or component-owned message state. A grouped row derives its UI identity from the first notice's persistent ID, so extending the run preserves component identity while a history split gives each resulting row a distinct canonical anchor. The row-kind discriminator is part of identity before React and Virtuoso: text is `message:`, a singleton is `single-notice:`, and a group is `notice-group:`. These leading namespaces remain disjoint even when an opaque wire-valid ID itself starts with another row kind's prefix. `MessageList` passes that same identity through the React key to Virtuoso; array indices, `first`/`last` presentation flags, and expand/collapse state never participate. + +### 8. Application-owned local truth with headless history orchestration + +The application/page Domain/model layer owns origin persistence. It stores `MessageRecord = ChatMessageRecord | SystemNoticeRecord` as a strict outer-type union. `MESSAGE_RECORD_TYPE` has exactly `CHAT_MESSAGE:'chat-message'` and `SYSTEM_NOTICE:'system-notice'`. `ChatMessageRecord` is exactly readonly `{type,id,message,user,receivedAt}`; text/reaction aliases retain their protocol subtype, `type === MESSAGE_RECORD_TYPE.CHAT_MESSAGE`, `id === message.id`, and `user.id === message.userId`. `Notice` is exactly readonly `{id,hlc,type,body}`, with `type:NoticeType` carrying the independent `join | leave | info` reason. `SystemNoticeRecord` is exactly readonly `{type,id,notice,user,receivedAt}` with `type === MESSAGE_RECORD_TYPE.SYSTEM_NOTICE`, `id === notice.id`, deterministic local-only identity, and no wire or peer-history projection. `DatabaseItem.key === record.id`, and both variants share one globally unique record-id space. Strict decoding selects the closed shape only through outer `record.type`; it never infers kind from Database key/id shape or `'message' in record`. There is no standalone `SYSTEM_NOTICE`, old outer notice `hlc`/`body`/`noticeType`, `LocalRecord`, `DurableEventRecord`, `event` field alias, `RecordStatus`, pending/sent/received state, outbox metadata, compatibility alias, or dual-read path. Record HLC/order helpers use `message.hlc` for Chat and `notice.hlc` for notice; reaction LWW/projections remain outside storage and `src/protocol`. + +`Database` is the only replaceable persistence contract. Its default IndexedDB and Memory implementations use a private `MessageDatabaseSchema` and private logical name/version/store/index configuration; external backends implement the public Database contract and pass the same suite rather than receiving a new public factory. The single internal concrete MessageStore is created around that Database and has only `insert`, `query`, `clear`, and `watch`; it has no independent backend implementations or fake port. Its `query()` performs one complete physical scan, strictly decodes each item independently, returns every valid canonical record, retains invalid rows physically, records bounded deduplicated private diagnostics without notifying record watchers, and then applies the optional exact outer-type filter; `signal` gates the physical read plus subsequent decode/diagnostic/filter work. It does not own peer-history protocol projection. The existing per-origin IndexedDB identity remains stable, including its existing v2 name, while private `MESSAGE_STORE_VERSION = 2` colocated with the native upgrade callback names the existing schema authority. No type index, migration, or version bump is introduced for this in-memory post-decode filter. App or wire versions and this abstraction-only change do not clear, rename, delete, or spuriously advance it. + +`HistoryDomain` owns history/network orchestration around that application-owned store: recent-first pull over the receiver/requester's own 180-day candidate window (`HISTORY_WINDOW_DAYS = 180`, single constant truth). The requester freezes its own `requester wall clock - 180 days` cutoff when a sync starts; remote response messages exactly at that cutoff are eligible and only earlier remote response messages are rejected. At its corresponding provider supply/session admission, the provider separately freezes its own `provider wall clock - 180 days` cutoff: local candidates exactly at that provider cutoff are eligible, and only earlier local candidates are excluded without deletion. The cutoffs are neither transmitted nor required to match: the requester independently validates every response against its own cutoff and has final acceptance authority, so a remote provider cannot expand the requester window, although clock skew can omit boundary candidates the requester would otherwise accept. Requester pagination/retry, including provider failover, retain the requester cutoff without re-reading time; provider local query, subsequent cursor, and local page failover retain its provider cutoff, while a dormant successor freezes its own provider cutoff at its own admission and retains it after promotion. `HistoryDomain` also owns requester/provider sessions, 10-second operational timeouts, ≤8MiB or ≤10,000-event operational budgets, supplier selection/failover, 5-second page supply attempts with AbortSignal-gated physical cancellation, and history admission. The public protocol defines only the typed `history-request`/`history-response` shapes, flat response `{syncId, users, messages, done}`, composite `(hlc, id)` cursor, public response-size/message-count limits, and pure user/message reference completeness checks. Because comctx callbacks are one-way, the internal Runtime page contract uses an explicit `{supplyId}` request/cancel event plus `resolveHistorySupply`/`rejectHistorySupply` RPC rather than treating callback return values as transport. `HistoryDomain` owns supplier/admission/final-settlement scheduling while `WireDomain` owns encode/send serialization and trusted-source queues; together they preserve one admitted path per provider source from supplier selection -> encode -> send -> final release without sharing writable State. That pipeline has four global active jobs and at most 32 admitted requests/8KiB of decoded request metadata. A replacement request occupies one bounded source-local dormant successor within those same global 32-request/8KiB limits while the old source job is unsettled; it cannot run concurrently and automatically promotes after physical settlement without another peer request. Timeout, leave, and release may remove an unstarted successor, while a started job remains counted until settlement. Queue admission, drop, apply, flush, and cleanup policy are internal Runtime contracts and are not protocol exports. A final response releases the source slot immediately after send settlement, and an old timer/token cannot close or delay a newer domain/request; peer and session ownership remain scoped by `(sourcePeerId, domain)`. + +### 9. Send-first local persistence without outbox + +Runtime readiness remains a send precondition: when unavailable, no message is allocated or persisted and the visible draft remains recoverable. After readiness, `SessionDomain` allocates id/HLC/userId, the Runtime completes transport acceptance through `WireDomain`, and only then the application adapter asks the internal MessageStore to insert the local `MessageRecord`. Once that operation successfully settles, `sendMessage` returns the exact allocated `ChatMessage` even when same-id first-winner handling reports an existing canonical value; it never substitutes that collision winner for this call's causal identity. The application uses the returned identity to emit exactly one local text-send projection without waiting for store watch, diffing visible records, or matching body/mentions. A rejected transport or local insert writes no successful local fact and returns no message. The Owner explicitly accepts that successful transport followed by browser exit or local insertion failure can leave a remote-visible message absent from local history. No delivery status, pending/sent/received transition, durable outbox, same-id crash retry, hidden callback, recovery, or fallback remains. Future reliable delivery would require a separately approved complete state machine. + +### 10. HLC discipline + +HLC timestamp/counter must be finite non-negative safe integers; events more than 5 minutes in the receiver's future are rejected and never advance the clock. All ordering, cursors, and LWW use `(hlc, id)`, closing v1's same-clock collision and future-poisoning defects. The public validator receives the receiver's current time as an explicit `now` argument; no protocol code calls `Date.now()`. The comparison implementation belongs to the application/page Domain/model layer (or a shared Domain/model module when both pages and Runtime consume it), while the public protocol defines only the canonical ordering rule. + +### 11. No compatibility and no migration + +v1 protocol is abandoned, not bridged. Old unstorage `LocalMessage[]` data is neither read nor converted into or retained by the v2 canonical store; v2 starts with the final store. Unmerged development residues are cleaned directly. This also makes the JSONR wire codec and its adapter, page-side routing, reaction toggle, history upsert, and HLC-only cursor deletion-only work. + +## Risks / Trade-offs + +- [Old local history is lost on upgrade] -> Explicit Owner ruling (`295d56a0`: "不兼容,不需要管旧历史"); accepted product cost of the one-shot rebuild. +- [Old clients lose the shared rooms] -> Accepted; v2 namespaces isolate rather than error. +- [Clean-cut owner migration can expose hidden coupling] -> Structural fail-before binds each old owner before movement; one owner moves at a time and the old owner is deleted immediately. No dual architecture is accepted as mitigation. +- [Domain splitting can create dependency cycles] -> Planner must audit the final graph before implementation, and dependency tests enforce the documented acyclic direction on the candidate. +- [Overusing modules can hide unique invariants] -> A module is allowed only after semantic-isomorphism proof; unique owner logic and pure functions remain direct. +- [Provider abstraction can become a second business API] -> `RoomTransportExtern` stays Runtime-private and provider-neutral; the eight-method `ChatRoomExtern` remains the only application port. +- [Big-bang migration risk] -> Mitigated by fake-deterministic owner/provider/race tests, repository browser automation, and exact-bound Chrome/Edge and Firefox verification in the Owner's local test environment. +- [Transport succeeds but local persistence fails or never starts] -> Explicit Owner decision B accepts remote-visible/local-missing history in this crash window; no delivery status, outbox, retry, or hidden fallback remains. +- [History budgets drop very old events for some peers] -> Explicit resource contract; recent data is always preserved first. +- [P2P data is not authenticated] -> Out of scope by decision; profiles/sites remain untrusted display data. + +## Migration Plan + +1. Freeze this requirements-only Remesh DDD + CQRS authority and synchronized architecture diagram without changing Runtime source; keep the authority commit outside source ancestry. +2. Planner reviews the authority, audits Remesh/DDD/CQRS dependencies and cycle risks, confirms concise final filenames, and decomposes one clean Runtime replacement from baseline `9c90bb0...`. +3. Before production repair, add structural fail-before tests on `9c90bb0...` for the stateful ChatRoom adapter, monolithic Network owner, missing private provider Extern/Wire Domain, CQRS violations, false Module names, dependency cycles, and protected peer-wire bytes. +4. Establish `RoomTransportExtern + WireDomain` and move trusted room/source, protocol translation, queues, and provider callbacks behind it; remove the old `WireExtern` route in the same owner step. +5. Split Runtime State one owner at a time into Lifecycle, Connection, Session, World, History, and Delivery. After each move, delete the corresponding old `NetworkDomain` State/read/write path immediately; never preserve dual reads/writes or compatibility aliases. +6. Make the Runtime ChatRoom implementation state-free, remove all retained join/session/recovery authority, and adapt internal comctx Commands/Events cleanly while preserving the exact public eight methods, persistence-before-ACK, history supply, and causal send result. +7. Convert surviving reusable Status/attempt capabilities to real `Remesh.module` only when the isomorphism rule is satisfied; otherwise rename helpers and keep unique logic in its owner. Finish all comments, tasks, architecture artifacts, tests, and residue cleanup before validation. +8. Freeze one clean source candidate whose sole source parent is `9c90bb0...`. Coder reports identity and scope only; fresh Reviewer inspects code logic, Diff/scope, and architecture boundaries only. Neither role runs or claims automated, static, build, or browser gates. Requirements and independent Archify tooling commits are not ancestors. +9. After Reviewer logic PASS, synchronize the same immutable exact directly to the Owner's local test environment for the sole current product smoke and verdict. Create no QA task and require no QA gate or verdict for the task #208 replacement. Push/PR update does not require separate Owner authorization; merge remains a separate explicit Owner decision. + +Rollback is code-only before release: revert the final candidate to the published `9c90bb0...` functional baseline. No data migration exists to roll back, and no intermediate dual-owner candidate is releasable. + +## Comment and terminology contract + +Necessary comments remain part of the implementation contract. Preserve and update comments that explain the Remesh DDD owner boundary, non-obvious CQRS transition/Effect semantics, trusted `WireDomain`/provider boundary, codec/schema validation layers, send-first/local-insert ordering and its accepted loss window, ACK/LWW ordering, Database transaction/watch/cancellation/value isolation, history pagination/timeouts/physical cancellation/resource limits, and World/Chat session lifecycles. Explain the one-way Runtime -> public-protocol dependency direction, why protocol validation receives `now` explicitly, why the ChatRoom adapter cannot cache recovery authority, and why a surviving `Remesh.module` is semantically reusable. Do not add line-by-line narration. The new head must remove stale `NetworkDomain` catch-all ownership, old `WireExtern` routing, false `*Module` naming, ChatRoom join/session caches, `LocalRecord`, `DurableEventRecord`, `event` wrapper, status/outbox/retry, local role, history, and system-record terminology across schema, type, function, test, and documentation surfaces. + +## Open Questions + +None. All requirement-level decisions were confirmed by the Owner, including Remesh DDD + CQRS as the mandatory architecture, the seven unique Runtime owners, the state-free ChatRoom adapter, private `RoomTransportExtern`, conditional real `Remesh.module` use, immutable current peer-wire bytes/schema, clean-cut internal comctx freedom, v2 without compatibility, no old-history migration, the exact Database/MessageRecord/Notice/MessageStore shapes and id equalities, reliability option B, transport-success-before-local-insert ordering, and the causal `Promise` send result. diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/proposal.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/proposal.md new file mode 100644 index 00000000..42d126ec --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/proposal.md @@ -0,0 +1,93 @@ +## Why + +WebChat currently creates and owns WebRTC connections inside each content-script page. Every tab on the same domain duplicates peer connections, page refresh destroys healthy connections, and the global WorldRoom is joined once per tab — amplifying presence counts and forcing a full-mesh topology per page. Connection, session, and routing logic is scattered across page lifecycles that the browser can destroy at any time. + +In addition, the current peer wire protocol carries accumulated defects that cannot be fixed compatibly: WorldRoom presence is keyed by page-scoped `peerId` (a single-Runtime browser cannot express multiple domains), presence publishes full page URLs and unconsumed metadata, reactions toggle on receipt (replays corrupt state), history sync uses an HLC-only incremental cursor (permanently loses events across partitions) and lets untrusted history upsert overwrite local messages, and arbitrary future-HLC values can poison ordering. + +The Owner has authorized a one-shot architecture AND protocol rebuild with no backward-compatibility constraint. + +Independent cumulative review of source exact `b818f504...` found one remaining physical-provider race. Artico emits Room `leave` only after a previously open call has already entered `closing`; during that interval, one non-sendable target in a Room target-array throws synchronously and prevents later sendable peers from receiving the same Chat session or World presence. This can skip healthy peers and can partially publish a provisional fact before the Runtime rolls back locally. The replacement therefore needs explicit per-target provider isolation without changing the public protocol, application API, persistence, or reliability model. + +Source exact `9c90bb0...` closes that functional race, but the Owner identified a separate pre-merge architecture defect: the page Domain, stateful `src/domain/impls/runtime/ChatRoom.ts`, and oversized `NetworkDomain` distribute or duplicate join, session, recovery, and history authority. The provider boundary also remains an imperative `Wire`/`RoomTransport` path rather than a private Runtime Extern, while several `*Module` helpers do not use `Remesh.module`. The Owner therefore requires this replacement to use **Remesh DDD + CQRS** as an enforceable state-authority model, not as naming or directory decoration. Current peer-wire message types, schemas, codec behavior, and canonical bytes remain immutable except for the later Owner-authorized logical-presence exception: SESSION requires one opaque `presenceId`, and a strict SESSION_END fact closes that generation without backward compatibility. `ChatMessage`, history, World, limits, codec behavior, and every unrelated byte remain frozen; the internal page-to-Runtime comctx control contract may change cleanly when needed to establish the new boundaries. + +## What Changes + +- **BREAKING (internal architecture)**: Remove page-owned WebRTC entirely. All peer connections, sessions, presence, and message routing move into a shared headless Remesh Runtime; pages retain only UI/application state. The application-facing `ChatRoom` is one replaceable extern with exactly `joinRoom`, `leaveRoom`, `sendMessage`, `onMessage`, `onJoinRoom`, `onLeaveRoom`, `onSessions`, and `onError`. Its contracts directly type-import the exact public protocol `ChatUser`, `ChatSession`, `ChatSite`, `ChatMessage`, and mention value; it exposes no Runtime snapshot, peer/source, host, timer, IDB, page, lease, retry, or concrete implementation type. `sendMessage` returns the exact `ChatMessage` allocated and transported by that call only after its local MessageStore operation settles, so the application can project that local send causally without a store scan or hidden callback. The application adapter consumes private Runtime details through one internal session-event path. The application layer owns UI projections; `impls` own Runtime/platform/storage side effects, and only composition roots choose concrete implementations. The Runtime imports the public protocol module one way; protocol code does not import Runtime, storage, UI, or application models. +- **BREAKING (internal state authority)**: Replace the stateful ChatRoom Runtime implementation and monolithic Network ownership with a Remesh DDD + CQRS graph. Application/page Domains may read Queries, issue Commands, and subscribe to Events; they own only retained user input and display read models, never writable Runtime network facts. `src/domain/impls/runtime/ChatRoom.ts` becomes a state-free application adapter and persistence-before-ACK/history-supply boundary. Runtime authority is uniquely split: Lifecycle owns page lease/grace; Connection owns join/leave/reconnect and generation; Session owns committed Chat sessions, snapshots, id/HLC allocation; World owns presence; History owns requester/provider sessions, cutoffs, cursors, and batches; Delivery owns inbound sequence and un-ACK buffering; Wire owns trusted rooms/sources, strict protocol translation, queues, and provider callback translation. +- **BREAKING (private provider injection)**: Introduce a Runtime-private `RoomTransportExtern`; Artico exists only in its provider implementation and composition root. `RoomTransport` may remain only as that implementation shape. `WireDomain` is the sole anti-corruption boundary between trusted Domain events and the immutable peer protocol/provider callbacks. No UI, application Domain, public protocol, or non-Wire Runtime Domain may import Artico or provider types. +- Enforce CQRS semantics per Domain: Command is intent, Query is pure read/derivation, Event is an already-occurred fact, Effect performs external I/O and returns outcomes to an owning Domain, and Extern injects an external capability without becoming a second state owner. Every Runtime Domain has an explicit State/Query/Command/Event/Effect/Extern matrix and one unique writable owner for each fact. +- Use `Remesh.module` only for reusable/configurable state machines whose state, transitions, and failure semantics are genuinely isomorphic in at least two host Domains. Existing Status-like `*Module` capabilities either become real `Remesh.module` instances or lose the misleading name. Shared Runtime state is obtained from one Domain instance through `domain.getDomain(...)`; a module never creates cross-Domain authority by itself. +- **BREAKING (wire protocol)**: Replace the released v1 peer wire protocol with the current v2 design, with no old-client compatibility. World and Chat use separate v2-namespaced rooms with strict closed schemas. Every message uses one fixed frame `base64(deflate(UTF8(JSON)))`; the public `NativeWireCodec` reference implementation uses native `CompressionStream`/`DecompressionStream`, fatal UTF-8 decoding, bounded `readLimited`, and a direct runtime dependency on only these scoped `core-js` global-patch imports: `core-js/actual/typed-array/from-base64` and `core-js/actual/typed-array/to-base64`. Business code calls the standard `Uint8Array.fromBase64(...)` and `bytes.toBase64()` methods. `WireDomain` composes that immutable `WireCodec` contract and owns trusted-context/parser/queue/drop behavior. The JSONR wire codec and its interop adapter, MessagePack candidates, framing headers, page-side routing, reaction toggle, history upsert, and HLC-only cursor are all eliminated. This historical v1-to-v2 cutover is already present in baseline `9c90bb0...`; the Remesh DDD + CQRS replacement preserves it, with only the separately authorized SESSION generation and SESSION_END lifecycle shapes changing. +- **BREAKING (local data)**: No migration or import of existing local history. The old unstorage `LocalMessage[]` data is neither read nor converted into or retained by the canonical store; v2 starts directly with the final immutable event store. Unmerged development residues are simply cleaned up. +- Add a shared headless Remesh Runtime core with browser host adapters: Chrome/Edge use an Offscreen Document, Firefox uses an equivalent long-lived persistent Background Page. Hosts only adapt lifecycle. Host recovery acceptance is platform-equivalent: Chrome MV3 directly destroys the Offscreen host while the Service Worker coordinator retains an active physical page port, asserts automatic rebuild, and replays one idempotent Lifecycle attach; Firefox MV2 terminates and restarts the test-owned Firefox process with the same isolated profile and restored target tab, then asserts one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection. No fallback to the old page-owned architecture, Firefox event page, reload watchdog, business fallback, or duplicate lease authority is introduced. +- The extension background remains the sole host coordinator. For Chrome/Edge, the Service Worker lives outside the Offscreen host and retains physical page-port liveness plus host phase, so a missing or destroyed Offscreen Runtime is rebuilt single-flight while a domain page remains online; it then replays idempotent attach Commands into `LifecycleDomain`, which remains the unique owner of domain leases/ref-count/grace. For Firefox, the coordinator and Runtime host share the persistent Background Page: supported recovery is in-context `HostOwner` replacement, while browser process restart relies on Firefox recreating the Background Page and a restored page idempotently reattaching. Direct `backgroundView.close()` is outside the supported recoverable lifecycle and is not a product failure; no event page, reload watchdog, business fallback, or coordinator-owned domain lease map is added. +- A logical Runtime domain join is not transport acceptance. A provider/peer may be alive while its requested physical Chat or World room is still unavailable. Cold start, restored Firefox page/process restart, and normal host startup SHALL converge automatically only after the required rooms accept the initial Chat session and World presence. A transient not-ready room SHALL use bounded Runtime-owned waiting/retry or atomic rollback; it SHALL NOT leave a retained logical/domain join that can receive remote traffic but has no local session/presence projection. Attempt cancellation, last-page release, host replacement, and late room-open signals SHALL be generation-scoped so stale work cannot publish a duplicate session or presence. +- Treat Artico Room readiness as advisory at send time. For both an untargeted ready-peer snapshot and an explicit one-or-many target set, attempt each distinct target independently in deterministic order. A target that became non-sendable before its delayed Room `leave` is a missed recipient for that operation; its target-local provider rejection SHALL NOT stop a later target, reject the whole room send, or roll back a provisional join/recovery after another peer already received the fact. The operation settles after all target attempts, including the empty/no-sendable-peer case. A missing physical room and codec/validation failure remain operation-level failures. This is best-effort physical acceptance, not delivery acknowledgement: it adds no retry, status, outbox, fallback, wire field, public method, or persistence behavior. +- Per domain, multiple local pages share one connection to the same remote peer; connections of different domains stay isolated. +- Introduce one unified 5-second grace rule: after the last page of a domain disconnects, the ChatRoom connection, Runtime domain state, un-ACK event buffer, and WorldRoom domain presence are all retained for 5 seconds and released together. A refresh (F5) inside the grace window reads the Runtime snapshot directly with no false offline/online transition. +- Classify Chat presence by causal logical generations rather than physical membership. SESSION carries required opaque `presenceId`; Session persists its private active lease and bounded observer ledger in browser session storage, reuses the generation across refresh/reconnect/recovery/reattach/host replacement, and treats PeerLeft as provisional. Final release durably replaces the active lease with a private `inflightEnd` identity before publishing strict SESSION_END. A rejected retirement keeps the same active lease plus Chat/World physical membership and sends no end. Explicit end rejection durably transitions that identity to retryable `pendingEnd`; same-host retry durably transitions it back to `inflightEnd` before resending. A same-user replacement with either unsettled marker continues that exact END transaction and cannot carry live messages; it does not restore the generation as an ordinary active presence. Successful END settlement durably transitions the identity to private `settledEnd` cleanup ownership before marker removal. A replacement with `settledEnd` may only remove that marker: it joins no room, publishes no SESSION, and cannot revive the observer-ended generation. Session's authoritative finalization state fences text/reaction allocation and live send from the first pending retirement through physical release, including unsettled END and settled cleanup failure; a rejected initial retirement removes that pending request while retaining `local`, so the still-active generation remains usable. Connection physically departs only after marker removal succeeds; every transition failure retains a safe retry identity and physical membership. Complete cleanup leaves no marker. Preparation peers, duplicates, and additional physical sessions create no notice; final end creates one observer leave; a later explicit return allocates a fresh generation and join. +- Keep durable presence storage in a browser context that actually exposes `browser.storage.session`. Chrome MV3's Service Worker owns the concrete two-method PresenceStore and the Offscreen Runtime consumes it through a dedicated comctx adapter on a point-to-point Runtime Port; the Port name/namespace is routing only, Background validates the exact Offscreen transport source before accepting messages, and each response is confined to the exact live request binding that originated it. From comctx request-ID response registration onward, each one-shot call reserves exactly one transport generation. Generic response subscription opens no Port; local heartbeat subscription removes itself before the actual operation consumes the oldest reserved generation. If that reserved generation terminates before `apply` enters the binding-owned pending registry, the operation rejects before any replacement connect/post and the adapter deterministically removes that operation's one-shot response entry. A detached old binding can never fall back to an active replacement Port. Disconnect, synchronous connect/send failure, or adapter disposal rejects every terminal operation and releases every adapter-owned response entry without replay; only a later new application call with a new request ID may reconnect. Provider-owned long-lived callback handles retain their existing provider-refresh lifetime and are not part of this one-shot cleanup. Content/options requests, uncorrelated/replayed responses, and broadcast forged provider responses reach no store method or Offscreen callback. The Offscreen document never dereferences `browser.storage.session`. Firefox MV2's persistent Background Page passes the same concrete store directly to the shared host. Neither platform may substitute memory storage, skip `load`/`save`, swallow persistence or Port rejection, automatically replay an interrupted operation, or widen `PresenceStoreExtern`, `RuntimeServer`, peer wire, or any public contract. +- Validate records at every uncontrolled boundary with existing strict Valibot schemas. Invalid inbound is rejected before persistence/barrage/list publication and does not poison later delivery. Retained physical rows decode independently, remain untouched, and produce bounded deduplicated private diagnostics without canonical-record watcher notification; valid rows remain queryable and visible. +- Group only adjacent SystemNotice messages after canonical projection and sorting. Singletons remain unchanged; runs render the latest notice and an icon expand/collapse control without a numeric count; the hidden notices animate open/closed with reduced-motion support; non-notices split runs; streaming history recomputes grouping without changing canonical records or synchronizing local notices. Each grouped UI row is anchored to its first persistent notice ID. Before React or Virtuoso sees a row, text, singleton-notice, and grouped-notice identities enter the structurally disjoint `message:`, `single-notice:`, and `notice-group:` namespaces respectively, so an arbitrary wire-valid ID cannot impersonate another row kind. Array position, edge flags, and expansion state never participate in identity. +- Isolate background Notification and AppAction RPC through mutually exclusive versioned comctx namespaces shared by each service's provider and injector. A notification RPC rejection is consumed by its own request and reported as a local diagnostic rather than an unhandled page error; the existing notification request Event, chat list, barrage, input, OS notification, and AppAction behaviors otherwise remain unchanged and exactly once. +- Keep the WorldRoom as a v2 room joined once per browser by the Runtime. Its exact public payload is `WorldRoomMessage extends ChatSession {sites: ChatSite[]}` with no payload discriminator; trusted `roomId` selects the World parser. The snapshot is atomically replaced per source peer; `ChatSite = {origin, title?, icon?, description?}`; `host/hostname/href` never go on the wire. World and Chat use the same `ChatSession {sessionId, user: ChatUser}` structure while maintaining distinct session instances and room protocols. +- Chat v2 uses the exact strict union `ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage`, where SESSION requires opaque `presenceId`, SESSION_END carries exactly that generation, and `ChatMessage = TextMessage | ReactionMessage`. `MentionedUser extends ChatUser` and adds exactly `ranges: [number, number][]`, so mentions contain only `id`, `name`, `avatar`, and `ranges`. Text and reaction messages use `userId`; reaction state is explicit `active:boolean` and LWW by `(targetId, userId, reaction)` over `(hlc, id)`. One paginated history sync uses one stable `syncId`; history response remains exactly `{type:'history-response', syncId, users: ChatUser[], messages: ChatMessage[], done}` and pagination uses `before/messages/done`. Ordering, cursors, and LWW are uniformly `(hlc, id)`; HLC values are strictly validated (non-negative safe integer, max 5-minute future skew, never advancing the clock when rejected). `requestId -> syncId`, mention `positions -> ranges`, and history response `events -> messages` are intentional breaking wire-key migrations: new strict schemas reject any old key, old/new pair, or alias, with no dual-read fallback; mixed versions cannot exchange affected frames, and other message families remain independently parser-scoped. +- The local store becomes the single truth of `MessageRecord = ChatMessageRecord | SystemNoticeRecord`. One `MESSAGE_RECORD_TYPE = {CHAT_MESSAGE:'chat-message', SYSTEM_NOTICE:'system-notice'}` constant defines the outer closed-union discriminator. `ChatMessageRecord` is exactly readonly `{type,id,message,user,receivedAt}` with `type:MESSAGE_RECORD_TYPE.CHAT_MESSAGE`, `id === message.id`, and `user.id === message.userId`; text/reaction subtype aliases retain the protocol subtype. `Notice` is exactly readonly `{id,hlc,type,body}`, where its `type:NoticeType` remains independently `join | leave | info`. `SystemNoticeRecord` is exactly readonly `{type,id,notice,user,receivedAt}` with `type:MESSAGE_RECORD_TYPE.SYSTEM_NOTICE` and `id === notice.id`; it remains deterministic and local-only. `DatabaseItem.key === record.id`, and Chat/notice record ids share one globally unique key space. Strict runtime decoding branches only on outer `record.type`, never Database key/id shape or property presence. The old standalone `SYSTEM_NOTICE`, outer notice `hlc`/`body`/`noticeType`, delivery `status`, pending/sent/received state, outbox metadata, legacy `LocalRecord`/`DurableEventRecord`/`event`, and every alias/dual-read path are removed. +- The only host-replaceable persistence extern becomes a typed transactional `Database` implemented by IndexedDB, Memory, or a future backend that passes the same contract suite. A single internal concrete `MessageStore` facade uses that Database for atomic first-value-wins insertion/conflict preservation, typed canonical-record query, clear, and cross-context watch; it is not a Remesh extern, public-barrel API, or host replacement point. Database primitives, store/index names, items, transactions, and codecs do not enter Chat/UI or the public protocol. +- Local outbound sending becomes intentionally send-first with no durable outbox: after readiness, `SessionDomain` allocates id/HLC/userId and transport acceptance completes through `WireDomain` before `MessageStore.insert` writes the local `MessageRecord`. After that insert operation successfully settles, `sendMessage` resolves with the exact allocated-and-transported `ChatMessage`, not an insert result, status DTO, collision winner, or storage record. The application uses that identity for exactly one causal local projection, independent of store-watch timing or same-content concurrent sends. A failed send or rejected local insert produces no returned message or local success projection and keeps the draft recoverable. A successful transport handoff followed by browser exit or local insertion failure MAY leave a remote-visible message absent from local history; the system SHALL NOT retain status, same-id crash retry, hidden recovery, or fallback behavior for that window. +- `src/protocol/index.ts` is the code-level public peer contract: it exports the exact Owner-frozen `ChatUser`, `ChatSession`, `HLC`, `MentionedUser`, `SessionMessage`, `SessionEndMessage`, `TextMessage`, `ReactionMessage`, `ChatMessage`, `HistoryCursor`, `HistoryRequestMessage`, `HistoryResponseMessage`, `ChatRoomMessage`, `ChatSite`, and `WorldRoomMessage` structures plus their strict schemas, pure parse/check/validation, protocol limits/constants, and the fixed codec surface (`WireCodec`, `NativeWireCodec` reference implementation, `WireCodecError`). No field, type, method, alias, compatibility key, or public extension may be added or renamed without explicit Owner intervention. Protocol contains no local records, UI models, projections, Runtime RPC, queue, or orchestration. `WireDomain` adds trusted transport context, source-local decode/drop handling, and typed Runtime send Commands/accepted-message Events without changing the peer representation. +- Converge persistence on the Owner-frozen `Database` surface: schema-linked store/key/value/index types; non-empty transaction scopes; primary/index range queries; readonly `DatabaseItem` snapshots; atomic insert returning the exact existing winner; generic get/scan/count/put/delete/clear; transaction-level cancellation; successful-commit watch; and close. The internal concrete `MessageStore` exposes only `insert`, `query`, `clear`, and `watch`. `query()` returns every canonical record; its only optional query fields are the outer record `type` and `signal`. The implementation strictly decodes every row of a complete physical scan independently, isolates and retains invalid rows with bounded private diagnostics, returns all valid canonical rows before optional type filtering, adds no index or schema migration, and keeps id/range/order/limit/cursor/syncId outside this facade. History orchestration/projection remains outside both, and test-only conflict counters remain absent. Message history stays per origin; inbound messages are persisted atomically and only the first remote live insert triggers side effects; the Runtime keeps no history copy. +- Remove naked timer dependency injection from production Runtime/application adapters. Supported browser targets and Node/Vitest use `globalThis.setTimeout`/`globalThis.clearTimeout`; tests use fake timers. Attempt ownership, generation fencing, and cleanup remain explicit state rather than becoming a timer extern. +- Keep the actions-menu entry "Reconnect this site" without extending ChatRoom: the application Domain retains the exact `leaveRoom()` then `joinRoom(command)` composition, while the Runtime ChatRoom adapter maps that first call to current-domain reconnect rather than final logical release. Refresh capability depends only on the current domain being joined and no reconnect operation already being active. Main-panel visibility is presentation state: activation neither reads nor changes it. Every enabled activation creates one authoritative request identity/pending fact, immediately starts the existing leave/join composition in Chrome and Firefox, disables the Refresh button, and spins only that button's own icon. The button has no Ready text, success region, or second result state; it returns to its ordinary eligible icon when the matching request terminal settles. Toast is one generic application feedback capability, not a reconnect subsystem: reconnect, Runtime readiness, and unrelated application notifications publish through the existing generic Toast APIs and share the original direct `AppMain -> ` surface. Delete the reconnect-specific `reconnect-toast` module, `useReconnectToast` hook, presenter naming, and separate lifecycle truth. Any presentation adapter consumes only generic Toast descriptors and stable IDs; it does not import or own ChatRoom, Readiness, panel, Runtime, or network behavior. The direct `Toaster` retains the original `richColors`, current theme, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and existing dark Toast class names, with no wrapper, launcher layer, or custom visual treatment. Runtime `connecting`/loading and `unavailable`/error feedback may appear only as generic Toast entries while that surface is mounted. ReadinessDomain is the sole application readiness-transition authority: its state command compares the mapped `connecting | ready | unavailable` input to current State and emits `StateChangedEvent` only when the value changes. Equal input performs no State write and emits no Event, so periodic healthy `ready` callbacks cannot trigger ChatRoom recovery or application feedback again. ClientLease retains its five-second heartbeat, 15-second page lease, host-generation/host-id/page-attachment checks, immediate host-phase callback, and real recovery behavior; no second dedup state belongs in ClientLease, the Readiness adapter, AppFeedback, or Toast presentation. Normal Runtime `ready` SHALL NOT publish `Ready to chat` or any other success descriptor: a genuine transition to ready and a surface mount/remount whose current snapshot is ready only dismiss the stable `webchat-runtime-readiness` entry by ID if it exists. That scoped dismissal, Toast acknowledgement, automatic close, and presentation settlement SHALL NOT publish another readiness descriptor. Remove the App-root fixed readiness output and the pre-App bootstrap `WebChat unavailable`/Retry view; when Toast cannot render because App/Toaster is absent, no alternate loading, unavailable, status, or result view is created. Feedback absence never delays bootstrap, Runtime, reconnect dispatch, or the independently captured reconnect outcome. One reconnect request may correlate its generic Toast entry to its authoritative request ID for stale-callback fencing, request-local cleanup, and the accepted 300ms visible loading dwell, but the generic Toast layer never becomes a second reconnect pending, operation, or network truth. Opening during an active request may present only its current pending entry; a terminated request is not replayed. Unmount, absence, or presentation failure settles boundedly, and cleanup never dismisses unrelated Toasts globally. Reconnect reuses the active `presenceId`, rotates only physical session membership, emits snapshots without logical join/leave facts or new notices, does not rebuild or leave WorldRoom, and adds no Options global reconnect. Owner task #228 removes the repeating normal-Runtime ready success mapping and adds ReadinessDomain-owned equal-state transition dedup to round-27 source exact `f4d9c401...`; it preserves manual reconnect request success, all loading/error feedback, ClientLease heartbeat/lease/recovery, and underlying Readiness/Runtime state. That exact and all prior source, Reviewer, test, smoke, and QA evidence remain exact-local and transfer no acceptance. +- Route task #228's replacement exactly Coder freeze without gates -> fresh Reviewer logic/Diff/scope/architecture review without gates -> same-exact `pnpm --dir /Users/molvqingtai/Developer/WebChat test` -> Owner. Create no browser or QA task, gate, matrix, or verdict. Existing acceptance controls remain candidate obligations, and historical source, Reviewer, test, smoke, and QA facts transfer no evidence. +- Replace the headless `NetworkDomain` catch-all with the uniquely owned Lifecycle, Connection, Session, World, History, Delivery, and Wire Domains described above. Keep Database-backed origin access, `MessageRecord` models, UI/notification/danmaku projections, and application LWW/order behavior in the application/page Domain/model layer. When ordering helpers are shared by both sides, they live in a shared Domain/model module rather than protocol or a mutable Runtime service. Keep internal page-host RPC contracts at the comctx boundary, and prohibit generic lock controllers, dual reads/writes, aliases, fallbacks, or compatibility routes during the clean-cut owner migration. + +## Capabilities + +### New Capabilities + +- `webrtc-runtime`: Shared Remesh Runtime, browser hosts, comctx surface, domain lifecycle with unified grace, message delivery with ACK buffering, reconnect behavior, and host coordination. +- `peer-wire-protocol`: The v2 wire contract — transport-trusted context, strict closed unions, World presence snapshots, Chat session/immutable events, reaction wire shape, history request/response cursors, public per-frame/resource limits, HLC validation, byte limits, and wire failure semantics. +- `world-room-presence`: Browser-singleton WorldRoom v2 membership inside the Runtime, full-snapshot presence publication, per-domain site sets, and lifecycle alignment with the unified grace rule. + +### Modified Capabilities + +- None. The repository has no archived stable capability specs for these behaviors yet; this change creates the current source of truth. + +## Impact + +- `src/domain/**` (application CQRS Domains, exact `ChatRoomExtern`, state-free Runtime client implementation, and the uniquely owned Lifecycle/Connection/Session/World/History/Delivery/Wire Runtime Domains), `src/app/background`, the Offscreen Document entry (Chrome/Edge), Firefox Background adaptation, `src/app/content/**` (pages remain Query/Command/Event consumers), `src/runtime/**` (internal comctx contracts, private `RoomTransportExtern`, provider implementation/composition), `src/protocol/**` (immutable public peer wire types/schemas/validators/reference codec only), the typed Database extern/default adapters, internal MessageStore, `MessageRecord`/projections/LWW behavior, `src/service/**`, `wxt.config.ts`, manifest/host registration, and architecture/OpenSpec artifacts. Old local data is not migrated. +- Chrome, Edge, and Firefox remain release-blocking acceptance targets. Repository automation provides exact-bound Vitest and Chrome MV3 E2E signals; the Owner's local test environment owns the remaining release-candidate Chrome/Edge and Firefox MV2 product verification. + +## Non-Goals + +- No backward compatibility with the released protocol or old clients; World v1 and Chat v1 are simply abandoned, not bridged. +- No import, migration, or retention of existing local message history or old reaction state in the canonical Database-backed record store; old unstorage remains physically inert, unread, unconverted, and uncleared. +- No history replica inside the Runtime; it is not a history store. +- No page-owned WebRTC fallback path and no dual-architecture intermediate release. +- No cryptographic identity, signatures, message editing, or standalone attachment protocol; image stays as Markdown data URLs within byte limits. +- No reduction of presence privacy below the v2 contract: `host/hostname/href` are never published. +- No agent-run `pnpm dev` is release evidence. Browser runtime verification uses exact-bound production artifacts in automated harnesses or the Owner's local test environment. +- No new product features (notifications, danmaku, options) beyond what the architecture migration requires. +- No local/UI model, projection, ordering implementation, Runtime lifecycle/RPC contract, queue, drop policy, or application orchestration is part of the public `src/protocol/index.ts` surface. +- No change to `ChatMessage`, history, World, room namespaces, limits, codec algorithm, or unrelated canonical bytes; only the explicitly authorized required SESSION `presenceId` and strict SESSION_END lifecycle fact change. +- No new public ChatRoom recovery, ACK, provider, transport, delivery-status, or Runtime method; the eight-method `ChatRoomExtern` remains the only application port. +- No requirement to turn every helper or unique state owner into a `Remesh.module`; pure functions and single-owner Domain logic remain direct when no genuinely isomorphic reuse exists. + +## Acceptance Criteria + +- Structural fail-before tests fail on baseline `9c90bb0...` for the distributed ChatRoom/Network authority, missing private `RoomTransportExtern`/`WireDomain` boundary, and misleading non-Remesh `*Module` names, then pass only after the old owners and paths are removed. The final source has exactly one writable owner for each lease/grace, connection generation, committed session/HLC, World presence, history session/cursor/batch, delivery sequence/buffer, and trusted wire/provider fact. +- Every Runtime Domain documents and implements its State/Query/Command/Event/Effect/Extern surface. Queries perform no I/O or writes; Events state past facts; Effects contain external I/O and feed outcomes back through Domain Commands/Events; application/UI code only consumes Queries, Commands, and Events. Each immutable Session/Chat/History/World peer value terminates in the documented Wire-to-owner mapping, and dependency tests prove an acyclic Domain graph. +- `ChatRoomExtern` remains exactly eight methods. `src/domain/impls/runtime/ChatRoom.ts` contains no command/session/local-session/published-session/join-recovery map or writable network authority; infrastructure-only in-flight call/subscription cleanup cannot be used for snapshot replay or recovery. Inbound remote persistence still settles before ACK, history supply remains page-owned, and `sendMessage` retains its exact causal result/order. +- Artico imports exist only in the provider implementation and explicit composition. A private `RoomTransportExtern` is injected only into `WireDomain`; the same provider contract suite passes for the Artico implementation and a deterministic fake, covering stable peer identity, join/leave, trusted inbound source, targeted send, target-local failure, room-level failure, close, error, and dispose. +- Protected `ChatMessage`, history, World, limits, codec artifacts, and their golden encoded bytes are byte-identical to baseline `9c90bb0...`; only the authorized strict presence facts differ, with no old alias, fallback, or dual route. Database schema, MessageStore's four-method surface, reliability option B, and public application ports remain unchanged; retained invalid rows are isolated and adjacent notice grouping is UI-only. +- The architecture JSON and generated HTML show the application port, stateless client, seven Runtime owners, immutable protocol boundary, private provider Extern, and Artico implementation; generation is reproducible and the diagram agrees with proposal, design, spec, and tasks. +- `lint`, `check`, Chrome and Firefox `build`, and OpenSpec strict validation pass on one exact candidate; the candidate proves only the two scoped `core-js` Uint8Array Base64 imports are actually referenced in Chrome and Firefox bundles, records the bundle-size diff, and contains no unrelated polyfill residue; agents never start or manage `pnpm dev`. +- Exact-bound real-extension verification covers dual-tab sharing, F5 continuity, 5-second release, WorldRoom cross-domain counts, v1/v2 isolation, platform-equivalent host recovery, reconnect isolation, send-first success/failure persistence behavior, and bounded eventual cold/restart rejoin with exact local-plus-remote membership/presence. Chrome MV3 automation uses the production unpacked build; Firefox MV2 uses a test-owned profile and production package. Chrome host recovery directly destroys Offscreen and asserts Service Worker-coordinator rebuild from the active physical page port plus one idempotent Lifecycle attach. Firefox host recovery terminates and restarts the test-owned Firefox process with the same isolated profile and restored target tab, asserts one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection, and may reinstall the same exact temporary XPI only as harness setup if process exit removes it; profile/tab continuity is verified separately and this is never represented as product auto-reinstall. Direct Firefox `backgroundView.close()` is recorded only as the persistent-page platform boundary, not a product failure; `HostOwner` dispose/swap remains deterministic. A readiness helper SHALL accept an already-mounted usable textarea immediately; its bounded eventual timeout prevents a hang and is not a lease-grace or product-latency deadline. The Owner records the final local product verdict and explicitly authorizes merge separately. +- No page-owned WebRTC creation remains; all network state lives in Runtime and pages hold only UI/application state. Application-facing ports have no Runtime imports. ChatRoom uses the exact frozen public protocol structures and eight methods only; `sendMessage` returns its exact allocated `ChatMessage` after transport acceptance and local insert settlement, while exposing no status/result DTO. The port otherwise exposes `ChatSession`/`ChatMessage` by design but no Runtime snapshot, provenance, peer/source, joinedAt, host, page, lease, timer, IDB, retry, or concrete implementation type. UI code outside composition roots does not import `impls` or Runtime clients. +- `src/protocol/index.ts` exposes only the documented public peer contract, and a dependency/export/residue scan proves no local/UI/Runtime/storage/browser-extension/host/app symbol or reverse import crosses that boundary. The scan SHALL allow the standard Web codec APIs used by `NativeWireCodec` (`CompressionStream`, `DecompressionStream`, `Blob`, `ReadableStream`, `TextEncoder`, and `TextDecoder`) plus the two scoped `core-js` imports; it SHALL reject browser-extension APIs/globals (`chrome.*`/`browser.*`), DOM/window/document access, comctx, host lifecycle APIs, and app configuration. HLC validation receives an explicit `now` value and protocol code does not call `Date.now()`. +- World presence is a full per-peer snapshot with per-domain sites; one browser's many pages count as one user per domain. +- Database and MessageStore contract tests prove atomic first-value-wins, same-id replay versus conflict preservation, independent strict outer-type MessageRecord decoding with retained invalid-row isolation and bounded private diagnostics, `DatabaseItem.key === record.id`, both record/payload id equalities, shared cross-variant id uniqueness, default-all/exact-type/abort/invalid-query behavior, removed `list` residue, deterministic scan ordering, successful-commit watch, close, and IndexedDB/Memory parity. History selection/projection remains outside the store and never upserts message bodies; reaction replays have no effect; future-HLC messages beyond the 5-minute skew are rejected without advancing the clock. +- Sending preserves drafts when Runtime/transport is unavailable, writes the local MessageRecord only after complete transport acceptance, returns the exact allocated `ChatMessage` only after MessageStore settlement, and emits exactly one causal local projection without visible-record diff or body matching. It contains no delivery status, durable outbox, crash retry, hidden callback, or fake sent record. The accepted post-handoff/pre-insert loss window is covered explicitly. +- Exact-bound provider tests prove that a Room call may transition `open -> closing` before Room `leave`; one target-local rejection never skips a later sendable target or turns an already partial external Chat/World publication into a local provisional rollback. Initial Chat, initial World, committed World presence, and World recovery all retain this property for untargeted and explicit multi-target sends, while a missing room still rejects. +- Contract tests import only from `@/protocol` for public wire behavior; application/page Domain/model tests cover local records, origin-store behavior, projections, and LWW/order implementation; headless Runtime tests cover network/history orchestration, queue/drop scheduling, and internal RPC contracts from their owning modules. diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/peer-wire-protocol/spec.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/peer-wire-protocol/spec.md new file mode 100644 index 00000000..df53b186 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/peer-wire-protocol/spec.md @@ -0,0 +1,263 @@ +## ADDED Requirements + +### Requirement: Public protocol module is pure and explicitly bounded + +The code-level public module `src/protocol/index.ts` SHALL be the third-party-facing peer contract without introducing a package, publishing flow, or SDK. Its wire structures SHALL be exactly the Owner-frozen `ChatUser`, `ChatSession`, `HLC`, `MentionedUser`, `SessionMessage`, `SessionEndMessage`, `TextMessage`, `ReactionMessage`, `ChatMessage`, `HistoryCursor`, `HistoryRequestMessage`, `HistoryResponseMessage`, `ChatRoomMessage`, `ChatSite`, and `WorldRoomMessage` contracts. It SHALL additionally export only their strict schemas, pure parse/check/validation, public limits/constants, and the public codec surface (`WireCodec`, `NativeWireCodec` reference implementation, `WireCodecError`). It SHALL NOT add or rename any field/type, export a structural alias or compatibility DTO, or expose an optional/open metadata bag without explicit Owner intervention. Pure validation SHALL cover closed-union and unknown-key rejection, field/resource limits, mention `ranges`, user/message size, origin-only and uniqueness rules, history-response reference completeness, and explicit-`now` HLC rules. The `NativeWireCodec` SHALL own only the fixed codec/security algorithm; the public protocol SHALL NOT export local persistence/UI models, projections, ordering implementations, Runtime lifecycle or page-host RPC contracts, WirePipeline queue/drop/apply/flush types, or application orchestration. + +`src/protocol/**` SHALL NOT depend on `domain/runtime`, `service`, `app`, UI, storage, comctx, browser-extension APIs/globals (`chrome.*`/`browser.*`), DOM/window/document, host lifecycle APIs, or app configuration. The public `NativeWireCodec` MAY use the standard Web codec APIs it implements (`CompressionStream`, `DecompressionStream`, `Blob`, `ReadableStream`, `TextEncoder`, and `TextDecoder`) and exactly the two scoped `core-js` imports; no whole-package polyfill is permitted. Protocol-owned limits and pure byte utilities SHALL be defined within the protocol boundary. Runtime and Domain code SHALL depend on the public protocol one way; the protocol SHALL NOT import Runtime or Domain code. + +The normative public structures SHALL be byte-for-byte equivalent to these declarations: + +```ts +interface ChatUser { + id: string + name: string + avatar: string +} +interface ChatSession { + sessionId: string + user: ChatUser +} +interface HLC { + timestamp: number + counter: number +} +interface MentionedUser extends ChatUser { + ranges: [number, number][] +} +interface SessionMessage extends ChatSession { + type: 'session' + presenceId: string +} +interface SessionEndMessage { + type: 'session-end' + presenceId: string +} +interface TextMessage { + type: 'text' + id: string + hlc: HLC + userId: string + body: string + mentions: MentionedUser[] +} +interface ReactionMessage { + type: 'reaction' + id: string + hlc: HLC + targetId: string + userId: string + reaction: 'like' | 'hate' + active: boolean +} +type ChatMessage = TextMessage | ReactionMessage +interface HistoryCursor { + hlc: HLC + id: string +} +interface HistoryRequestMessage { + type: 'history-request' + syncId: string + before?: HistoryCursor +} +interface HistoryResponseMessage { + type: 'history-response' + syncId: string + users: ChatUser[] + messages: ChatMessage[] + done: boolean +} +type ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage +interface ChatSite { + origin: string + title?: string + icon?: string + description?: string +} +interface WorldRoomMessage extends ChatSession { + sites: ChatSite[] +} +``` + +#### Scenario: Public entry has no local or internal exports + +- **WHEN** a consumer imports from `@/protocol` +- **THEN** it SHALL see only the documented peer wire types, schemas, validators, `WireCodec` interface, `NativeWireCodec` reference implementation, `WireCodecError`, and protocol constants; `LocalRecord`, UI models, projections, Runtime RPC, queue/drop types, and internal Runtime symbols SHALL not be reachable + +#### Scenario: Public protocol dependency direction + +- **WHEN** the protocol dependency graph is inspected +- **THEN** every protocol dependency SHALL remain cross-target compatible, and no reverse import from protocol into Runtime, Domain, storage, service, app, UI, comctx, browser-extension APIs/globals, DOM/window/document, host lifecycle APIs, or app configuration SHALL exist; standard Web codec APIs used by `NativeWireCodec` are allowed + +### Requirement: Current v2 peer wire remains immutable during the Runtime architecture replacement + +Except for the Owner-authorized logical-presence exception below, the Remesh DDD + CQRS Runtime replacement SHALL NOT change the current v2 peer message types, field names, strict schemas, parser behavior, room namespaces, limits, codec algorithm, reference codec exports, canonical JSON, or encoded golden bytes from baseline `9c90bb0...`. The exception adds required opaque `presenceId` to `SessionMessage` and the strict `SessionEndMessage`; it changes no `ChatMessage`, history, World, room namespace, limit, or codec algorithm. The internal page-to-Runtime comctx control contract and Runtime Domain CQRS surface MAY change cleanly because neither is peer wire, but those changes SHALL NOT add a peer field, alias, envelope, fallback, compatibility path, or different canonical encoding. Protected-input and golden-byte checks SHALL bind this immutability to the exact baseline files and fixtures. + +#### Scenario: Internal CQRS refactor preserves peer bytes + +- **WHEN** the same canonical Chat and World values are encoded before and after the Runtime architecture replacement +- **THEN** their schemas, validation result, canonical JSON, room selection, and final encoded bytes SHALL be identical to baseline `9c90bb0...` + +#### Scenario: comctx is not peer protocol + +- **WHEN** internal page-host Commands, Events, snapshots, or subscription contracts change to express the new Domain graph +- **THEN** no corresponding peer-wire field, message type, schema branch, codec change, alias, or version bridge SHALL be introduced + +### Requirement: Transport delivers trusted context + +`WireDomain` SHALL receive inbound data only from the Runtime-private `RoomTransportExtern`, which delivers `(roomId, sourcePeerId, rawPayload)`. It SHALL compose the public `WireCodec` contract and validators; production/default wiring MAY use the `NativeWireCodec` reference implementation. Fatal UTF-8 decoding and bounded `readLimited` belong to that public codec implementation. `WireDomain` SHALL bind routing and session decisions to the provider-supplied `roomId` and `sourcePeerId`, and SHALL NOT trust any peer identity, room, or sender field self-reported inside a payload. It SHALL emit only strict, typed Events to the Runtime; raw frames and unvalidated decoded values SHALL not cross the Domain boundary. The public protocol module itself SHALL contain no transport routing, queue orchestration, provider type, or Runtime Extern. + +#### Scenario: Payload self-reports identity + +- **WHEN** a payload contains a peerId, room, or sender field, whether conflicting with or matching the transport context +- **THEN** the adapter SHALL reject the entire source-local frame as a forbidden envelope instead of stripping or ignoring the claim; accepted messages use the transport-provided `roomId` and `sourcePeerId` + +#### Scenario: Fixed codec representation + +- **WHEN** wire payloads are encoded or decoded +- **THEN** the public `NativeWireCodec` reference implementation SHALL provide `base64(deflate(UTF8(JSON)))`, native `CompressionStream`/`DecompressionStream`, fatal UTF-8 decoding, bounded `readLimited`, and a direct runtime dependency on exactly these scoped `core-js` global-patch imports: `core-js/actual/typed-array/from-base64` and `core-js/actual/typed-array/to-base64`. Business code SHALL call standard `Uint8Array.fromBase64(value, {alphabet:'base64', lastChunkHandling:'strict'})`, re-encode with `bytes.toBase64()`, and accept only exact equality with the input; no whole-package entry, pure export, JSONR special-value layer, or interop adapter SHALL exist + +### Requirement: WireDomain composes the public codec and typed message boundary + +The public protocol SHALL export `WireCodec` as the codec interface, `NativeWireCodec` as the reference implementation, and `WireCodecError` for codec failures. `WireDomain` SHALL expose a typed send Command whose value is logically `{roomId, targetPeerIds?, message}` and typed accepted-message Events whose value is logically `{roomId, sourcePeerId, message}`; exact local symbol names MAY follow the concise Runtime directory context. It SHALL compose the public `WireCodec` contract and validators; production/default wiring MAY use the public `NativeWireCodec` reference implementation. It SHALL enforce operational queue/drop/apply/flush policies, select parsers by trusted `roomId`, perform strict schema parsing, and bind source context. Queue/drop types SHALL remain private Wire State; concurrency/resource scheduling belongs to its named Runtime owner and SHALL NOT become a public protocol export. Other Runtime Domains SHALL receive typed Wire Events only and SHALL NOT receive `decoded: unknown`, encoded strings, provider callbacks, or Artico values. `targetPeerIds` SHALL remain optional in the logical send value; omission SHALL mean room broadcast. `sourcePeerId` SHALL always be the single transport-confirmed inbound source. + +#### Scenario: Typed inbound boundary + +- **WHEN** an inbound frame is accepted +- **THEN** `WireDomain` SHALL emit a typed message fact together with `{roomId, sourcePeerId}` and SHALL not expose an unvalidated decoded value or encoded frame to another Domain + +#### Scenario: Symmetric CQRS boundary + +- **WHEN** Runtime code sends or receives a wire message +- **THEN** it SHALL use the paired typed send Command and accepted-message Event values with `targetPeerIds`/`sourcePeerId` semantics; provider lifecycle callbacks SHALL enter as independent Wire Commands/Events rather than a public imperative adapter + +### Requirement: Wire messages are strict closed unions with limits + +The public protocol SHALL define the closed schemas, pure limits, and malformed-input validation. `WireDomain` SHALL call those validators and may apply source-local operational policies, but queue/drop/apply/flush scheduling, rate-limited logging, reconnect behavior, and delivery admission are not public protocol semantics. + +Chat wire messages SHALL form a strict, closed discriminated union keyed by `type`; World wire payloads SHALL use one strict schema selected by trusted v2 `roomId` and SHALL NOT carry a payload `type`. The public protocol SHALL export and enforce these fixed limits: `MAX_WIRE_BYTES = 64KiB` for final encoded frames, `MAX_DECODED_JSON_BYTES = 256KiB` for streaming decompressed JSON before parse, `MAX_CHAT_EVENT_BYTES = 48KiB` for one canonical message, `MAX_USER_BYTES = 8KiB` for one `ChatUser` JSON value, and at most 100 messages in one history response. Every string, array, nesting depth, and final encoded byte size SHALL have an explicit public limit. Unknown types, unknown keys, forbidden envelope/context fields, non-canonical or malformed Base64, limit violations, and malformed payloads SHALL produce a public validation failure for the complete frame; the Runtime decides how to drop or log that failure. + +#### Scenario: Unknown or oversized message + +- **WHEN** the public validator receives an unknown type or a message exceeding `MAX_WIRE_BYTES = 64KiB` +- **THEN** it SHALL reject the complete message before any Runtime application, without specifying queue, retry, reconnect, or logging behavior + +#### Scenario: Decompression and field resource limits + +- **WHEN** decompression would produce more than `MAX_DECODED_JSON_BYTES = 256KiB`, one `ChatUser` JSON value exceeds `MAX_USER_BYTES = 8KiB`, or one canonical `ChatMessage` exceeds `MAX_CHAT_EVENT_BYTES = 48KiB` +- **THEN** the public codec/validator SHALL reject the complete frame before application, without prescribing Runtime queue, retry, reconnect, or logging behavior + +#### Scenario: Redundant envelope fields + +- **WHEN** any wire type is defined +- **THEN** its strict schema SHALL reject a frame carrying room, sender peerId, version, sentAt, receivedAt, or any other forbidden envelope/context field; `receivedAt` exists only as receiver-local metadata and no field SHALL be stripped or tolerated + +### Requirement: HLC is strictly validated + +Hybrid Logical Clock values on wire `ChatMessage` values SHALL be finite non-negative safe integers for both timestamp and counter. The public validator SHALL receive the receiver's current time as an explicit `now` argument; it SHALL NOT call `Date.now()` or any hidden clock. An event whose HLC timestamp exceeds `now` by more than 5 minutes SHALL produce a validation failure. The protocol SHALL define the canonical total-ordering, cursor, and last-writer-wins rule as composite `(hlc, id)`; the comparison implementation belongs to the application/page Domain/model layer, or a shared Domain/model module when both pages and Runtime consume it. + +#### Scenario: Future-poisoned event + +- **WHEN** the public validator receives an event with an HLC timestamp more than 5 minutes in the future and an explicit `now` +- **THEN** it SHALL return a validation failure without calling a hidden clock or mutating the caller's clock state + +#### Scenario: Same-clock different messages + +- **WHEN** two messages share an HLC timestamp and counter but have different ids +- **THEN** they SHALL be ordered and deduplicated by `(hlc, id)`, never by HLC alone + +### Requirement: World presence is a full peer snapshot + +World presence wire data SHALL be exactly `WorldRoomMessage extends ChatSession {sites: ChatSite[]}`, where `ChatSession = {sessionId, user: ChatUser}` and `ChatSite = {origin, title?, icon?, description?}`. It SHALL have no payload `type`. The trusted World `roomId` SHALL select this strict parser. World and Chat SHALL use the same structures while maintaining separate session instances and room protocols. The public schema SHALL require a complete `sites` array and SHALL reject unknown or forbidden fields. Runtime registry aggregation, snapshot publication, source replacement, peer leave handling, and per-domain counting are specified by the `world-room-presence` capability. `host`, `hostname`, and `href` SHALL NOT appear on the wire. + +#### Scenario: World snapshot wire shape + +- **WHEN** a peer publishes World presence +- **THEN** the payload SHALL contain exactly `sessionId`, `user`, and `sites`, with each site limited to `origin` and optional `title`, `icon`, and `description`; the payload SHALL not contain a discriminator or page URL fields + +### Requirement: Chat wire uses immutable typed messages + +Chat wire SHALL be exactly `ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage`, where `ChatMessage = TextMessage | ReactionMessage`, `SessionMessage extends ChatSession {type:'session', presenceId:string}`, `SessionEndMessage = {type:'session-end', presenceId:string}`, and `ChatSession = {sessionId, user:ChatUser}`. `ChatUser` SHALL be exactly `{id,name,avatar}`. `MentionedUser extends ChatUser` and SHALL add exactly `ranges: [number, number][]`. Each pair SHALL be an inclusive `[start,end]` range in JavaScript string/UTF-16 code-unit indices with non-negative integers and `start <= end < body.length`. Text and reaction messages SHALL be immutable once created, and live fields SHALL use `userId`; Runtime session binding and application are specified by `webrtc-runtime`. + +#### Scenario: Chat union and text shape + +- **WHEN** a peer sends a Chat message +- **THEN** the strict wire union SHALL accept only the exact frozen fields; each mention SHALL contain exactly `id`, `name`, `avatar`, and `ranges`, each range SHALL be one inclusive valid `[start,end]` pair, and the canonical text message SHALL remain within `MAX_CHAT_EVENT_BYTES = 48KiB` + +#### Scenario: Reaction is explicit state + +- **WHEN** a peer sends a reaction message +- **THEN** it SHALL carry `userId` and `active: boolean` plus the documented target/reaction/HLC/id fields + +### Requirement: Chat presence uses causal generation and final-end facts + +Every `SessionMessage` SHALL carry a required opaque `presenceId` identifying one logical online generation independently of physical `sessionId` and transport `sourcePeerId`. A reconnect, refresh, recovery, reattach, duplicate publication, additional physical session, or supported Runtime host replacement SHALL reuse the active generation. Only an initial join or a return after the prior generation ended SHALL allocate a new generation. `SessionEndMessage` SHALL carry exactly `type:'session-end'` and that generation's `presenceId`; its strict schema SHALL reject missing or unknown fields. No old SESSION decoder, optional-field fallback, alias, dual schema, or compatibility bridge SHALL exist. + +A graceful final release SHALL first durably replace the private active lease with the same generation's unsettled final-end identity, then publish the end fact on the source-ordered Wire lane. Retirement persistence rejection SHALL send no end and preserve the active generation plus physical membership. The durable identity SHALL remain present throughout every unsettled first or retry send. Explicit end-send rejection SHALL durably mark that generation retryable; a same-host retry SHALL durably mark the same identity in flight again before resending the idempotent end. A same-user replacement that loads either unsettled marker SHALL continue that exact END transaction with the same `presenceId`; it SHALL NOT expose the generation as a successful active join or let it carry live messages. Successful send settlement SHALL durably replace the unsettled marker with private settled-cleanup ownership before removing the marker. A replacement that loads settled-cleanup ownership SHALL only retry marker removal and SHALL publish neither SESSION nor SESSION_END. Only successful marker removal may physically leave the Chat room. A failed transition or cleanup SHALL retain a safe durable identity and physical membership still owned by that host. Complete cleanup SHALL leave no persistent retry marker. Receivers SHALL apply duplicate SESSION and SESSION_END facts idempotently, reject SESSION after its accepted end, and classify logical joins/leaves from generation/end facts rather than wall clocks, debounce, transport loss, `sourcePeerId`, or physical `sessionId` conventions. `ChatMessage`, history, and World shapes remain unchanged. + +#### Scenario: Transport recovery reuses a generation + +- **WHEN** one logical user loses and restores a physical transport, refreshes, reattaches, or replaces a supported Runtime host without a final generation end +- **THEN** every replacement SESSION SHALL carry the same `presenceId`, and receivers SHALL not classify a new logical join or leave + +#### Scenario: Final end is ordered and idempotent + +- **WHEN** the last local owner gracefully releases a generation and each external stage succeeds +- **THEN** it SHALL durably retire the private active lease while retaining the generation identity, settle exactly the strict `SessionEndMessage`, durably record settled-cleanup ownership, remove that marker, then physically leave the room, and receivers SHALL classify at most one final logical leave even if the end fact is duplicated + +#### Scenario: Final end is fenced by an external rejection + +- **WHEN** private retirement persistence, an END-state transition, SESSION_END send, or post-settlement cleanup rejects +- **THEN** the peer SHALL NOT physically leave or publish a false local lifecycle end; retirement rejection SHALL retain the same active lease, while every later rejection SHALL retain a recoverable final-end identity for the already-retired generation + +#### Scenario: Unsettled end survives host replacement + +- **GIVEN** durable retirement succeeded and the first or retry SESSION_END is unsettled or explicitly rejected +- **WHEN** the Runtime host is replaced before END settlement +- **THEN** the replacement SHALL physically rebind the retained `presenceId` only to continue the same idempotent END transaction, SHALL expose no successful active join or live-message authority, and SHALL produce at most one observer leave while clearing every private final-end marker + +#### Scenario: Settled cleanup survives host replacement + +- **GIVEN** receivers accepted SESSION_END and the durable record contains settled-cleanup ownership because marker removal is incomplete +- **WHEN** the Runtime host is replaced +- **THEN** the replacement SHALL remove that marker without joining Chat or World, publishing SESSION or SESSION_END, reviving the ended generation, or changing the observer's exactly-once leave + +#### Scenario: Later return uses a fresh generation + +- **WHEN** the same user returns after its accepted final generation end +- **THEN** its SESSION SHALL use a different `presenceId` and receivers SHALL classify one fresh logical join + +### Requirement: History wire shapes are bounded and reference-complete + +The public peer protocol SHALL define only this exact history wire contract: `HistoryRequestMessage = {type:'history-request', syncId, before?: {hlc,id}}` and `HistoryResponseMessage = {type:'history-response', syncId, users:ChatUser[], messages:ChatMessage[], done}`. One `syncId` SHALL identify one complete paginated synchronization and remain stable while `before` advances. `before/messages/done` SHALL express cursor and completion semantics; the public protocol SHALL not prescribe local candidate windows, provider selection, timeout ownership, page cancellation, store effects, or Runtime queue scheduling. Each response SHALL carry at most 100 messages and remain strictly below `MAX_WIRE_BYTES = 64KiB` final encoded bytes. Every `message.userId` in a response SHALL resolve to exactly one matching `users[].id`; duplicate user ids SHALL be rejected, while additional users MAY be present. + +The current serialized `requestId` and history-response `events` keys SHALL be replaced by `syncId` and `messages` in one intentional breaking clean cutover. The existing mention `positions` key SHALL likewise be replaced by `ranges`. New schemas SHALL accept only `syncId`, `messages`, and `ranges`; any old key, old/new pair, alias, or unknown key SHALL reject the entire frame before projection or store writes. No dual-read, fallback, migration, or compatibility alias SHALL exist. Mixed-development-version peers therefore cannot exchange history request/response frames; Chat session/text/reaction and World parsing remain independently determined by their own strict schemas. The fixed `NativeWireCodec` envelope algorithm does not change, but canonical history JSON and encoded bytes do. + +#### Scenario: Pull pagination + +- **WHEN** two current peers exchange history +- **THEN** a peer MAY issue `history-request {syncId, before?}`, receive a flat byte-bounded `history-response`, and issue the next request using the same `syncId` plus the previous response's oldest `(hlc, id)` cursor; `done` SHALL state whether the provider has no more candidates for that sync + +#### Scenario: Complete history references + +- **WHEN** a `history-response` contains messages and users +- **THEN** every message `userId` SHALL have exactly one matching user entry, duplicate user ids SHALL cause the response to be rejected as a whole, and additional users SHALL remain accepted + +#### Scenario: History response wire limits + +- **WHEN** a `history-response` exceeds 100 messages or is greater than or equal to `MAX_WIRE_BYTES = 64KiB` after canonical encoding +- **THEN** the public codec/validator SHALL reject the response before Runtime application, without prescribing any retry, supplier, timeout, or queue behavior + +#### Scenario: Old and ambiguous history keys reject + +- **WHEN** a history frame contains `requestId`, contains both `requestId` and `syncId`, uses `events`, contains both `events` and `messages`, omits a required new key, or contains any compatibility alias; or a text mention uses `positions` or both mention keys +- **THEN** the strict schema SHALL reject the complete frame, the Runtime SHALL project and persist nothing from it, and no fallback SHALL run + +### Requirement: No old-protocol compatibility + +The peer protocol SHALL NOT bridge, translate, or interoperate with the released v1 wire protocol. v1 and v2 SHALL use isolated room namespaces so neither parses the other's wire traffic. Local-data migration and old-record handling are application/page Domain concerns defined by the `webrtc-runtime` capability. Unmerged development residues MAY be cleaned up directly. + +#### Scenario: v1/v2 cross-traffic + +- **WHEN** a v1 client and a v2 client meet in a shared physical environment +- **THEN** they SHALL use isolated room namespaces such that neither parses the other's protocol, and no compatibility fallback SHALL exist diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/webrtc-runtime/spec.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/webrtc-runtime/spec.md new file mode 100644 index 00000000..6ff7fd31 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/webrtc-runtime/spec.md @@ -0,0 +1,1147 @@ +## ADDED Requirements + +### Requirement: Shared Remesh Runtime owns all network state + +The headless Runtime SHALL consume the immutable public peer contract from `src/protocol/index.ts` through a one-way dependency. The application/page Domain/model layer SHALL own origin-store access, retained user input, local/UI read models, projections, LWW/order behavior, and record helpers. The uniquely owned Runtime Domains SHALL own network/history facts, Wire queue/drop scheduling, and internal page-host control contracts. None of these application or internal Runtime symbols SHALL be exported by the public protocol module. + +The system SHALL move all WebRTC peer connections, sessions, presence, and message routing into one shared Remesh DDD + CQRS Runtime per browser. Pages SHALL read Queries, issue Commands, and subscribe to Events only. They MAY retain join input and display projections, but SHALL NOT create, hold, or operate `RTCPeerConnection`, peer routing state, or a writable copy of Runtime network facts. Page-owned WebRTC SHALL be removed with no fallback path. + +#### Scenario: Page contains no network ownership + +- **WHEN** any content page is inspected or executed +- **THEN** it SHALL NOT construct or retain peer connections, sessions, or routing state, and SHALL consume the Runtime only through the internal comctx surface + +#### Scenario: No old-architecture fallback + +- **WHEN** Runtime host creation fails on a supported browser +- **THEN** the system SHALL present an explicit unavailable/retry state and SHALL NOT silently restore page-owned WebRTC + +### Requirement: Browser hosts adapt only lifecycle + +The system SHALL provide one shared Remesh Runtime core consumed by browser-specific hosts: Chrome and Edge SHALL use an Offscreen Document host and Firefox SHALL use an equivalent long-lived persistent Background Page host. Hosts SHALL only create, destroy, and inject browser capabilities into the Runtime and SHALL NOT duplicate ChatRoom or WorldRoom business logic. + +#### Scenario: Shared core across browsers + +- **WHEN** the Chrome/Edge and Firefox builds are compared +- **THEN** the Runtime business logic SHALL be the same shared Remesh implementation and host files SHALL contain only lifecycle adaptation + +#### Scenario: Host lifecycle adaptation + +- **WHEN** a browser destroys and recreates its host environment +- **THEN** the host SHALL re-instantiate the shared Runtime core without changing domain business semantics + +### Requirement: Background is the sole host coordinator + +The extension background SHALL be the only coordinator allowed to create or rebuild the Runtime host. Creation SHALL be single-flight: concurrent page requests SHALL wait for the same ready result. While at least one physical page port is online, a missing or destroyed Runtime within the live coordinator's supported host context SHALL be recreated automatically without user action. For Chrome/Edge, the Service Worker coordinator SHALL retain physical page-port liveness and host-phase observations outside the Offscreen host, so Offscreen destruction is recoverable; after host creation it SHALL replay idempotent attach Commands into `LifecycleDomain`. `LifecycleDomain` SHALL remain the unique owner of domain leases, ref-count, grace, and release State; the coordinator SHALL NOT keep a parallel domain lease/grace map. For Firefox, the coordinator and Runtime host SHALL share the persistent Background Page; supported recovery SHALL be limited to in-context `HostOwner` replacement, while a browser process restart SHALL be recovered when Firefox recreates the Background Page and a restored page idempotently reattaches. Direct `backgroundView.close()` SHALL be outside the supported recoverable lifecycle and SHALL NOT require an event page, reload watchdog, or business fallback. A page watchdog MAY supplement Chrome/Edge probing but SHALL NOT be the only controller. The background coordinator itself MAY be suspended or restarted by the browser; after such a supported restart it SHALL recover physical port/host observations from active ports or idempotent page reattach, reconstruct one host, and dispatch the resulting attach Commands without producing duplicate Lifecycle leases, hosts, or physical rooms. + +#### Scenario: Concurrent creation requests + +- **WHEN** multiple pages detect a missing Runtime at the same time +- **THEN** exactly one host creation SHALL proceed and all pages SHALL attach to its single ready result + +#### Scenario: Automatic rebuild in a supported host context + +- **WHEN** Chrome/Edge destroys its Offscreen host, or Firefox replaces an in-context Runtime provider, while a domain page remains online +- **THEN** the coordinator SHALL rebuild the supported host context and re-establish that domain's connections and the WorldRoom without user action + +#### Scenario: Stale Offscreen document + +- **WHEN** the Offscreen document still exists but the Runtime provider or its identity probe does not respond while a physical page port remains online +- **THEN** the background health sweep SHALL close and recreate the stale document, verify the replacement provider, and replay an idempotent attach Command into the replacement Lifecycle Domain without requiring a page watchdog or retaining a parallel lease map + +#### Scenario: DOM-free MV3 health probe + +- **WHEN** the Chrome/Edge background service worker probes a newly created or steady-state Offscreen Runtime +- **THEN** the injector SHALL operate without `window`, `document`, or content-page location metadata and SHALL validate the responding provider identity + +#### Scenario: Single Firefox replacement owner + +- **WHEN** the Firefox persistent Background Page replaces its in-context Runtime +- **THEN** it SHALL dispose the old comctx provider listener, Remesh store, room transport, and Artico peer before exposing one replacement, leaving exactly one provider and physical Runtime + +#### Scenario: Chrome MV3 Offscreen destruction recovery + +- **WHEN** the production Chrome MV3 Offscreen document is directly destroyed while the Service Worker coordinator retains an active physical page port +- **THEN** the coordinator SHALL automatically recreate the Offscreen host, re-instantiate the shared Runtime, replay one idempotent Lifecycle attach, and restore Runtime readiness and room participation without page-owned fallback or duplicate domain lease authority + +#### Scenario: Firefox MV2 process restart recovery + +- **WHEN** the test-owned Firefox process is terminated and restarted with the same isolated profile and the target tab is restored, with the harness reinstalling the same exact temporary XPI only as setup if process exit removed it +- **THEN** the test SHALL observe one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection; profile and tab continuity SHALL be asserted separately, and the harness SHALL NOT claim product auto-reinstallation + +#### Scenario: Firefox persistent-page boundary + +- **WHEN** a diagnostic harness directly closes the Firefox persistent Background Page through `backgroundView.close()` +- **THEN** the result SHALL be recorded as negative evidence of the platform's non-recoverable persistent-page boundary rather than a product failure, and SHALL NOT motivate an event page, reload watchdog, or business fallback + +#### Scenario: Deterministic Firefox HostOwner swap + +- **WHEN** the Firefox host is disposed and replaced during lifecycle recovery +- **THEN** deterministic `HostOwner` dispose/swap tests SHALL prove that the old provider, store, rooms, and peer are fully disposed before exactly one replacement is exposed + +#### Scenario: Observable steady-state host loss + +- **WHEN** the coordinator detects provider loss or replacement failure after startup +- **THEN** Lifecycle-backed Runtime snapshots exposed to pages SHALL report the resulting `connecting` or `unavailable` phase instead of a hard-coded `ready` value + +#### Scenario: Coordinator restart + +- **WHEN** the background coordinator itself is suspended or restarted by the browser +- **THEN** it SHALL recover physical page-port and host-phase observations from active ports or idempotent page reattach, reconstruct one host, and dispatch idempotent Lifecycle attach Commands without producing duplicate domain leases, hosts, or physical rooms + +### Requirement: Background service RPC routing is service-specific + +Notification and AppAction SHALL use mutually exclusive versioned comctx namespaces. Each service's background provider and content injector SHALL obtain its namespace from the same internal proxy contract combined with the current extension runtime id. A heartbeat SHALL therefore admit only the requested service provider. One Notification or AppAction request SHALL produce one matching provider response, execute its intended side effect exactly once, and return request listeners to the live-provider baseline. + +A rejected Notification `push` Promise SHALL be consumed by that request and produce one local observable diagnostic. It SHALL NOT surface as an unhandled Promise or global page error, terminate later notification requests, or remove or duplicate the chat list, barrage, input, OS notification, or AppAction behavior. The existing Notification request Event and all successful behavior SHALL remain unchanged. This isolation SHALL NOT add a public method, retry, fallback, product copy, shared failure authority, comctx fork, Runtime/PresenceStore change, or peer-wire change. + +#### Scenario: Notification reaches only its provider + +- **GIVEN** Notification and AppAction providers are both live on the extension runtime bus with heartbeat enabled +- **WHEN** the content injector calls Notification `push` +- **THEN** exactly one Notification provider SHALL answer and execute `push` once, AppAction SHALL execute nothing, the request SHALL receive one response, and request listeners SHALL return to baseline + +#### Scenario: AppAction reaches only its provider + +- **GIVEN** Notification and AppAction providers are both live on the extension runtime bus with heartbeat enabled +- **WHEN** the content injector calls AppAction `openOptionsPage` +- **THEN** exactly one AppAction provider SHALL answer and open Options once, Notification SHALL execute nothing, the request SHALL receive one response, and request listeners SHALL return to baseline + +#### Scenario: Notification rejection is request-local + +- **GIVEN** a Notification request rejects internally +- **WHEN** `NotificationDomain` issues that request and then issues a later healthy request +- **THEN** the first request SHALL emit one local diagnostic with no unhandled page error, both existing notification request Events SHALL occur, and the later request SHALL execute normally without harming the chat list, barrage, or input + +### Requirement: Domain connection sharing and isolation + +For each domain, `ConnectionDomain` SHALL maintain at most one connection to the same remote peer, shared by every local page of that domain. Connection, Session, World, History, and Delivery owners SHALL keep different domains isolated from one another. + +#### Scenario: Two tabs on one domain + +- **WHEN** two pages of the same domain are online +- **THEN** the Runtime SHALL hold exactly one connection to each remote peer of that domain, not one per page + +#### Scenario: Cross-domain isolation + +- **WHEN** pages of two domains are online +- **THEN** neither domain's connections, sessions, presence, buffered events, nor history-provider cleanup SHALL be visible to or affect the other domain + +### Requirement: Physical room acceptance commits a domain join + +A Runtime logical room registration or desired-room request SHALL NOT by itself constitute physical room readiness or successful domain join. `ConnectionDomain` SHALL keep every initial domain join provisional until the required physical Chat and World rooms accept the initial Chat `session` and World presence snapshot through `WireDomain`. It SHALL then command `SessionDomain` and `WorldDomain` to commit the domain's local-session and local-World facts and complete the join operation exactly once. + +For a transient provider/room-not-ready result, the Runtime SHALL use bounded Runtime-owned readiness waiting or automatic retry, or atomically roll back the provisional join before surfacing a retryable failure. It SHALL NOT leave a terminal state that can receive remote Chat sessions or World presence while the local page has no local-session/presence projection. Cold existing-user startup, normal startup, and supported host/process recovery SHALL automatically converge to exact local plus remote Chat membership and World presence without a page reload or manual reconnect. Pages SHALL NOT implement a fallback retry queue or browser-specific business logic. + +Each provisional attempt SHALL be fenced by the active host and domain lifecycle generation. Last-page release, explicit leave/reconnect, host replacement, and late provider/room-open callbacks SHALL cancel or supersede the prior attempt; a stale completion SHALL not alter state or emit a projection. One logical join SHALL create at most one physical room for each required room id and SHALL emit at most one initial local session and one local World presence. + +Peer-ready and inbound session events received during a provisional window SHALL belong to the active attempt rather than the retained committed runtime. A peer that becomes ready after an accepted initial Chat or World broadcast but before commit, and therefore could not receive that broadcast, SHALL receive exactly one targeted current local Chat session or World presence after commit for each missed room. The Runtime SHALL send no catch-up for a rolled-back or superseded attempt and no duplicate to a peer already covered by the accepted initial broadcast. A remote Chat session received during a provisional reconnect SHALL remain invisible to pages until commit and SHALL then appear exactly once in the replacement committed snapshot; it SHALL NOT leak through the previous committed runtime or be overwritten by an empty attempt snapshot. + +A bounded room-recovery timeout SHALL retain ownership of the underlying asynchronous join. A timeout surfaced as terminal for an attempt SHALL invalidate and leave or otherwise fence that attempt before a late completion can register trusted membership. An automatically retained retry SHALL remain provisional until its current presence/session is accepted. In either case, late physical readiness SHALL NOT leave a persistent physical or trusted World room, or an observable joined snapshot, without an accepted current World presence; a later recovery attempt SHALL publish the full current snapshot before commit. + +#### Scenario: Delayed physical room on cold existing-user join + +- **GIVEN** an existing page identity and an injected transport whose logical join records desired rooms while physical Chat and World rooms remain unavailable +- **WHEN** the page automatically joins and a remote peer later becomes available before the bounded Runtime retry/window ends +- **THEN** no failed provisional attempt may remain remote-receivable without local projection, the Runtime SHALL eventually emit exactly one local session and one local World presence, the page SHALL show the exact local-plus-remote memberships, and each required physical room/session/presence SHALL occur once + +#### Scenario: Cancelled or superseded delayed join + +- **GIVEN** a domain join is waiting for physical room acceptance +- **WHEN** its page lease releases, the host is replaced, the domain reconnects, or an earlier provider opens after a newer generation begins +- **THEN** the stale attempt SHALL not send a session/presence or mutate the current domain, and the current generation alone may converge + +#### Scenario: Peer becomes ready during provisional commit + +- **GIVEN** the initial Chat and World broadcasts are accepted or pending while the domain join remains provisional +- **WHEN** a peer becomes ready too late to receive either accepted initial broadcast and the same attempt later commits +- **THEN** the Runtime SHALL send that peer exactly one targeted current Chat session and one targeted current World presence for the missed rooms after commit, without duplicating an initial delivery or sending from a failed or stale attempt + +#### Scenario: Remote session arrives during provisional reconnect + +- **GIVEN** a committed domain is retained while a replacement Chat room reconnect remains provisional +- **WHEN** the replacement room receives a remote session before its initial local session send settles +- **THEN** the remote session SHALL remain attempt-owned and invisible to pages before commit, then appear exactly once in the committed replacement snapshot without entering the prior runtime or being overwritten + +#### Scenario: World recovery times out before physical readiness + +- **GIVEN** a World recovery join remains physically pending beyond its bounded timeout +- **WHEN** the timeout settles and the underlying provider reports readiness later +- **THEN** the timed-out completion SHALL be fenced from trusted membership, or an owned automatic retry SHALL remain provisional until the current full World presence is accepted; no settled state SHALL expose joined World membership without that publication + +### Requirement: Physical sends isolate per-target readiness transitions + +Artico Room readiness events SHALL be treated as advisory for recipient selection rather than as an atomic guarantee that every remembered call remains sendable. An untargeted send SHALL snapshot the currently remembered peer ids. An explicit one-or-many-target send SHALL use the supplied peer ids. The transport SHALL de-duplicate either set in deterministic order and attempt each distinct target independently. + +A target-local provider rejection after that call has become non-sendable but before Room `leave` SHALL be contained to that target. It SHALL NOT prevent a later target from being attempted exactly once, reject the whole room send, or cause a provisional domain join or World recovery to roll back after any other target has already accepted the fact. The operation SHALL settle after all target attempts, including an empty or no-longer-sendable target set. A missing physical room, an untrusted/stale room selection, or a codec/validation failure before provider target attempts SHALL remain an operation-level rejection. + +This per-target settlement is best-effort physical provider acceptance, not remote delivery acknowledgement. It SHALL add no retry, durable outbox, delivery status, fallback, callback channel, wire field, public ChatRoom method, or persistence behavior. A target that later becomes ready again participates only through the existing provider-ready/session/presence convergence paths. + +#### Scenario: Untargeted send continues after a ready call starts closing + +- **GIVEN** an untargeted Chat or World send snapshots two remembered-ready peers in order, the first call enters `closing` before Room `leave`, and the second call remains sendable +- **WHEN** the provider rejects the first target synchronously +- **THEN** the first target SHALL receive nothing from that attempt, the second target SHALL receive the fact exactly once, and the room send SHALL settle without a room-wide rejection + +#### Scenario: Explicit multi-target catch-up isolates a stale target + +- **GIVEN** a post-commit Chat-session or World-presence catch-up explicitly names multiple distinct peers and one named call becomes non-sendable before its delayed Room `leave` +- **WHEN** the transport attempts the explicit targets +- **THEN** that target-local miss SHALL NOT skip or duplicate any later sendable target, and every later sendable target SHALL receive its catch-up exactly once + +#### Scenario: Partial provisional publication is not rolled back by a later target miss + +- **GIVEN** an initial Chat session, initial World snapshot, or World recovery presence has been accepted by one provider target +- **WHEN** a later target in the same physical send rejects because its call crossed the ready-to-closing window +- **THEN** the target-local miss SHALL be contained, the physical send SHALL finish the remaining targets, and the Runtime SHALL NOT roll back the provisional attempt solely because of that miss or replay the already accepted target + +#### Scenario: Room-level failure remains an operation failure + +- **GIVEN** no current physical/trusted room exists, or wire validation/encoding rejects before any provider target attempt +- **WHEN** Chat or World tries to send +- **THEN** the operation SHALL reject through the existing generation-owned failure path, send to no target, and gain no retry, outbox, status, fallback, or local persistence success + +### Requirement: Unified five-second lifecycle grace + +When the last page of a domain disconnects, `LifecycleDomain` SHALL uniquely own one unified five-second grace phase/deadline. During it, Connection SHALL retain that domain's ChatRoom connection, Session/History SHALL retain domain State, Delivery SHALL retain the volatile inbound un-ACK buffer, and World SHALL retain domain presence. On grace expiry, the Lifecycle domain-released Event SHALL begin a fenced final release: Session SHALL persist the retired private presence record with an unsettled final-end identity before publishing SESSION_END, retain that identity until the send settles, durably replace it with settled-cleanup ownership, and then remove that marker. Session's authoritative finalization state SHALL reject text/reaction allocation and live send from pending retirement through physical release. Connection SHALL physically leave Chat or the last World room only after marker removal succeeds. A page that reconnects within the grace period SHALL cancel grace through Lifecycle and read the current Runtime snapshot without a false offline/online transition. No persistent outbound outbox or delivery-status retry survives a successfully completed grace release; only the separately specified volatile inbound un-ACK buffer participates in this lifecycle. + +#### Scenario: Refresh within grace + +- **WHEN** a user refreshes the only page of a domain and the new page attaches within 5 seconds +- **THEN** the domain connection and state SHALL continue without re-join flapping, presence flicker, or message loss caused by the refresh + +#### Scenario: Application reconnect preserves the logical generation + +- **GIVEN** the application Reconnect Effect retains the frozen `leaveRoom()` then `joinRoom(command)` composition +- **WHEN** the Runtime ChatRoom implementation executes that composition for an active domain +- **THEN** `leaveRoom()` SHALL invoke current-domain Runtime reconnect rather than final logical release, the replacement physical Chat session SHALL reuse the same `presenceId`, World SHALL remain physically joined, and local plus observer views SHALL receive snapshots without SESSION_END, logical join/leave, or another notice + +#### Scenario: Durable retirement rejects + +- **GIVEN** a committed active presence generation and a PresenceStore that rejects the retired record +- **WHEN** final release begins +- **THEN** the same active durable and in-memory lease, Chat/World physical membership, History state, World desired presence, and joined Runtime snapshot SHALL remain; no SESSION_END, observer leave, or physical departure SHALL occur; the pending release fence SHALL be removed so allocation and live send remain usable; and the existing Runtime error path SHALL surface a retryable request-local failure + +#### Scenario: Retirement succeeds after storage recovery + +- **GIVEN** a prior retirement attempt was fenced by storage rejection and the PresenceStore later recovers +- **WHEN** final release is requested again +- **THEN** the same generation SHALL persist one retired identity before exactly one SESSION_END settles, durably transition it to settled-cleanup ownership, remove that marker, and only then SHALL Connection physically leave Chat and the last World room while observers classify one leave + +#### Scenario: Every non-active final-release phase fences live authority + +- **GIVEN** Session has a pending release in `retiring`, `retrying`, `publishing`, `pending`, `settling`, `settlement-failed`, `cleaning`, or `cleanup-failed`, or has restored `inflightEnd`, `pendingEnd`, or `settledEnd` without an active `local` lease +- **WHEN** the current or replacement host requests text allocation, reaction allocation, or live Chat send +- **THEN** both Server preflight and the authoritative Session Command SHALL reject before HLC allocation or Wire send, no live frame SHALL be added, and successful marker cleanup SHALL retain that fence until physical domain release completes + +#### Scenario: SESSION_END send rejects + +- **GIVEN** durable retirement succeeded but the SESSION_END send rejects +- **WHEN** the send failure settles +- **THEN** Session SHALL durably transition that generation from in-flight to retryable pending final end, Connection SHALL retain Chat/World physical membership and publish no false local departure, and a later same-host final-release request SHALL durably transition the same marker back to in-flight before retrying the idempotent end + +#### Scenario: Host replacement continues an unsettled final end + +- **GIVEN** durable retirement succeeded and a first or retry SESSION_END is unsettled or explicitly rejected +- **WHEN** the Runtime host is replaced and the same user invokes join before END settlement +- **THEN** the replacement SHALL use the retained `presenceId` only to physically rebind and continue the same END transaction, SHALL expose no successful active join or live-message authority, and SHALL finish with at most one observer leave plus no persistent marker; a subsequent explicit join SHALL allocate a new generation + +#### Scenario: Post-settlement cleanup rejects + +- **GIVEN** SESSION_END settled and Session durably replaced the unsettled identity with private settled-cleanup ownership +- **WHEN** marker removal rejects +- **THEN** Session SHALL retain settled-cleanup ownership and Chat/World physical membership still owned by the current host, surface a request-local error, publish no second SESSION_END merely to retry cleanup in the same host, and permit physical departure only after later marker removal succeeds + +#### Scenario: Host replacement assumes settled cleanup ownership + +- **GIVEN** the observer ledger accepted SESSION_END and durable settled-cleanup ownership remains after a cleanup rejection +- **WHEN** the same user's replacement host invokes join +- **THEN** it SHALL only remove that marker, SHALL join neither Chat nor World, SHALL publish no SESSION or SESSION_END, SHALL expose no active session or live-message authority, and SHALL preserve the observer's exactly-once leave; only a later explicit join MAY allocate a fresh `presenceId` and one new logical join + +#### Scenario: Readiness helper distinguishes mounted UI from convergence + +- **WHEN** automated acceptance observes an already-mounted usable chat textarea after a refresh or restart +- **THEN** the helper SHALL accept that UI readiness immediately; a separate bounded eventual membership/presence wait MAY guard against a hang, and the five-second domain grace SHALL NOT be treated as a UI-convergence deadline + +#### Scenario: Grace expiry + +- **WHEN** no page of the domain reconnects within 5 seconds and durable retirement plus SESSION_END settlement succeed +- **THEN** the ChatRoom connection, Runtime domain state, volatile inbound un-ACK delivery buffer, and WorldRoom presence for that domain SHALL all be released or removed in the required causal order, with no persistent outbound status or same-id crash retry retained + +#### Scenario: Event outside grace + +- **WHEN** an inbound event targets a domain that is unregistered or past its grace period +- **THEN** the system SHALL discard the event because no persistence location exists for it + +### Requirement: Peer wire protocol is replaced with v2, without compatibility + +The peer-to-peer wire protocol SHALL be replaced with the v2 contract defined by the `peer-wire-protocol` capability. The system SHALL NOT bridge, translate, or interoperate with the released v1 protocol, and v1 and v2 clients SHALL be isolated by v2 room namespaces so neither parses the other's traffic. + +#### Scenario: v1/v2 isolation + +- **WHEN** a v1 client and a v2 client exchange traffic +- **THEN** they SHALL NOT share a room namespace and no compatibility fallback SHALL exist + +#### Scenario: Old protocol removal + +- **WHEN** the release candidate is inspected +- **THEN** the old protocol schemas, the JSONR interop adapter, page-side message routing, reaction toggle, history upsert, and HLC-only history cursor SHALL be absent + +### Requirement: Application/page Domain owns local records and projections + +Each domain's existing origin database SHALL remain the only message-history and local tab-synchronization mechanism. The application/page Domain/model layer SHALL own `MessageRecord`, `ChatMessageRecord`, `SystemNoticeRecord`, projected UI models, internal record helpers/codecs, and projection adapters. It SHALL directly consume protocol `ChatMessage` values and SHALL NOT define a second public/local `TextMessage` or `ReactionMessage` DTO with the same wire names. `MessageProjection` SHALL remain outside `src/protocol` and SHALL own reaction LWW winner calculation, record-to-UI message projection, and notification/danmaku projection. No headless Runtime Domain SHALL maintain a history copy or expose a writable application read-model replica. Designated Runtime/application persistence modules MAY use the injected Database through the internal concrete MessageStore; Chat/UI and the public ChatRoom extern SHALL NOT name stores, indexes, ranges, transactions, `DatabaseItem`, or adapters. + +The application/page Domain SHALL define this exact clean-cut record contract: + +```ts +export const MESSAGE_RECORD_TYPE = { + CHAT_MESSAGE: 'chat-message', + SYSTEM_NOTICE: 'system-notice' +} as const + +export interface ChatMessageRecord { + readonly type: typeof MESSAGE_RECORD_TYPE.CHAT_MESSAGE + readonly id: string + readonly message: Message + readonly user: ChatUser + readonly receivedAt: number +} + +export type TextMessageRecord = ChatMessageRecord +export type ReactionMessageRecord = ChatMessageRecord + +export interface Notice { + readonly id: string + readonly hlc: HLC + readonly type: NoticeType + readonly body: string +} + +export interface SystemNoticeRecord { + readonly type: typeof MESSAGE_RECORD_TYPE.SYSTEM_NOTICE + readonly id: string + readonly notice: Notice + readonly user: ChatUser + readonly receivedAt: number +} + +export type MessageRecord = ChatMessageRecord | SystemNoticeRecord +``` + +A strict decoder SHALL choose the closed variant only through outer `record.type`; it SHALL NOT infer record kind from `DatabaseItem.key`, an id/key prefix or shape, or the presence of `message`/`notice`. Every decoded record SHALL satisfy `DatabaseItem.key === record.id`. A decoded Chat record SHALL also satisfy `record.id === record.message.id` and `record.user.id === record.message.userId`; a decoded SystemNotice SHALL satisfy `record.id === record.notice.id`. Chat and notice ids SHALL occupy one globally unique record-id space so atomic first-value-wins cannot collide across variants. `Notice.type` remains the independent `join | leave | info` reason; outer `record.type` remains only the `chat-message | system-notice` storage/Domain category. SystemNotice identity SHALL be deterministic and it SHALL never enter peer wire or history. `receivedAt` is finite local first-acceptance/creation time, not an HLC, peer timestamp, or delivery state. Record ordering helpers SHALL use `(record.message.hlc, record.id)` for Chat and `(record.notice.hlc, record.id)` for SystemNotice; reaction LWW SHALL continue to use `(message.hlc, message.id)`, and UI reaction aggregation remains projection-only. There SHALL be no standalone `SYSTEM_NOTICE`, old outer notice `hlc`/`body`/`noticeType`, `LocalRecord`, `DurableEventRecord`, outer `event` alias, property-presence guard, key-based discriminator, `RecordStatus`, pending/sent/received state, mark method, outbox metadata, compatibility alias, or dual-read path. + +The only host-replaceable persistence extern SHALL be `Database`. IndexedDB and Memory SHALL implement it for one private `MessageDatabaseSchema` and private logical database name/version/store/index configuration. A future backend is compatible only after running the same public contract suite. The single internal concrete MessageStore SHALL be Database-backed and expose only `insert(record): Promise`, `query(query?: MessageQuery)`, `clear`, and `watch`; it SHALL NOT expose `list` or a compatibility alias and SHALL NOT be a Remesh extern, public-barrel export, host injection point, or independently replaceable backend. Its internal helpers SHALL strictly decode MessageRecord, derive message/notice keys and HLCs, validate Database item identity, distinguish same-id canonical replay from different-content conflict, and keep the first value without overwrite. `InsertMessageResult` SHALL be the readonly inserted/existing Domain union and SHALL NOT reuse Database's result type. + +The canonical per-origin IndexedDB identity SHALL remain stable, including its existing v2 database name. Private `MESSAGE_STORE_VERSION = 2` colocated with the native upgrade callback SHALL name the existing schema authority. This abstraction and record cleanup SHALL NOT itself advance the version or add an upgrade. The version SHALL advance only with an implemented compatible store/index/key/value migration; ordered IndexedDB upgrade transactions SHALL preserve canonical records and bounded conflict diagnostics. App/wire versions and ordinary fixes SHALL NOT clear, rename, or delete the database. Old unstorage data remains unread, unconverted, and uncleared. + +The headless Runtime SHALL own only network/history orchestration around the application-owned store: `HistoryDomain` owns requester/provider State, candidate-window and byte/message budgets, supplier selection/failover, page cancellation, and physical settlement; `WireDomain` owns protocol scheduling/queues; the page-host boundary owns internal RPC. Shared models consumed by both pages and Runtime SHALL remain defined by an application Domain/model module rather than by any Runtime owner or the public protocol. + +#### Scenario: Application persistence boundary + +- **WHEN** a page persists, projects, or synchronizes local records +- **THEN** the Database-backed application persistence boundary SHALL own that work, the headless Runtime Domains SHALL own no history read model, and Chat/UI SHALL receive only decoded Domain records/projections rather than storage primitives + +### Requirement: Application ports are exact, minimal, and replaceable + +`RuntimeServer` and `PagePort` SHALL remain internal page-host boundaries and MAY retain private Runtime contracts. Their adapter SHALL terminate trusted `sourcePeerId`, local/remote provenance, host gating, snapshots, replay, transient operation state, history supply, callback cleanup, timers, and storage detail before application Domains receive values. Runtime-to-page session delivery SHALL converge through one internal session-event path capable of carrying local self convergence, remote live changes, live leaves, and replacement snapshots with private provenance; separate `onLocalSession`/`onSession`/`onSessionLeave` public or adapter-facing paths SHALL be removed. This internal path SHALL NOT be exported from an extern. + +The application `ChatRoom` extern SHALL directly type-import the exact public protocol `ChatUser`, `ChatSession`, `ChatSite`, `ChatMessage`, and `MentionedUser` structures. Its entire public contract SHALL be exactly: + +```ts +interface JoinRoomCommand { + user: ChatUser + site: ChatSite +} +interface SendTextCommand { + type: 'text' + body: string + mentions: MentionedUser[] +} +interface SendReactionCommand { + type: 'reaction' + targetId: string + reaction: 'like' | 'hate' + active: boolean +} +type SendMessageCommand = SendTextCommand | SendReactionCommand +type Unsubscribe = () => void +interface ChatRoom { + joinRoom(command: JoinRoomCommand): Promise + leaveRoom(): Promise + sendMessage(command: SendMessageCommand): Promise + onMessage(listener: (message: ChatMessage) => void): Unsubscribe + onJoinRoom(listener: (session: ChatSession) => void): Unsubscribe + onLeaveRoom(listener: (session: ChatSession) => void): Unsubscribe + onSessions(listener: (sessions: readonly ChatSession[]) => void): Unsubscribe + onError(listener: (error: Error) => void): Unsubscribe +} +``` + +No alias, compatibility field, overload, extra method, generic metadata bag, Runtime type, or peer/source/joinedAt/timer/IDB/host/page/lease/retry field SHALL extend this contract without explicit Owner intervention. `joinRoom` SHALL use the caller-supplied user/site and `impls` SHALL create `sessionId`. `leaveRoom` SHALL operate on the currently joined room instance. `sendMessage` SHALL accept only the frozen business commands; `impls` SHALL create `id`, `hlc`, and `userId`, complete transport acceptance, and call local `MessageStore.insert`. Only after that insert operation successfully settles SHALL it resolve with the exact `ChatMessage` allocated and transported by that call. It SHALL NOT return or expose an insert result, same-id existing winner, `MessageRecord`, delivery status, or Runtime provenance. + +`onSessions` SHALL be the only application session-state truth. For a normal accepted live change, the implementation SHALL publish the updated immutable session snapshot before the corresponding `onJoinRoom` or `onLeaveRoom` fact. Initialization, first join, hydration, refresh, reconnect, host replacement, and replay SHALL publish snapshots only, with no synthetic live delta. `onMessage` SHALL publish only a first durably accepted remote live `ChatMessage`; it SHALL exclude `SessionMessage`, history request/response/control traffic, history messages/replay, initial IDB reads, local sends, duplicate/conflicting inserts, and store-watch replay. Chat and World session instances SHALL remain distinct; same-origin pages share one Runtime logical Chat view, while separate browsers/devices maintain local views that converge. + +Only explicit composition roots SHALL select and inject concrete `impls`. Application Domains and externs SHALL NOT import concrete `impls` or Runtime contracts. Application readiness SHALL retain immediate replay of `connecting | ready | unavailable`. `WorldRoomExtern` SHALL remain separately projected and source-free as specified by `world-room-presence`. + +`Unsubscribe = () => void` SHALL be a neutral Domain type. The only replaceable persistence extern SHALL have this exact complete surface: + +```ts +type DatabaseKey = string | number +interface StoreSchema { + key: DatabaseKey + value: unknown + indexes: Record +} +type DatabaseSchema = { [Store in keyof S]: StoreSchema } +type StoreName = keyof S & string +type Scope = readonly [Name, ...Name[]] +type LowerBound = { lower?: never; lowerOpen?: never } | { lower: T; lowerOpen?: boolean } +type UpperBound = { upper?: never; upperOpen?: never } | { upper: T; upperOpen?: boolean } +type DatabaseRange = LowerBound & UpperBound +type PrimaryQuery = { + index?: never + range?: DatabaseRange +} +type IndexQuery = { + [Index in keyof S['indexes'] & string]: { + index: Index + range?: DatabaseRange + } +}[keyof S['indexes'] & string] +type QueryOptions = PrimaryQuery | IndexQuery +type ScanOptions = QueryOptions & { + direction?: 'asc' | 'desc' + limit?: number +} +interface DatabaseItem { + readonly key: Key + readonly value: Value +} +type InsertResult = { readonly inserted: true } | { readonly inserted: false; readonly existing: Value } +interface ReadTransaction, Allowed extends StoreName> { + get(store: Store, key: Schema[Store]['key']): Promise + scan( + store: Store, + options?: ScanOptions + ): Promise[]> + count(store: Store, options?: QueryOptions): Promise +} +interface WriteTransaction< + Schema extends DatabaseSchema, + Allowed extends StoreName +> extends ReadTransaction { + insert( + store: Store, + key: Schema[Store]['key'], + value: Schema[Store]['value'] + ): Promise> + put(store: Store, key: Schema[Store]['key'], value: Schema[Store]['value']): Promise + delete(store: Store, key: Schema[Store]['key']): Promise + clear(store: Store): Promise +} +interface Database> { + read>, Result>( + stores: Stores, + operation: (transaction: ReadTransaction) => Promise, + signal?: AbortSignal + ): Promise + write>, Result>( + stores: Stores, + operation: (transaction: WriteTransaction) => Promise, + signal?: AbortSignal + ): Promise + watch>>(stores: Stores, listener: () => void): Unsubscribe + close(): Promise +} +``` + +`DatabaseKey` deliberately supports only strings and finite numbers; arbitrary IndexedDB Date/array/binary keys are outside the contract. A schema SHALL statically associate each store with its primary-key, value, index names, and index-key types, and a transaction SHALL reject any store not enlisted in its non-empty, duplicate-free scope. Primary scans order by primary key. Index scans order by index key and then primary key as a deterministic tie-break; `desc` reverses both. Bounds are inclusive unless their corresponding open flag is true. An open flag without its bound, a reversed bound, an unknown store/index, a non-finite key, or a limit that is not a finite non-negative safe integer SHALL reject deterministically; an equal bound with either side open yields an empty range. + +A read SHALL observe one consistent transaction snapshot. A write SHALL be atomic across its enlisted stores and SHALL never automatically retry its callback. The callback MAY await only promises created by that same transaction and SHALL NOT await network, timers, or arbitrary external promises. Callback throw/rejection or signal abort before commit SHALL abort the physical transaction, reject, persist nothing, and emit no watch notification. A successful commit linearized before a later abort SHALL remain successful. An atomic insert collision SHALL not abort the transaction; it SHALL return the exact same-key existing winner from that operation. `inserted:false` itself is not a mutation. Database failures, decode failures, and aborts SHALL reject rather than masquerade as collisions. + +`get` and `scan` SHALL return by-value-isolated snapshots: reassigning readonly item fields or mutating a returned nested object SHALL NOT mutate persistence; only an explicit `put` can write a replacement. Memory SHALL clone/isolate values to match IndexedDB. Canonical Message database values are limited to null, booleans, strings, finite numbers, dense arrays, and plain string-keyed objects; undefined, non-finite numbers, bigint, functions, symbols, Date, Map, Set, binary values, sparse arrays, cycles, and implementation-specific objects SHALL reject. TypeScript schema types do not replace strict runtime decoding. + +`watch` SHALL register synchronously for a non-empty store scope and act only as invalidation. Every relevant successful write commit after registration SHALL produce at most one notification per Database instance/context; rollback and failed writes SHALL produce none. Same-logical-database notifications SHALL cross supported same-origin contexts, listener exceptions SHALL be isolated, and unsubscribe SHALL be idempotent and prevent later callbacks. Conservative notification for an explicit delete/clear/put that makes no observable value change MAY occur, but no state-changing commit may be permanently missed. `close` SHALL be idempotent, stop new read/write/watch calls and this instance's callbacks immediately, allow already-started transactions to drain to their normal settlement, then release database and cross-context resources. + +The one internal concrete facade SHALL have this exact shape: + +```ts +type InsertMessageResult = { readonly inserted: true } | { readonly inserted: false; readonly existing: MessageRecord } +type MessageQuery = Readonly<{ + type?: MessageRecord['type'] + signal?: AbortSignal +}> +interface MessageStore { + insert(record: MessageRecord): Promise + query(query?: MessageQuery): Promise + clear(): Promise + watch(listener: () => void): Unsubscribe +} +``` + +MessageStore SHALL have one Database-backed implementation and no Remesh extern, public-barrel export, host injection, Memory-specific implementation, or fake replacement. It SHALL map Database's insert result into its own Domain result; strictly decode each physical item independently by outer `record.type`; retain every invalid raw row without publication; record bounded, per-key deduplicated diagnostics in the existing private conflicts store without notifying canonical-record watchers; enforce `DatabaseItem.key === record.id`, Chat `record.id === record.message.id`, notice `record.id === record.notice.id`, and Chat user/message identity; preserve the first canonical value; retain bounded conflict diagnostics for same-id different-content input; and classify same-content replay without overwrite. Key/id prefixes, property presence, the removed standalone `SYSTEM_NOTICE`, and compatibility aliases SHALL NOT participate in decode or dispatch. `query()` SHALL return every valid canonical record in physical primary-scan order even when other physical rows are invalid. `query({type})` SHALL return only valid records whose exact outer discriminator equals that type, after independently attempting to decode every physically read item before applying the type filter. `query({signal})` SHALL gate the physical read transaction and all subsequent decode/filter work. The optional query object SHALL contain only `type` and `signal`; an unknown field or a type outside the exact outer discriminator union SHALL reject deterministically. It SHALL expose no id, range, order, limit, cursor, or syncId criterion. The type filter SHALL add no physical index, schema migration, or version bump; private `MESSAGE_STORE_VERSION = 2` remains the schema authority. `clear` SHALL run only for an explicit user/application clear command and SHALL atomically clear canonical records plus conflicts; startup, app-version checks, and compatibility code SHALL NOT call it. `watch` SHALL observe canonical-record invalidation; a conflict-only diagnostic write need not invalidate visible query results. Neither Database nor MessageStore SHALL own history wire DTOs, `syncId`, cursor, cutoff, limit, response `messages`, or `done`; history cutoff/pagination/projection remains in its Runtime/application owner. No `list` alias or test-only conflict-count API is exposed. + +Default IndexedDB and Memory implementations SHALL use the same private `MessageDatabaseSchema` plus private logical name/version/store/index definition and run one unchanged backend contract suite; this requirement SHALL NOT add a public generic adapter factory. Type-negative tests SHALL reject wrong stores, keys, values, indexes, ranges, scopes, MessageQuery fields, and MessageQuery discriminator values. Separate parity tests SHALL cover Database transactions/abort/insert/order/watch/close/value isolation, MessageStore replay/conflict/decode/query-default/query-type/query-abort/clear/watch, and static imports proving Chat/UI/public ChatRoom do not import Database items, transactions, schemas, or concrete adapters. Residue scans SHALL reject the removed MessageStore `list` member, call sites, aliases, and dual paths without treating ordinary UI list nouns as compatibility APIs. If implementation proves any frozen field, method, or semantic insufficient, work on that boundary SHALL stop and the implementer SHALL send the exact gap, earliest blocked call chain, and minimum decision only to `@PM`. PM SHALL independently verify it and, when Owner input is required, ask `@molvqingtai` the one minimum decision. No implementation may add an alias, field, method, overload, fallback, or relaxed behavior autonomously. + +#### Scenario: Atomic Database commit and cancellation + +- **WHEN** a scoped read/write callback completes, throws, rejects, or receives an AbortSignal before or after physical commit +- **THEN** IndexedDB and Memory SHALL agree on snapshot visibility, all-or-nothing settlement, no callback retry, pre-commit rollback/no-watch, post-commit success, and rejection of scope-external access + +#### Scenario: Atomic existing-winner insert + +- **WHEN** concurrent writers insert the same primary key with the same or different canonical value +- **THEN** exactly one first value SHALL persist, every loser SHALL receive an isolated exact existing snapshot without aborting its transaction, and the internal MessageStore SHALL classify replay or bounded conflict without overwrite + +#### Scenario: Deterministic primary and index scan + +- **WHEN** a primary or secondary-index scan applies bounds, direction, duplicate index keys, and a limit +- **THEN** both backends SHALL return readonly DatabaseItems in the same index-then-primary order, preserve the primary key in each item, and reject every invalid store/index/range/key/limit combination identically + +#### Scenario: Cross-context committed-write invalidation + +- **WHEN** an enlisted store is mutated, rolled back, closed, or watched from another supported same-origin context +- **THEN** each relevant successful commit SHALL cause at most one invalidation per context, rollback SHALL cause none, listener failure SHALL not affect commit, unsubscribe/close SHALL stop callbacks, and no state-changing commit SHALL be permanently missed + +#### Scenario: Strict MessageRecord boundary + +- **WHEN** DatabaseItems are inserted, queried with default or exact-type criteria, replayed, conflicted, cleared, or observed through MessageStore +- **THEN** MessageStore SHALL accept only the exact outer-type MessageRecord union; validate `DatabaseItem.key === record.id`, Chat `record.id === record.message.id`, notice `record.id === record.notice.id`, shared cross-variant id uniqueness, and Chat user/message identity; reject key/property-presence discrimination and every old shape; keep SystemNotice local-only; expose only its four methods; and never expose Database primitives or history protocol values + +#### Scenario: Default-all typed MessageStore query + +- **WHEN** a caller invokes `query()` with no object, with one exact outer `type`, with an AbortSignal, or with an invalid field/discriminator +- **THEN** the facade SHALL respectively return all valid strictly decoded canonical records while isolating invalid rows, return only that type after independently decoding the complete scan, honor cancellation through read/decode/diagnostic/filter settlement, or reject deterministically; it SHALL preserve primary-scan order, expose no `list` alias or history criteria, and introduce no physical index, migration, or version change + +#### Scenario: Frozen definition proves insufficient + +- **WHEN** implementation or parity testing cannot satisfy a required behavior with the exact frozen Database, MessageRecord, MessageStore, or ChatRoom definitions +- **THEN** the implementer SHALL stop that work and report only to `@PM`; PM SHALL verify the exact gap and earliest blocked call chain before asking `@molvqingtai` for any contract extension or fallback decision + +Production Runtime/application adapters SHALL use `globalThis.setTimeout` and `globalThis.clearTimeout`; naked timer functions SHALL NOT be constructor dependencies or extern capabilities. Tests SHALL use Vitest fake timers. Timer callbacks SHALL still validate attempt/generation identity, and cleanup SHALL cancel owned handles without letting a stale callback mutate current state. + +#### Scenario: Exact ChatRoom surface + +- **WHEN** the ChatRoom extern, its imports, or a fake replacement is inspected +- **THEN** only the exact frozen commands, protocol values, eight methods, and `Unsubscribe` SHALL exist; `join`, `sendText`, `sendReaction`, `onRecord`, `onMembership`, `reconnect`, Runtime snapshots, source metadata, aliases, and compatibility members SHALL be absent + +#### Scenario: One internal session path and ordered application events + +- **WHEN** local convergence, a remote live join/leave, initialization, refresh, reconnect, host replacement, or replay changes session state +- **THEN** one private Runtime session-event path SHALL feed the implementation; a normal live change SHALL deliver the new `onSessions` snapshot before its one join/leave fact, while initialization/recovery/replay SHALL deliver snapshots only and no Runtime provenance SHALL escape + +#### Scenario: Natural live message callback + +- **WHEN** a remote live Chat message, history response, local send, duplicate, conflict, initial store read, or store replay is processed +- **THEN** `onMessage` SHALL fire exactly once only for the first durably accepted remote live `ChatMessage` and SHALL remain silent for every excluded path + +#### Scenario: Exact command allocation and send-first persistence + +- **WHEN** `sendMessage` accepts `SendTextCommand` or `SendReactionCommand` +- **THEN** the caller SHALL provide none of `id`, `hlc`, `userId`, or `sessionId`; `impls` SHALL allocate them, complete transport acceptance, then call `MessageStore.insert`, and only after that operation successfully settles resolve with the exact allocated-and-transported `ChatMessage`, even when same-id handling retains an existing canonical winner; it SHALL return neither that winner nor a status/result DTO and SHALL add no hidden callback or retry + +#### Scenario: Causal local send projection + +- **WHEN** local text sends race store-watch refresh, another tab sends the same body/mentions, or a same-id collision retains another canonical winner +- **THEN** the application SHALL derive exactly one local success projection from the `ChatMessage` returned by that call, never visible-record diff, body/mention matching, `onMessage`, or a hidden side channel; transport or MessageStore rejection SHALL return no message, preserve the draft, and emit no local success projection + +#### Scenario: Database and MessageStore exclude history protocol + +- **WHEN** the Database extern, internal MessageStore, and their implementation imports are inventoried +- **THEN** only Database SHALL be replaceable; MessageStore SHALL remain the one concrete four-method facade; neither SHALL expose history request/response/cursor/cutoff/limit/done projection or a test-only count API, while the outside owner retains cancellable physical history selection and projection + +#### Scenario: Duplicate delivery and conflict convergence + +- **WHEN** multiple pages/deliveries attempt one message id or a history value conflicts +- **THEN** one atomic insert SHALL win, the first canonical message SHALL remain, same-content replay SHALL be idempotent, different-content conflict SHALL not overwrite it, and side effects SHALL fire at most once + +#### Scenario: Global timer ownership + +- **WHEN** retry, timeout, grace, or cleanup behavior is constructed and tested +- **THEN** production SHALL call `globalThis` timers directly, tests SHALL control them with fake timers, no timer dependency SHALL be injected, and stale/cancelled callbacks SHALL fail their current-attempt identity check + +#### Scenario: No concrete implementation bypass + +- **WHEN** application imports are scanned +- **THEN** only composition roots SHALL wire concrete implementations; Domains SHALL depend on extern contracts and public protocol types, never `impls` or Runtime contracts + +#### Scenario: Runtime unavailable and accepted send-first loss window + +- **WHEN** a send begins before readiness, transport rejects, or transport succeeds but local insertion later fails or never starts because the browser exits +- **THEN** unavailable/rejected input SHALL preserve the draft, write no normal local record, return no `ChatMessage`, and emit no local success projection, while the post-handoff local-loss window SHALL remain accepted with no status, outbox, same-id crash retry, or hidden fallback + +#### Scenario: No Runtime history copy + +- **WHEN** Runtime and persistence ownership are inspected +- **THEN** the headless Runtime Domains SHALL keep no history replica, Database SHALL remain the only replaceable persistence extern, and the one internal MessageStore SHALL own no peer-history protocol orchestration + +### Requirement: Runtime Chat session lifecycle + +The headless Runtime SHALL bind each Chat source to a session identity and incarnation. A join SHALL send `session {sessionId, user}` before live text, reaction, or history traffic. A bound `sessionId` SHALL not change its `user.id`; live event `userId` SHALL match the transport-bound session user. A new incarnation SHALL retire the old binding and old history sync, and SHALL trigger exactly one fresh history request for the replacement without running it concurrently with unsettled old source work. + +#### Scenario: Session binding and replacement + +- **WHEN** a source joins Chat, sends a second session with a changed user, or reconnects with a new incarnation +- **THEN** the Runtime SHALL require the session message first, reject a user change for the same `sessionId`, reject live events whose `userId` does not match the bound user, retire the old binding/sync for a new incarnation, and issue exactly one fresh history request for the replacement + +#### Scenario: Future HLC does not advance Runtime clock + +- **WHEN** the Runtime receives a wire event rejected because its HLC is more than five minutes ahead of the explicit receiver `now` +- **THEN** it SHALL reject the event, leave the central HLC clock unchanged, and continue processing later valid events + +### Requirement: Headless Runtime owns history orchestration + +`HistoryDomain` SHALL own history synchronization policy around the application/page Domain's origin store. At each sync start, it SHALL freeze the receiver/requester's own `requester cutoff = requester wall clock - 180 days` (`HISTORY_WINDOW_DAYS = 180`). A remote response message exactly at that requester cutoff SHALL be eligible; only an earlier remote response message SHALL be rejected. At its corresponding provider supply/session admission, the selected provider SHALL separately freeze its own `provider cutoff = provider wall clock - 180 days`. A local candidate exactly at that provider cutoff SHALL be eligible, and only earlier local candidates SHALL be excluded without deletion; its local query, subsequent cursor, and local page failover SHALL retain that provider cutoff without re-reading time. A dormant successor SHALL freeze its own provider cutoff at its own admission and SHALL retain it after promotion. The cutoffs SHALL NOT be transmitted or required to match. The requester SHALL independently validate every response against its own cutoff and has final acceptance authority, so a remote provider SHALL NOT expand the requester window, although clock skew MAY omit boundary candidates that the requester would otherwise accept. The requester cutoff SHALL remain unchanged through that sync's pagination, retry, and provider failover. `HistoryDomain` SHALL enforce at most one outstanding request per source with a 10-second operational timeout, and stop a session at 8MiB or 10,000 messages while preserving the most recent accepted responses and never falsely claiming provider completion. `HistoryDomain` SHALL select one application/page supplier per request, keep requester/provider State scoped by `(sourcePeerId, domain, syncId, unique sync token)`, and SHALL not store a history copy or read-model replica. The public protocol remains responsible only for validating the typed history request/response shape, cursor, response-size limit, and user/message references. + +The Runtime page contract SHALL use an explicit `{supplyId}` request/cancel event plus `resolveHistorySupply`/`rejectHistorySupply` RPC. Each page supply attempt SHALL have a 5-second boundary. A page query SHALL receive an `AbortSignal` wired to its readonly IndexedDB transaction; the signal SHALL abort that transaction and gate all subsequent projection/filter/sort work, and the page SHALL confirm physical settlement only after the entire query and gated work truly exits. Failover, old-job release, and successor promotion SHALL wait for that confirmation. Supplier work SHALL be serial per source, isolated across domains and sources, and admitted through one pipeline covering supplier selection, encode, send, and final release; the pipeline SHALL have at most four active jobs, 32 admitted requests, and 8KiB of decoded request metadata. A replacement session's one-shot request arriving before the old source job settles SHALL occupy one dormant source-local successor within the same global admission; the successor itself SHALL count toward the 32-request and 8KiB decoded-metadata limits, SHALL NOT run concurrently, and SHALL automatically promote after physical settlement without another peer request. Timeout, leave, and release MAY remove an unstarted successor; a started job SHALL remain counted until settlement. A completed response releases its source slot immediately after send settlement, and an old timer/token SHALL NOT delay or close a newer domain/request. Cleanup SHALL retain active admission until physical settlement and remove dormant successors without starting them. + +#### Scenario: Frozen local history policy + +- **WHEN** a history sync begins, and when requester pagination/retry/provider failover, provider local query/subsequent cursor/local page failover, or dormant-successor promotion continues +- **THEN** the Runtime SHALL freeze the receiver/requester's own `requester cutoff = requester wall clock - 180 days`, accept remote response messages exactly at that requester cutoff and reject only earlier remote response messages after independently validating them against that requester cutoff. At corresponding provider supply/session admission, the provider SHALL separately freeze its own `provider cutoff = provider wall clock - 180 days`, accept local candidates exactly at that provider cutoff, exclude only earlier local candidates without deleting them, and retain it through its local query/subsequent cursor/local page failover; a dormant successor SHALL freeze its own provider cutoff at its own admission and retain it after promotion. The cutoffs SHALL NOT be transmitted or required to match; the requester has final acceptance authority, so remote data cannot expand its window, though clock skew MAY omit otherwise acceptable boundary candidates. The requester SHALL retain its cutoff without re-reading time through pagination/retry/provider failover, stop at provider exhaustion/cutoff/8MiB/10,000 events, and SHALL distinguish local budget exhaustion from provider completion + +#### Scenario: Scoped timeout ownership + +- **WHEN** an old requester/provider timeout fires after a replacement domain, request, or sync has started +- **THEN** the Runtime SHALL require the `(sourcePeerId, domain, syncId, unique sync token)` match before changing state, so the replacement retains its own timeout interval + +#### Scenario: Supplier isolation across domains and sources + +- **WHEN** a peer leaves domain A while domain B is waiting on a local supplier, or source A's supplier is hung while source B requests history +- **THEN** only the invalidated domain/source work SHALL be removed; eligible domain B and source B work SHALL continue within the bounded cross-source concurrency pool + +#### Scenario: Physical cancellation settlement + +- **WHEN** a selected page supply attempt reaches its 5-second boundary and the Runtime sends its `supplyId` cancellation +- **THEN** the page SHALL use the `AbortSignal` to abort its readonly IndexedDB transaction and gate subsequent projection/filter/sort work, and SHALL confirm only after the entire physical query and gated work truly exit; failover, old-job release, and successor promotion SHALL wait for that confirmation, and ignored cancellation SHALL keep the old job admitted until settlement + +#### Scenario: Bounded provider admission + +- **WHEN** supplier work is queued or active and cleanup, rejoin, or replacement occurs +- **THEN** started jobs SHALL remain counted until final settlement, dormant successors SHALL be included in the same global counts and removed without starting, and admission SHALL never exceed four active jobs, 32 requests, or 8KiB of decoded request metadata; excess requests SHALL be dropped source-locally without room reconnect + +#### Scenario: Replacement request continues after prior settlement + +- **WHEN** a replacement session sends its one history request while the prior source job is unsettled +- **THEN** the Runtime SHALL admit it as the one dormant source-local successor within the global 32-request/8KiB decoded-metadata admission, SHALL count it against those limits, SHALL NOT run it concurrently, and SHALL automatically promote it to supplier selection after the old physical job settles without requiring another peer request + +#### Scenario: One end-to-end concurrency boundary + +- **WHEN** supplier selection, encoding, or response sending is still active for an admitted history job +- **THEN** all stages SHALL retain the same job admission, and no more than four jobs SHALL be active across the supplier-to-encode-to-send pipeline + +#### Scenario: Completed provider releases its source slot + +- **WHEN** source A completes a final response for domain A and immediately requests domain B +- **THEN** domain B SHALL enter supplier work after the domain A send settles, without waiting for the old 10-second timer; any old timer/token SHALL fail its source/domain/request identity check + +#### Scenario: History application has no UI side effects + +- **WHEN** a valid history response reaches an application/page Domain +- **THEN** records SHALL be inserted-if-absent through the origin store without notifications, unread increments, or system notices caused solely by history application + +### Requirement: Idempotent inbound delivery without locks + +The Runtime SHALL publish each inbound peer message to all pages of the domain as an event carrying the stable message ID. Each page SHALL persist the message through one atomic insert-if-absent transaction against the shared domain store. Only the page whose transaction first inserts the message SHALL trigger side effects such as notification or unread increments. The system SHALL NOT use locks for this coordination. + +#### Scenario: Duplicate delivery across tabs + +- **WHEN** multiple pages of one domain receive the same inbound message event +- **THEN** exactly one atomic insert SHALL succeed, other pages SHALL observe the existing record without duplicating it, and side effects SHALL fire at most once + +#### Scenario: Page crash during delivery + +- **WHEN** a page crashes after receiving an event but before acknowledging +- **THEN** the system SHALL rely on the event buffer and idempotent persistence, not on lock release, to complete delivery safely + +### Requirement: Event sequence and un-ACK buffer + +`DeliveryDomain` SHALL maintain a short-term per-domain event sequence and volatile inbound un-ACK delivery buffer bounded to 512 records and 8MiB. An event SHALL be cleared once at least one page acknowledges durable persistence. A history-response batch SHALL be admitted atomically or rejected as a whole when it would exceed either bound; rejection SHALL not partially receive records, advance the cursor, or request the next response. A page that reconnects SHALL be re-sent unacknowledged inbound events by sequence. Events still unacknowledged when the domain's grace period ends SHALL be discarded. Loss of the buffer when the browser kills the Runtime is an accepted boundary. This inbound buffer is not an outbound outbox and SHALL NOT be used to recover or retry a local send. + +#### Scenario: ACK clears buffer + +- **WHEN** at least one page of the domain acknowledges durable storage of an event +- **THEN** the Runtime SHALL remove that event from the un-ACK buffer + +#### Scenario: Reconnect resend + +- **WHEN** a page reconnects within the grace period +- **THEN** the Runtime SHALL re-deliver buffered events by sequence so the page can persist them idempotently + +#### Scenario: Grace-expiry discard + +- **WHEN** the domain's 5-second grace ends with unacknowledged events +- **THEN** the Runtime SHALL discard those events and accept the documented loss boundary + +#### Scenario: Atomic history batch admission + +- **WHEN** a history-response batch would exceed 512 records or 8MiB in the volatile un-ACK buffer +- **THEN** the Runtime SHALL reject the whole batch, preserve existing records, leave the cursor unchanged, and SHALL NOT request the next response + +### Requirement: Runtime WirePipeline and internal RPC contracts stay inside Runtime boundaries + +`WireDomain` SHALL own per-room send serialization, per-source decode queues bounded to at most 8 frames and 256KiB of wire data, and source-local drop/apply/flush behavior. `HistoryDomain` SHALL own history concurrency admission and resource scheduling. The page-host boundary SHALL own the internal comctx RPC contracts/namespace. Malformed frames and queue overflow SHALL be dropped only for the affected source, logged with rate limiting, and SHALL NOT reconnect the room. Those contracts SHALL not be exported from `src/protocol/index.ts`; queue/drop types SHALL live with `WireDomain`, history admission types with `HistoryDomain`, and RPC types with the page-host boundary rather than the codec or public protocol module. `compareHLC` and `compareEventPosition` implementations SHALL live in the application/page Domain/model layer, or in a shared Domain/model module when both pages and Runtime consume them, while the public protocol defines only the canonical `(hlc, id)` ordering rule. HLC validators exposed by protocol SHALL accept an explicit `now` argument and SHALL NOT call `Date.now()` internally. + +#### Scenario: Internal Runtime ownership + +- **WHEN** queue/drop/pipeline scheduling, ordering implementation, or page-host RPC contracts are inspected +- **THEN** queue/drop/pipeline scheduling and page-host RPC contracts SHALL be defined under headless Runtime owners, while ordering implementations SHALL be defined under application/page Domain/model owners or a shared Domain/model module; none SHALL be exported from `@/protocol` or imported by protocol code + +#### Scenario: Source-local decode overflow + +- **WHEN** one source exceeds 8 queued frames or 256KiB of queued wire data, or sends a malformed frame +- **THEN** the Runtime SHALL drop only that source-local frame/work, rate-limit the diagnostic, preserve other sources, and SHALL NOT reconnect the room + +#### Scenario: Final send settlement releases source slot + +- **WHEN** a provider's final history response send settles and an old timeout/token later fires +- **THEN** the Runtime SHALL release the source slot immediately after send settlement, and the old timer/token SHALL fail its identity check without delaying or closing a newer domain/request + +### Requirement: Remesh DDD and CQRS govern every Runtime Domain + +Every Runtime Domain SHALL document and implement its State, Query, Command, Event, Effect, and Extern surface. A Command SHALL express intent and SHALL NOT be treated as proof that external work already occurred. A Query SHALL be a pure read or derivation from State and SHALL perform no I/O, issue no Command/Event, and mutate no State. An Event SHALL represent an immutable fact that already occurred after a transition or external outcome and SHALL NOT be used as a disguised Command. An Effect SHALL contain asynchronous or external I/O and SHALL feed its outcome back through the owning Domain's Command/Event path rather than mutate another Domain directly. An Extern SHALL inject an external capability and SHALL NOT become a parallel State, cache, or recovery owner. + +Application/UI code SHALL interact through Queries, Commands, and Events only. It MAY retain join input, draft state, and display read models, but it SHALL NOT own or write back a Runtime network fact. A shared network fact SHALL be read from the same Domain instance obtained through `domain.getDomain(...)`, never recreated by a module or copied into another writable State. + +#### Scenario: Query remains pure + +- **WHEN** any Runtime or application Query is evaluated +- **THEN** it SHALL derive only from current Domain State and SHALL perform no transport, persistence, comctx, timer, provider, Command, Event, or State-write operation + +#### Scenario: External result re-enters its owner + +- **WHEN** an Effect completes provider, clock, identity, page-port, persistence, or comctx I/O +- **THEN** the result SHALL re-enter through the owning Domain's Command/Event transition before becoming observable State, and the Effect SHALL NOT mutate another Domain's State directly + +#### Scenario: UI has no Runtime write authority + +- **WHEN** a page renders sessions, presence, readiness, or message projections +- **THEN** it SHALL derive them from application Queries fed by Runtime/application Events and SHALL NOT use the display model as a recoverable or writable Runtime source + +### Requirement: Runtime facts have exactly one writable Domain owner + +The shared Runtime SHALL split writable authority exactly by responsibility: + +- `LifecycleDomain` SHALL uniquely own page leases, per-domain reference counts, the unified five-second grace phase/deadline, and domain-release identity. +- `ConnectionDomain` SHALL uniquely own join, leave, reconnect, recovery attempts, physical-acceptance phase, and current host/domain generation. +- `SessionDomain` SHALL uniquely own committed local/remote Chat sessions, the full committed session snapshot, session incarnation, and central id/HLC allocation State. +- `WorldDomain` SHALL uniquely own the active-domain registry, local World session/snapshot, remote per-source presence snapshots, and derived World presence. +- `HistoryDomain` SHALL uniquely own requester/provider sessions, frozen cutoffs, cursors, budgets, supply ids, batches, and timeout identities. +- `DeliveryDomain` SHALL uniquely own per-domain inbound sequence, volatile un-ACK buffer, byte/event admission, history-batch membership, replay, and ACK completion. +- `WireDomain` SHALL uniquely own trusted room/source membership, provider-ready peer facts, per-room send serialization, per-source decode queues/drop bounds, immutable protocol translation, and provider callback translation. + +The application/page Domain SHALL remain the unique owner of retained input, origin records, and UI projections. The comctx Server SHALL only construct the graph and adapt request/reply/subscription registration. Mutable Server maps, a catch-all `NetworkDomain`, generic lock controllers, and direct cross-Domain State imports/writes SHALL NOT own any fact listed above. + +The Domain dependency graph SHALL be acyclic. Connection MAY consume Lifecycle, Wire, Session, World, and History; Session MAY consume Wire and Delivery; World MAY consume Wire; History MAY consume Wire, Delivery, and Session; Delivery MAY consume Lifecycle; Wire SHALL consume only the immutable public protocol and Runtime-private provider Extern. The resulting chain `Connection -> History -> Session -> Delivery -> Lifecycle`, together with the allowed edges toward Wire, SHALL remain acyclic. Session SHALL use Delivery only through one admit Command after Session-owned live source/user validation. History SHALL use Session only through a Query that verifies the current trusted `(room/domain, source)` binding before History-owned requester/provider transitions. A Domain SHALL consume another Domain only through its Queries, Commands, and Events. + +#### Scenario: Owner matrix has no duplicate writer + +- **WHEN** each lease, grace, connection generation, committed session/HLC, World presence, history session/cursor/batch, delivery sequence/buffer, and trusted wire/provider fact is traced +- **THEN** exactly one listed Domain SHALL define its writable State and transitions, while every other consumer uses that owner's CQRS surface + +#### Scenario: Runtime graph remains acyclic + +- **WHEN** Runtime Domain imports and `domain.getDomain(...)` dependencies are inspected +- **THEN** they SHALL follow the documented direction, contain no cycle, and expose no direct import or mutation of another Domain's State definition + +#### Scenario: Server owns no network truth + +- **WHEN** the comctx Server and host composition are inspected +- **THEN** they SHALL contain only graph construction, Extern injection, and request/reply/subscription adaptation, with no authoritative session, generation, presence, history, delivery, or trusted-room map + +### Requirement: ChatRoom Runtime implementation is a state-free application adapter + +The public `ChatRoomExtern` SHALL remain exactly the eight frozen methods. `src/domain/impls/runtime/ChatRoom.ts` SHALL be only a Runtime client proxy, Event bridge, persistence-before-ACK boundary, and history-supply boundary. It SHALL contain no retained join command, session map, local-session or published-session State, Runtime generation, join/recovery queue, snapshot cache used for replay, or writable network fact. It MAY retain only infrastructure needed to settle one in-flight call or dispose one active subscription; those handles SHALL be bounded to that operation/subscription and SHALL NOT be read as State or used for recovery. + +Inbound remote live messages SHALL still settle canonical MessageStore insertion before Runtime ACK. History supply SHALL still query the application-owned store through the private page-host contract. Outbound `sendMessage` SHALL retain `allocate -> transport acceptance -> local insert settlement -> exact causal ChatMessage result`, with `onMessage` remaining remote-live-only. Internal page-to-Runtime comctx Commands/Events MAY change in one clean cut to express the Domain graph, but the public eight methods, persistence/ACK order, product behavior, and reliability option B SHALL NOT change. + +#### Scenario: Adapter has no hidden recovery State + +- **WHEN** the Runtime ChatRoom implementation is inspected after construction and across host recovery +- **THEN** it SHALL have no command/session/local-session/published-session/generation/recovery map or snapshot authority, and host recovery SHALL converge from current application intent plus Runtime Domain State + +#### Scenario: Adapter preserves persistence and ACK order + +- **WHEN** a first remote live ChatMessage crosses the adapter +- **THEN** canonical insertion or confirmed canonical existence SHALL settle before ACK, while history and local sends SHALL remain excluded from `onMessage` + +#### Scenario: Internal control contract changes cleanly + +- **WHEN** the page-host comctx contract is replaced to express CQRS Commands and Events +- **THEN** no old request/event alias, fallback, dual listener, or dual read/write route SHALL remain, and the eight-method application port plus product behavior SHALL be unchanged + +### Requirement: Provider capability is private behind WireDomain + +The Runtime SHALL define one private `RoomTransportExtern` injected only into `WireDomain`. It SHALL express provider-neutral capabilities for stable local peer identity, room join/leave, targeted send, transport-confirmed inbound source, peer ready/leave, room close/error, and deterministic dispose. `RoomTransport` MAY remain only as the concrete implementation shape behind that Extern; it SHALL NOT be a public application port, protocol export, or capability imported by UI, application Domains, or non-Wire Runtime Domains. + +Artico SHALL appear only in the provider implementation and explicit composition root. `WireDomain` SHALL be the sole anti-corruption boundary: it validates trusted room/source identity, codec/schema/size limits, ordering and queue bounds, then emits typed Runtime Events; outbound typed Domain intent is encoded and sent only through its Effect and `RoomTransportExtern`. The former imperative `WireExtern` route and every direct concrete/provider call from another Domain SHALL be removed. + +#### Scenario: Provider can be replaced without application change + +- **WHEN** a second provider implementation satisfies the private provider contract +- **THEN** ChatRoomExtern, application Domains, Runtime owner semantics, peer protocol, persistence, and UI SHALL require no change; only provider implementation/composition SHALL differ + +#### Scenario: Artico does not leak + +- **WHEN** imports, public exports, Domain Externs, protocol types, and comctx contracts are scanned +- **THEN** Artico symbols SHALL exist only in its provider implementation and explicit composition, and only `WireDomain` SHALL obtain `RoomTransportExtern` + +#### Scenario: Provider contract parity + +- **WHEN** the provider contract suite runs against the Artico implementation and deterministic fake +- **THEN** both SHALL satisfy stable peer identity, join/leave, trusted inbound source, targeted send, target-local isolation, room-level failure, close, error, and dispose semantics without adding delivery acknowledgement + +### Requirement: Immutable peer values terminate in explicit Domain mappings + +`WireDomain` SHALL terminate every protocol DTO at one typed accepted-message Event and SHALL NOT expose raw provider callbacks, decoded unknown values, or a shared mutable wire model. `SessionMessage` SHALL enter Session binding/generation commit Commands, and `SessionEndMessage` SHALL enter Session's source-bound idempotent generation-end Command. `TextMessage` and `ReactionMessage` SHALL enter Session source/user validation and then Delivery admission. `HistoryRequestMessage` and `HistoryResponseMessage` SHALL enter History Commands; History SHALL verify the current trusted source/session binding through a Session Query before its requester/provider transition, with accepted response batches entering Delivery atomically. `WorldRoomMessage` SHALL enter World source-snapshot replacement. Provider peer-ready/leave and room-close/error facts SHALL enter Connection transitions, which SHALL request Session/World/History cleanup through their Commands rather than mutate them. + +Outbound `SessionMessage` SHALL originate from Session after an accepted Connection generation. Outbound `SessionEndMessage` SHALL originate from final Session release only after private lease retirement and SHALL be sent before physical Chat-room leave. Outbound Text/Reaction SHALL use Session-owned id/HLC allocation and a Wire send Command. History request/response SHALL originate from History State and page-supply outcomes. `WorldRoomMessage` SHALL originate from World's current full snapshot only after Connection acceptance. All outbound values SHALL use the strict current schemas and unchanged codec algorithm; only the Owner-authorized SESSION `presenceId` and SESSION_END shapes differ from the prior baseline. + +#### Scenario: Chat message crosses one trust and delivery path + +- **WHEN** Wire accepts a TextMessage or ReactionMessage from a transport-confirmed source +- **THEN** Session SHALL validate the committed source/user binding before Delivery receives one admit Command, and no other Domain or adapter SHALL store a parallel writable copy + +#### Scenario: History response crosses one owner path + +- **WHEN** Wire accepts a HistoryResponseMessage +- **THEN** History SHALL first verify the current trusted source/session binding through a Session Query, then validate its requester/provider/cursor/budget State and issue at most one atomic Delivery batch admission, without Wire, Server, or ChatRoom adapter owning the history session + +#### Scenario: World snapshot crosses one owner path + +- **WHEN** Wire accepts a WorldRoomMessage or Connection accepts a generation that must publish local presence +- **THEN** World SHALL be the only source-snapshot/presence owner and SHALL replace or publish one full current snapshot through the unchanged protocol + +### Requirement: Remesh modules represent only semantic reuse + +A surviving `*Module` capability SHALL be a real `Remesh.module` instance that is reusable/configurable and whose State, Queries, Commands, Events, transitions, cancellation, and failure semantics are isomorphic in at least two host Domains. Each instantiated module SHALL remain private to its host Domain. A module SHALL NOT be used to share one Runtime State instance across Domains; cross-Domain authority SHALL come only from the unique owner Domain obtained through `domain.getDomain(...)`. + +Existing Status-like capabilities SHALL either become real `Remesh.module` definitions or lose the misleading `Module` name. Attempt/generation logic SHALL become a module only after at least two flows prove the same transition and failure semantics. `DeliveryDomain` SHALL remain a direct unique owner while there is no second isomorphic consumer. Codec/parser, ordering, range, size, and provider callback logic SHALL remain pure functions or Extern implementation logic rather than forced modules. + +#### Scenario: Shared State is not duplicated by modules + +- **WHEN** two Domains need the same Runtime fact +- **THEN** both SHALL consume the unique owner Domain, and SHALL NOT instantiate matching modules as parallel writable copies + +#### Scenario: Module naming is truthful + +- **WHEN** a production `*Module` symbol is inspected +- **THEN** it SHALL call `Remesh.module` and satisfy the semantic-reuse rule, or the helper SHALL have a non-Module name + +### Requirement: Architecture migration is a structural clean cut + +Baseline `9c90bb0...` SHALL remain the functional/fail-before reference and SHALL NOT be accepted as the final Runtime architecture. The requirements commit SHALL remain authority-only and SHALL NOT become a source ancestor. Candidate-sensitive structural tests SHALL first fail on baseline for the stateful ChatRoom adapter, catch-all Network owner, missing private RoomTransport Extern/Wire Domain, CQRS boundary violations, misleading Module names, dependency cycles, and architecture artifact mismatch. + +Implementation SHALL move one writable owner at a time and immediately delete that fact's old owner. No dual read, dual write, alias, fallback, compatibility adapter, old `WireExtern` route, or transitional release SHALL remain. The final candidate SHALL have all source, tests, comments, tasks, residue cleanup, architecture JSON, and generated HTML complete before validation begins. + +#### Scenario: Baseline proves structural sensitivity + +- **WHEN** the architecture fail-before suite runs on exact `9c90bb0...` +- **THEN** each documented old-owner/provider/module/artifact violation SHALL fail for its intended reason while protected peer-wire behavior remains a passing control + +#### Scenario: Final candidate has no old authority path + +- **WHEN** source, imports, State definitions, comctx contracts, tests, comments, and artifacts are scanned +- **THEN** no catch-all Network owner, stateful ChatRoom recovery cache, old WireExtern path, provider leak, false Module name, dual owner, or compatibility route SHALL remain + +#### Scenario: Architecture artifacts are synchronized + +- **WHEN** the canonical architecture JSON is rendered +- **THEN** the generated HTML SHALL reproduce deterministically and show the exact application port, state-free client, seven Runtime owners, immutable protocol, private RoomTransport Extern, provider implementation, and one-way dependency flow + +### Requirement: Session classifies logical presence across physical lifecycles + +Session SHALL uniquely own local active-generation state, unsettled in-flight final-end identity, rejected retryable pending-final-end identity, observer-accepted settled-cleanup identity, and a bounded observer ledger. A private two-method `PresenceStoreExtern` SHALL persist those facts through `browser.storage.session` across supported Runtime host replacement; it SHALL NOT expand MessageStore, the origin database schema, `RuntimeServer`, `ChatRoomExtern`, or any UI/public model. Active lease, in-flight final end, retryable pending final end, and settled cleanup SHALL be four mutually exclusive strict records. Session SHALL allocate a generation only for initial join or true return after complete final end. Refresh, reconnect, recovery, replay, duplicate SESSION, additional physical session, page reattach, supported host replacement, and replacement recovery of any final-end marker SHALL reuse the retained generation and emit snapshot convergence without a logical join/leave. + +Chrome MV3 SHALL construct the concrete session-backed PresenceStore in the background Service Worker and expose only its existing `load`/`save` methods to the Offscreen Runtime through a dedicated comctx adapter over a point-to-point Runtime Port. Port name and comctx namespace SHALL be routing values rather than authority. Before delivering a message, Background SHALL require the transport sender's runtime id, exact Offscreen document URL, and absence of a tab; content, options, and every other extension source SHALL be disconnected without reading or writing durable state. Every provider response SHALL resolve through the exact request-to-Port binding recorded when its request arrived. If that binding has detached or been replaced, the response SHALL be dropped and SHALL NOT fall back to the current active Port. Offscreen SHALL admit a response only while that request remains pending on the same binding; uncorrelated, replayed, old-binding, wrong-namespace, wrong-direction, and broadcast responses SHALL reach no comctx callback. From request-ID response registration, each one-shot call SHALL reserve exactly one ordered transport generation. Generic response subscription SHALL NOT open a Port. The local heartbeat response subscription SHALL unregister before the actual `apply`, and that `apply` SHALL consume the oldest remaining request reservation. If the reserved generation terminates before pending insertion, the call SHALL reject before connecting or posting to a replacement and the adapter SHALL remove that operation's one-shot response entry. Port disconnect, synchronous connect/send failure, and adapter disposal SHALL reject every request and pre-send reservation owned by the terminal generation exactly once and release every adapter-owned per-operation response entry, without hanging or automatically replaying `load` or `save`; stale and late traffic SHALL traverse no terminal operation callback, and only a later new application call with a new request ID may create a replacement Port and correlation. Provider-owned long-lived callback handles SHALL retain their existing refresh/re-registration lifetime and SHALL NOT be removed by this one-shot cleanup. The dedicated adapter SHALL use Port send/disconnect as its liveness authority, satisfy comctx heartbeat preflight locally, and transmit only actual one-shot PresenceStore operations. Offscreen SHALL register no broadcast Runtime-message listener for PresenceStore, so another context cannot forge a provider response or observe one through that adapter. The Offscreen document SHALL receive the dependency through host assembly and SHALL NOT dereference an unavailable `browser.storage.session`, create memory storage, or route presence records through tabs/pages. Firefox MV2 SHALL pass the same concrete session-backed store directly from its persistent Background Page into the same shared host. Storage rejection and authenticated-Port termination SHALL reach Session's existing request-local failure fences without acknowledging, discarding, or weakening the durable transition; a later call after Service Worker recreation SHALL reconnect and use the same session-backed record. + +A peer discovered during preparation SHALL establish baseline membership without a historical observer join. A newly accepted post-baseline generation SHALL produce one observer-local join only when that user transitions from zero active logical generations to one. Physical `PeerLeft` SHALL not produce a logical leave. A valid SESSION_END SHALL produce one observer-local leave only when the user transitions from one active generation to zero. On graceful final local release, Session SHALL replace the active lease with an in-flight final-end identity, send SESSION_END, durably remove that identity after settlement, and only then allow Connection to leave the Chat room. The departing local client need not persist its own leave. + +The local self-join notice SHALL be generation-scoped, persist immediately after successful new-generation join without waiting for history, and consume only Runtime private join provenance. Reconnect/recovery/host replacement SHALL not create a candidate; later true return SHALL use a later stable generation event time and produce a distinct notice. All SystemNotice records SHALL remain observer-local and SHALL never enter ChatMessage history. + +#### Scenario: Chrome Offscreen mounts with background-owned durability + +- **GIVEN** a Chrome MV3 Offscreen document does not expose `browser.storage.session` while the background Service Worker does +- **WHEN** the shared Runtime host mounts and Session loads or saves a presence record +- **THEN** the Offscreen host SHALL remain available, the request SHALL use the private background PresenceStore adapter, and the exact session-backed record or persistence rejection SHALL return without a volatile fallback or page relay + +#### Scenario: Unauthorized extension contexts cannot access PresenceStore + +- **GIVEN** content and options contexts know the deterministic Port name and comctx namespace +- **WHEN** either context opens the named Port and sends a valid `load` or `save` injector envelope +- **THEN** Background SHALL reject the transport source before comctx dispatch, return no lifecycle record, perform no storage write, and leave the durable bytes unchanged + +#### Scenario: Forged provider response cannot reach Offscreen + +- **WHEN** a content, options, or other extension context broadcasts a provider-shaped response with the exact namespace and request id +- **THEN** the Offscreen PresenceStore injector SHALL receive nothing because it listens only to its background-owned point-to-point Port + +#### Scenario: Service Worker recreation preserves the private route + +- **WHEN** the accepted Port disconnects with a request in flight and the Service Worker provider is later recreated +- **THEN** every request owned by that Port SHALL reject through the existing request-local fence without replay, and only the next new `load`/`save` SHALL reconnect to the authenticated provider and use the retained session-storage record + +#### Scenario: Old provider completion cannot cross into a replacement Port + +- **GIVEN** an authenticated request arrived through one Port and that binding detached before its provider operation completed +- **WHEN** a replacement authenticated Port becomes active and the old operation later produces its response +- **THEN** Background SHALL drop the old response because its original request binding no longer exists; the replacement Port SHALL receive nothing and Session SHALL not advance from that completion + +#### Scenario: Terminal send failure settles every binding-owned request + +- **GIVEN** one or more Offscreen PresenceStore requests are pending on the same Port +- **WHEN** a later send on that Port throws a disconnected-Port error before the asynchronous disconnect event is observed +- **THEN** every request owned by that terminal binding SHALL reject without timeout, the operation that was already accepted SHALL not be replayed, and only a later new request may create a new Port and correlation + +#### Scenario: Pre-send request cannot migrate generations + +- **GIVEN** comctx generated request IDs and registered one or more one-shot response callbacks against a live generation, but their `apply` messages have not entered the pending registry +- **WHEN** that generation disconnects before those messages can be posted +- **THEN** every prepared call SHALL reject through its original generation, no replacement Port SHALL receive any original `load`/`save` bytes, and only a later application call with a new request ID may connect and settle + +#### Scenario: Terminal operations release adapter-owned response entries + +- **GIVEN** one-shot `load` or `save` operations registered response callbacks before or after pending insertion on one generation +- **WHEN** connect, post, disconnect, termination, or disposal makes that generation terminal +- **THEN** every affected operation SHALL reject, every adapter-owned response entry and reservation SHALL return to baseline, stale or late responses SHALL settle nothing, no original bytes SHALL migrate to a replacement, and a later fresh request SHALL use a new correlation + +#### Scenario: Dispose settles a prepared first call + +- **GIVEN** a one-shot call registered its response callback while no Port was open and its `apply` has not started +- **WHEN** the Offscreen adapter is disposed +- **THEN** the call SHALL reject locally, its adapter-owned response entry SHALL be released, no Port SHALL open, and no durable PresenceStore operation SHALL be posted + +#### Scenario: Firefox uses the equivalent direct store + +- **WHEN** Firefox MV2 mounts the shared Runtime in its persistent Background Page +- **THEN** host assembly SHALL pass the concrete session-backed PresenceStore directly and preserve the same strict records, rejection semantics, and supported replacement behavior + +#### Scenario: Six-timepoint A/B/C/D lifecycle + +- **GIVEN** independent actual Runtime Server/Session/Wire stacks use deterministic in-repo transport, A is an existing observer, B is a new local user, C is an additional physical session for B's generation, and D is B's replacement Runtime host +- **WHEN** the control executes preparation baseline, B first join, duplicate/C publication, transient B loss/D recovery, D final release, and B later return +- **THEN** B and A SHALL each persist one join for the first logical transition; duplicate/C/loss/recovery SHALL add no notice; A SHALL persist one leave on final end; and later return SHALL persist one fresh self join plus one fresh observer join + +#### Scenario: Physical loss remains provisional + +- **WHEN** a bound peer leaves transport without a valid final generation end and later republishes the same generation from reconnect, recovery, host replacement, or rejected-final-end replacement recovery +- **THEN** Session SHALL publish snapshots only and preserve the logical observer state without a leave/join pair + +#### Scenario: Duplicate and late lifecycle facts + +- **WHEN** SESSION or SESSION_END is duplicated, or an ended generation's SESSION arrives late +- **THEN** Session SHALL apply the accepted generation/end at most once, reject resurrection of the ended generation, and persist no duplicate notice + +### Requirement: Invalid records are isolated at send, receive, and retained-load boundaries + +Every uncontrolled record boundary SHALL use the existing strict Valibot schemas and `safeParse`. Invalid inbound Runtime records SHALL be rejected before persistence, barrage, or primary-list publication; the adapter SHALL diagnose and ACK only that invalid sequence so later valid delivery continues. MessageStore queries SHALL decode retained physical rows independently: valid rows remain queryable, invalid raw rows remain physically untouched, and bounded deduplicated private diagnostics use the existing conflicts store without invalidating canonical-record watchers. A diagnostic-write failure SHALL not hide valid results. No Zod schema, database version change, MessageStore method, or public failure channel SHALL be added. + +#### Scenario: Valid inbound event beside invalid retained data + +- **GIVEN** one unsupported retained row remains in the physical records store +- **WHEN** a valid remote live event is accepted +- **THEN** the valid event SHALL persist, emit once to realtime consumers, and appear in the primary list; the invalid row SHALL remain physical with one bounded diagnostic and SHALL not fail the canonical query + +#### Scenario: Invalid inbound does not poison the lane + +- **WHEN** one Runtime inbound record fails strict MessageRecord validation and a later valid event follows +- **THEN** the invalid event SHALL produce request-local diagnostics and no persistence/barrage/list publication, its sequence SHALL be discarded through ACK, and the later valid event SHALL settle normally + +### Requirement: Adjacent SystemNotice grouping is UI-only and history-responsive + +After the complete latest application projection is canonically sorted by event `(hlc,id)`, the UI SHALL group each maximal adjacent run of SystemNotice messages. A singleton SHALL render unchanged. A run of two or more SHALL initially render the latest notice and an icon expand/collapse control without a numeric count; expansion and collapse SHALL reveal or hide the earlier notices in canonical order through a height/opacity transition, while reduced-motion preference SHALL remove the transition without changing content. Any non-notice message SHALL split groups. The transform SHALL not alter, delete, merge, or persist canonical records, and lifecycle terminology SHALL not appear in the UI. + +Each grouped row SHALL derive one stable UI identity from its first canonical notice's persistent ID. Extending the same run SHALL preserve that identity. Before React and Virtuoso receive a row, text SHALL project as `message:`, singleton notice as `single-notice:`, and grouped notice as `notice-group:`. These row-kind namespaces SHALL remain structurally disjoint for every wire-valid opaque ID, including IDs that begin with another row kind's namespace. Every row SHALL pass that same projected identity to Virtuoso as the item key. Raw ID alone, array position, first/last presentation flags, and expand/collapse state SHALL NOT participate in row identity. + +Streaming history MAY insert Chat messages before, after, or between existing SystemNotice messages according to canonical event time. The UI SHALL recompute grouping from the new sorted projection so late history can create, split, or reposition a group without changing observer-local notice ownership or synchronizing notices through peer history. + +#### Scenario: Non-notice splits a run + +- **WHEN** two adjacent notices are followed by a Chat message and then two more adjacent notices +- **THEN** the UI SHALL render two independent collapsible groups separated by the unchanged Chat message + +#### Scenario: Late history reprojects grouping + +- **WHEN** streaming history inserts a canonically ordered Chat message between notices that were previously adjacent +- **THEN** the next UI projection SHALL split the prior group while every canonical notice and Chat record remains unchanged + +#### Scenario: Virtualized grouped-row identity remains stable + +- **WHEN** a two-notice group renders, its expand/collapse state changes, another notice extends the same run, or late history splits that run +- **THEN** Virtuoso SHALL receive a defined persistent key for every rendered row; expansion and extension SHALL not remount the original group, and split rows SHALL have distinct non-index identities + +#### Scenario: Count-free animated notice disclosure + +- **WHEN** a grouped notice row is expanded or collapsed +- **THEN** earlier notices SHALL enter or leave in canonical order through a smooth height/opacity transition, the latest notice SHALL remain the control anchor, reduced-motion preference SHALL remove the transition, and no numeric group count SHALL render + +#### Scenario: Opaque IDs cannot impersonate another row kind + +- **WHEN** text or notice IDs begin with `message:`, `single-notice:`, or `notice-group:` and late history creates or splits an adjacent-notice group +- **THEN** every React and Virtuoso identity SHALL retain its actual row-kind namespace, remain unique across the projection, preserve the original group through expansion or extension, and never transfer DOM, measurement, or scroll identity to another row kind + +### Requirement: Domain-scoped manual reconnect + +The actions menu SHALL include "Reconnect this site", which SHALL rebuild only the current domain's ChatRoom connection and re-publish that domain's presence. Because the frozen `ChatRoom` has no `reconnect` method, the application Domain command SHALL use the exact public `leaveRoom()` and retained `JoinRoomCommand` with `joinRoom(command)` rather than extending the extern. Availability SHALL be exactly `joined && !reconnecting` in Chrome and Firefox; `panelOpen` SHALL NOT participate. One enabled activation SHALL create one authoritative request identity and pending fact, immediately invoke the leave/join composition, preserve the main panel's current open/closed state, disable the Refresh control, and spin that control's own icon. The button SHALL expose no Ready text, success region, result badge, or second terminal state: it SHALL return to its ordinary eligible icon when the matching request terminal settles. Every callback SHALL validate the request identity so stale work cannot clear a newer request's spin or feedback. Toast subscription, mount, paint, dwell, failure, unmount, or absence SHALL NOT delay, cancel, reject, or redefine reconnect dispatch or the independently captured network operation outcome. A visibly painted request-correlated generic loading Toast SHALL contribute the accepted minimum 300ms dwell; feedback absence, unmount, or presentation failure SHALL settle boundedly and SHALL NOT strand the button or duplicate gate. This pending lifetime SHALL reject duplicates only and SHALL NOT become Runtime or network authority. + +The application SHALL expose one generic Toast feedback capability, not a reconnect-owned presenter. Reconnect, Runtime readiness, and unrelated application notifications SHALL publish through the existing generic Toast APIs and share the original direct `AppMain -> ` surface. `src/app/content/components/reconnect-toast.tsx`, `useReconnectToast`, reconnect-specific presenter/renderer naming, and any independent reconnect Toast lifecycle truth SHALL be removed. Any presentation adapter SHALL consume only generic Toast descriptors, stable IDs, and presentation acknowledgements; it SHALL NOT directly import or own ChatRoom, Readiness, Runtime, network, or panel behavior. Business Domain effects MAY map source events to generic Toast entries, but the reconnect request SHALL remain the only owner of operation outcome, duplicate rejection, button pending state, and stale-request fencing. Generic Toast state SHALL NOT become a second reconnect pending or network truth. + +The Toaster SHALL remain a direct `AppMain` child using the existing AppMain Motion translate containing mechanism. There SHALL be no wrapper, reconnect-specific Toaster, always-mounted launcher layer, host-page global overlay, second renderer, or source-specific Toast restyling. The direct Toaster SHALL retain `richColors`, current `themeMode`, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and `dark:bg-slate-950 border dark:border-slate-600` Toast classes. No source SHALL add custom geometry, width, content-fit, placement, pointer, opacity-tracking, pseudo-element, or eligibility styles. While mounted, generic entries MAY present reconnect loading/result, Runtime connecting loading, Runtime unavailable error, and unrelated feedback through that same surface. Normal Runtime ready SHALL NOT publish a success descriptor. One reconnect request SHALL correlate only its own generic Toast ID for visible-paint acknowledgement, the accepted 300ms minimum loading dwell, terminal update, and cleanup. Absence, unmount, or presentation failure SHALL settle boundedly without delaying bootstrap, Runtime, reconnect, or the matching button. Opening during an active reconnect MAY present only its current pending entry; terminal reconnect SHALL NOT replay later. Cleanup SHALL address only the correlated ID, SHALL NOT use unscoped/global dismissal, and SHALL preserve unrelated Toasts. + +`App.tsx` SHALL NOT render a fixed readiness output, and `content/index.tsx` SHALL NOT render a pre-App loading, unavailable, Retry, status, or result fallback when client bootstrap fails. Runtime lifecycle SHALL retain its immediate-replay `connecting | ready | unavailable` state for recovery and send preconditions; only the independent presentation authority is removed. ReadinessDomain SHALL be the sole application readiness-transition authority. Its State-setting Command SHALL compare every mapped extern input to current State: equal input SHALL perform no State write and emit no `StateChangedEvent`, while a different input SHALL update State and emit exactly one Event. The current Query SHALL remain immediately available independently of transition Events. No duplicate filter or second readiness State SHALL be added to ClientLease, the Readiness implementation adapter, AppFeedback, or Toast presentation. + +The ClientLease watchdog SHALL retain its existing five-second cadence, page lease renewal before the Coordinator's 15-second TTL, host availability probe, generation/host-id/page-attachment comparison, and real replacement/loss recovery. Equal healthy host-phase callbacks MAY still reach the Readiness extern boundary, but after mapping they SHALL NOT become application transitions. This heartbeat SHALL remain liveness infrastructure rather than proof of a new readiness fact. + +While AppMain/Toaster exists, normal Runtime `connecting` MAY publish the stable loading entry and `unavailable` MAY publish the stable error entry. A genuine transition to `ready`, or an immediate mounted-surface ready Query, SHALL issue only an ID-scoped dismissal for `webchat-runtime-readiness` if present; it SHALL NOT publish `Ready to chat` or any other success descriptor. Periodic equal-state ready health samples SHALL produce neither a `StateChangedEvent` nor any AppFeedback/Toast command. Toast dismissal, automatic close, acknowledgement, and presentation settlement SHALL NOT publish another readiness descriptor or create a loop. These rules SHALL NOT change recovery or send preconditions or alter the independently request-correlated reconnect success result. If Toast cannot render because App/Toaster is absent, the application SHALL NOT create an alternate loading, unavailable, Retry, status, or result view. Presentation absence SHALL NOT redefine bootstrap, Runtime, or network truth. Reconnect SHALL NOT rebuild the shared WorldRoom; the Runtime SHALL auto-reconnect WorldRoom only on its own connection failure. The Options page SHALL NOT gain a global reconnect entry. Owner task #228 supersedes the Runtime ready success mapping and equal-state Readiness transition behavior at round-27 exact `f4d9c401...`; prior source and evidence SHALL NOT certify the replacement. + +#### Scenario: Manual domain reconnect + +- **WHEN** a user activates "Reconnect this site" on one domain +- **THEN** only that domain's ChatRoom connection and presence SHALL be rebuilt, and other domains and the WorldRoom SHALL be undisturbed + +#### Scenario: Button and generic Toast entry share one reconnect request + +- **GIVEN** `AppMain` and its original Toaster are mounted +- **WHEN** an enabled user activates reconnect and the current-domain leave/join composition succeeds or fails +- **THEN** one request identity SHALL immediately invoke the composition, disable and spin the Refresh button, and correlate one generic loading-to-ready/success-or-failure Toast entry; the bounded request terminal SHALL stop only the matching spin, and neither the generic Toast layer nor the button SHALL create a second reconnect state owner + +#### Scenario: Reconnect does not wait for Toast presentation + +- **GIVEN** the Toast library defers its subscriber update or fails to render loading +- **WHEN** an enabled user activates reconnect +- **THEN** the leave/join ports SHALL be invoked immediately, the Refresh icon SHALL represent the same pending request, no Toast state SHALL delay or alter that operation, and a bounded presentation-failure outcome SHALL allow the shared request terminal rather than strand the icon or duplicate gate + +#### Scenario: Fast terminal reconnect respects mounted feedback + +- **GIVEN** `AppMain` remains mounted and the leave/join ports settle before the request-owned loading Toast receives its first visible paint +- **WHEN** the operation outcome reaches the shared request +- **THEN** the operation outcome SHALL be captured without waiting for feedback, while the matching Refresh icon and loading Toast SHALL remain tied to that request until the visible Toast completes its 300ms minimum dwell and transitions to the matching terminal Toast result + +#### Scenario: Stale terminal work cannot clear a newer request + +- **GIVEN** one reconnect has settled and a later reconnect owns a newer request identity +- **WHEN** delayed Toast paint, dwell, or terminal cleanup from the older request completes +- **THEN** it SHALL NOT stop the newer Refresh spin, dismiss the newer loading Toast, emit a newer error, or alter the newer reconnect operation + +#### Scenario: Original AppMain Toaster structure and visuals are preserved + +- **WHEN** the main panel renders application feedback +- **THEN** `AppMain` SHALL contain the direct original Toaster with `richColors`, current theme, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and the existing dark Toast classes, without an added wrapper, launcher layer, reconnect-specific Toaster component, or custom geometry/pointer styling + +#### Scenario: Generic Toast surface carries independent business sources + +- **GIVEN** unrelated application Toast feedback exists before or during reconnect or a Runtime readiness transition +- **WHEN** reconnect or Runtime status publishes feedback +- **THEN** all sources SHALL use the same generic Toaster and generic descriptor contract, reconnect/Readiness SHALL own no presenter or renderer, source-local cleanup SHALL preserve unrelated entries, and no unscoped dismissal SHALL occur + +#### Scenario: Readiness emits only actual transitions + +- **GIVEN** Readiness State already equals the mapped extern input +- **WHEN** the five-second heartbeat or any other source repeats that same `connecting`, `ready`, or `unavailable` value +- **THEN** Readiness SHALL perform no State write, emit no `StateChangedEvent`, trigger no ChatRoom recovery, and publish or dismiss no Toast + +#### Scenario: Real readiness transitions remain observable once + +- **GIVEN** Readiness currently exposes one state through its Query +- **WHEN** the mapped extern input changes to a different `connecting`, `ready`, or `unavailable` value +- **THEN** Readiness SHALL update its State and emit exactly one matching `StateChangedEvent`, while current-state Query/replay remains available without manufacturing a duplicate transition + +#### Scenario: Runtime watchdog retains lease and recovery behavior + +- **GIVEN** a page lease requires renewal and the shared Runtime host may be lost or replaced without a reliable terminal event +- **WHEN** the ClientLease watchdog runs +- **THEN** it SHALL retain the five-second lease/probe/snapshot checks and initiate recovery only for a real unavailable, generation, host-id, or page-attachment change; Readiness transition dedup SHALL NOT disable or delay that liveness behavior + +#### Scenario: Runtime ready dismisses loading without success replay + +- **GIVEN** the Runtime readiness Toast uses stable ID `webchat-runtime-readiness` +- **WHEN** readiness genuinely enters ready or a newly mounted Toast surface reads the current ready Query +- **THEN** application feedback SHALL only dismiss that ID if present, SHALL NOT publish `Ready to chat` or another success descriptor, and SHALL NOT dismiss unrelated Toasts + +#### Scenario: Runtime ready settlement cannot form a presentation loop + +- **GIVEN** the Runtime readiness loading entry was dismissed or a prior Toast automatically closed, acknowledged, or settled +- **WHEN** readiness remains ready without a later connecting or unavailable transition +- **THEN** no Toast lifecycle fact SHALL republish the ready success descriptor, while Runtime recovery, immediate-replay readiness truth, and manual reconnect feedback SHALL remain unchanged + +#### Scenario: No independent readiness or bootstrap status view + +- **WHEN** Runtime is connecting or unavailable, or client bootstrap fails before App/Toaster exists +- **THEN** `App.tsx` and `content/index.tsx` SHALL render no fixed loading, unavailable, Retry, status, or result view; mounted normal-runtime feedback MAY use generic Toast, while absent Toast presentation SHALL create no replacement UI and SHALL NOT become bootstrap or Runtime authority + +#### Scenario: Closed-panel reconnect has no Toast prerequisite + +- **GIVEN** the main panel is closed and `AppMain` plus Toaster are unmounted +- **WHEN** an enabled user activates reconnect +- **THEN** the panel SHALL remain closed, the leave/join composition SHALL start immediately, the matching Refresh icon SHALL remain disabled and spinning while pending, and Toast absence SHALL neither queue nor strand the operation + +#### Scenario: Ready and result feedback use generic Toast only + +- **GIVEN** `AppMain` and Toaster are mounted for the reconnect request +- **WHEN** that request captures success/readiness or failure +- **THEN** the request-correlated generic Toast entry SHALL present the matching terminal result, while the Refresh button SHALL expose no Ready text, success region, error region, result badge, or second result state + +#### Scenario: Active request may enter a newly mounted Toaster once + +- **GIVEN** reconnect began while the panel was closed and the same request remains pending +- **WHEN** the user opens the panel +- **THEN** the original generic Toaster MAY present that request-correlated loading-to-terminal entry once, SHALL NOT restart reconnect, and SHALL NOT replay any request that had already terminated + +#### Scenario: Reconnect unavailable state is not a silent action + +- **GIVEN** the current domain cannot start the reconnect composition +- **WHEN** the actions menu renders "Reconnect this site" +- **THEN** the action SHALL be visibly disabled, SHALL NOT dispatch activation, and SHALL NOT accept a click that silently produces neither feedback nor an operation + +#### Scenario: Panel state changes only Toast availability + +- **WHEN** the main plugin panel opens or closes during an active reconnect +- **THEN** the same operation and matching button spin SHALL continue without cancellation, replay, restart, duplication, or panel mutation; opening MAY mount the request-correlated generic Toast entry once, while closing MAY unmount it and SHALL settle feedback absence boundedly + +#### Scenario: Reconnect cleanup is request-local + +- **GIVEN** unrelated Toast feedback exists before or during reconnect +- **WHEN** reconnect succeeds, fails, or the main panel changes state +- **THEN** cleanup SHALL address only the generic Toast ID correlated to the matching reconnect request, SHALL NOT invoke an unscoped/global dismissal, SHALL preserve all unrelated Toast feedback, and SHALL NOT affect a newer request + +#### Scenario: WorldRoom self-recovery + +- **WHEN** the WorldRoom connection itself fails +- **THEN** the Runtime SHALL reconnect it automatically without requiring the domain reconnect action + +### Requirement: One-shot migration without dual architecture + +The change SHALL be delivered as one candidate that includes the hosts, exact eight-method ChatRoom port, state-free Runtime client, clean-cut internal comctx surface, uniquely owned Lifecycle/Connection/Session/World/History/Delivery/Wire Domain graph, private RoomTransport Extern/provider composition, message delivery, reconnect entry, current v2 peer protocol with only the authorized logical-presence exception, exact typed Database extern/default adapters, internal concrete MessageStore, canonical outer-type/outer-id `MessageRecord` with `ChatMessageRecord.message` and `SystemNoticeRecord.notice`, send-first persistence, and complete removal of page-owned WebRTC, the v1 protocol, stateful ChatRoom authority, catch-all Network ownership, and old WireExtern/provider route. Persistence and Runtime authority SHALL be complete clean-cut structural replacements rather than minimal repairs; no compatibility wrapper, alias, dual path, dead facade, hidden state channel, provider leak, or test-only accommodation may retain an obsolete owner/record/Store/outbox architecture. No intermediate release SHALL ship both architectures. Existing local message history SHALL NOT be imported, migrated, or retained by the canonical database. + +#### Scenario: Single-candidate completeness + +- **WHEN** the release candidate is inspected +- **THEN** it SHALL contain the full Remesh DDD + CQRS Runtime architecture and SHALL NOT contain any active page-owned WebRTC path, v1 protocol path, stateful ChatRoom recovery authority, catch-all Network owner, old WireExtern route, or dual writable fact + +#### Scenario: No data migration + +- **WHEN** the extension upgrades with old unstorage message data present +- **THEN** the old data SHALL be left unread and unconverted, and no migration code, marker, or reaction conversion SHALL exist + +### Requirement: Verification and runtime acceptance boundary + +For the current task #208 replacement, candidate delivery SHALL follow exactly Coder -> Reviewer -> Owner. Coder SHALL implement, freeze one immutable exact, and report identity and scope without running or claiming automated, static, build, or browser gates. Fresh Reviewer SHALL inspect only code logic, Diff/scope, and architecture boundaries and SHALL run no automated, static, build, or browser tests. Only a Reviewer logic PASS SHALL release that exact to the Owner's local test environment. No QA task, QA gate, or QA verdict SHALL be created or required for this replacement; the Owner's local smoke SHALL be the sole current product-acceptance path. Existing candidate-sensitive structural tests SHALL remain capable of proving the owner matrix, CQRS semantics, acyclic Domain dependencies, exact eight-method ChatRoom port, state-free ChatRoom implementation, provider import boundary, private RoomTransport Extern, old-owner/old-WireExtern removal, truthful `Remesh.module` usage/naming, and synchronized architecture artifacts. The same provider contract suite SHALL remain applicable to deterministic fake and Artico, and protected-input controls SHALL retain byte-identity coverage for current peer protocol source/schemas/limits/codec goldens and canonical bytes against baseline `9c90bb0...`. Removing the QA route SHALL NOT mark any acceptance control complete. Historical QA records SHALL remain immutable facts for their own exacts and SHALL transfer no evidence to the replacement. Merge SHALL remain separately authorized. + +Contract tests for peer wire behavior SHALL import only from `@/protocol` or the documented public entry. Type-negative and unchanged-backend suites SHALL cover Database typing, transactions, cancellation, ordering, insertion, watch, close, and value isolation on IndexedDB and Memory. Application/page Domain/model tests SHALL cover exact outer-type MessageRecord decoding, all three item/record/payload id equalities, shared cross-variant id uniqueness, key/property-presence rejection, Chat user/message identity, internal MessageStore replay/conflict/query-default/query-type/query-abort/invalid-query/clear/watch, removal of its `list` API, send-first success/failure/loss behavior, exact causal returned-message projection under watch-order and same-content concurrency, projections, and LWW/order implementation. Headless Runtime tests SHALL cover each unique owner, provisional connection generations, committed sessions/HLC, World presence, history sessions/cursors/batches, delivery ACK/replay, Wire queues/trust/provider translation, and clean-cut internal RPC contracts. + +A dependency/export/residue scan SHALL prove that `src/protocol/**` has no reverse imports or local/UI/internal Runtime exports; Chat/UI do not import Runtime/provider/Database primitives or concrete adapters; MessageStore is not an extern/public export; Artico imports are provider/composition-only; and no removed Network owner, WireExtern route, ChatRoom cache, false Module name, status/outbox/retry, or legacy record alias remains. No agent SHALL treat `pnpm dev` or ad hoc browsing as release evidence. After Reviewer logic PASS, the same immutable exact SHALL be synchronized directly to the Owner's local test environment without an intervening QA route. The Owner determines the current product verdict. Candidate controls SHALL retain one request identity across immediate leave/join invocation, Refresh disabled/spinning pending state, mounted Toast loading-to-ready/success-or-failure flow, terminal cleanup, duplicate rejection, and stale-request fencing. They SHALL cover panel-open activation with the original direct `AppMain -> Toaster`, panel-closed activation with no Toaster and no operation delay, opening while the same request remains active, closing while feedback is present, fast success, fast rejection, presentation absence/failure, and long operation. Structural controls SHALL prove the original Toaster parameters remain, no wrapper or launcher feedback layer exists, Refresh adds no custom Toast geometry/pointer styling, and no separate Ready/result button area exists. Removing the QA route SHALL NOT mark these controls complete or transfer historical results. If rendered behavior is claimed from Owner-local smoke, it SHALL use the exact candidate and real controls; isolated Toaster mounting, direct synthetic `.click()`, or source call order alone SHALL NOT substitute for the claimed result. Commit, push, and PR update need no separate Owner authorization; PR merge and `master` release SHALL remain separate explicit Owner authorizations. + +#### Scenario: Reviewer logic PASS precedes Owner testing + +- **WHEN** Coder freezes a replacement exact +- **THEN** fresh Reviewer SHALL inspect code logic, Diff/scope, and architecture boundaries without running tests, and the Owner SHALL begin local product smoke only after that logic review passes + +#### Scenario: Task 208 replacement has no QA gate + +- **WHEN** Reviewer logic PASS releases the immutable replacement exact +- **THEN** the exact SHALL go directly to the Owner's local test environment without creating or requiring a QA task, QA gate, QA-run browser matrix, or QA verdict + +#### Scenario: Owner acceptance and release authorization + +- **WHEN** the Reviewer-passed exact is ready for product acceptance and release judgment +- **THEN** the Owner SHALL record the local product verdict, historical QA evidence SHALL NOT transfer to the replacement, and PR merge and `master` release SHALL remain separate explicit Owner authorizations diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/world-room-presence/spec.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/world-room-presence/spec.md new file mode 100644 index 00000000..9ef7a869 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/specs/world-room-presence/spec.md @@ -0,0 +1,97 @@ +## ADDED Requirements + +### Requirement: WorldRoom v2 is a browser singleton inside the Runtime + +The World wire types, schema, limits, and pure validation SHALL be defined by the public `src/protocol/index.ts` module. Runtime-only registry, projection, lifecycle, and transport orchestration SHALL remain outside that module; the dependency direction SHALL be Runtime → protocol. + +The system SHALL keep the WorldRoom as the cross-domain discovery index, joined exactly once per browser by the shared Runtime in a v2-namespaced room rather than once per page. `ConnectionDomain` SHALL uniquely own that physical WorldRoom membership/recovery generation, while `WorldDomain` uniquely owns its session, active-domain registry, and presence snapshots. While at least one supported domain page is online, Connection SHALL maintain one WorldRoom membership; after the last page disconnects, the membership SHALL follow the Lifecycle domain-released Event after the unified five-second grace before exit. + +#### Scenario: Single membership per browser + +- **WHEN** multiple pages across one or more domains are online in one browser +- **THEN** `ConnectionDomain` SHALL hold exactly one WorldRoom membership, not one per page or per domain + +#### Scenario: Grace-aligned exit + +- **WHEN** the last page of the browser disconnects +- **THEN** the WorldRoom membership SHALL exit only after the same unified 5-second grace used by domain lifecycles + +### Requirement: Presence is a full per-peer snapshot + +WorldRoom presence SHALL be published as exactly one `WorldRoomMessage` complete snapshot per source peer: `WorldRoomMessage extends ChatSession {sites: ChatSite[]}`, `ChatSession = {sessionId,user:ChatUser}`, and `ChatSite = {origin,title?,icon?,description?}`. It SHALL have no payload discriminator. The trusted World `roomId` SHALL select this strict parser. World and Chat SHALL use the same structures while maintaining distinct session instances and room protocols. `WorldDomain` SHALL maintain the browser's active-domain registry, publish a fresh complete snapshot whenever the registry changes, atomically replace a trusted source peer's whole record on each snapshot, and delete that record when the peer leaves. + +#### Scenario: Registry-driven publication + +- **WHEN** a domain joins or leaves the browser's active registry +- **THEN** `WorldDomain` SHALL publish one new complete `sites` snapshot reflecting the post-change registry + +#### Scenario: Atomic replace and delete + +- **WHEN** a receiver gets a snapshot from a source peer +- **THEN** it SHALL replace that peer's entire site set; when the peer leaves it SHALL delete the entire record, never producing ghosts + +#### Scenario: No partial-update artifacts + +- **WHEN** presence behavior is inspected +- **THEN** there SHALL be no payload `type`, composite keys, TTL expiry, World reconnect-republish cycles, withdraw message types, or list flicker by design + +### Requirement: Presence counts per domain per browser + +Presence SHALL be aggregated by domain: multiple pages of one browser on one domain SHALL count as exactly one online user for that domain. A user on domain A who has not joined domain B's ChatRoom SHALL still see domain B's online count in the sites list. + +#### Scenario: Multi-page dedup + +- **WHEN** one browser has several pages open on the same domain +- **THEN** its WorldRoom contribution for that domain SHALL count as exactly one online user + +#### Scenario: Cross-domain visibility + +- **WHEN** a user stays on domain A without joining domain B's ChatRoom +- **THEN** the user SHALL still see domain B's online count derived from WorldRoom presence + +#### Scenario: Grace-period counting stability + +- **WHEN** a domain enters its 5-second refresh grace period +- **THEN** it SHALL remain in the published `sites` snapshot during the window and SHALL be removed only after grace expiry, avoiding count flapping + +### Requirement: Presence site metadata is display-safe and privacy-bounded + +`ChatSite = {origin, title?, icon?, description?}`. `host` and `hostname` SHALL NOT be published because they are derivable from `origin`; raw `href` SHALL NOT be published because it can leak paths, queries, and tokens. Any future page-specific display SHALL use newly designed sanitized fields behind an explicit privacy decision. + +#### Scenario: No page-content disclosure + +- **WHEN** any presence snapshot is published +- **THEN** each site SHALL contain only `origin` and optional `title`, `icon`, and `description`, and SHALL NOT contain `host`, `hostname`, `href`, paths, or query strings + +### Requirement: WorldRoom self-recovery is independent + +`ConnectionDomain` SHALL reconnect the WorldRoom automatically when its physical connection fails, and `WorldDomain` SHALL republish the current full presence snapshot only after the new generation is accepted. The domain-scoped manual reconnect action SHALL NOT rebuild or interrupt the WorldRoom. + +#### Scenario: WorldRoom connection failure + +- **WHEN** the WorldRoom connection itself fails while pages remain online +- **THEN** `ConnectionDomain` SHALL re-establish it automatically and `WorldDomain` SHALL republish current presence without user action or duplicate ownership + +#### Scenario: Domain reconnect isolation + +- **WHEN** the user activates "Reconnect this site" on any domain +- **THEN** the WorldRoom membership and its presence SHALL continue unaffected + +### Requirement: World application port is projected and source-free + +`WorldRoomExtern` SHALL directly type-import the exact protocol `ChatSite` and `ChatUser` structures and SHALL expose exactly `getState(): Promise`, `onState((WorldState) => void): disposer`, and `onError((Error) => void): disposer`, where `WorldState = (ChatSite & {users: ChatUser[]})[]`. `WorldDomain` SHALL remain the unique owner of private source/site contributions and SHALL project the source-free `WorldState`; the application adapter SHALL subscribe before taking that projected upstream snapshot, so no update can fall into a getter/subscription gap, and SHALL NOT reconstruct or retain a second writable source multiset. The application port SHALL NOT expose peer/source identity, Runtime snapshot/presence types, synthetic join times, or routing fields. Each `(source, origin)` contribution in `WorldDomain` SHALL receive stable order at first appearance; updates SHALL not reorder it and new contributions SHALL append. Every active contribution SHALL retain one entry in that origin's `users`, including repeated entries for the same user id; origin groups and their users SHALL project in stable order, and every origin SHALL have exactly one group. Group `ChatSite` metadata SHALL always be the current ChatSite value of the first active contribution: a metadata-only update of that first contribution updates the metadata while it remains first, and only its leave or move to another origin promotes the next active contribution. World application consumers and non-runtime Domains SHALL have zero `@/runtime/**` imports. + +#### Scenario: Exact-origin projected multiset and metadata order + +- **WHEN** multiple source contributions describe one exact origin, including multiple contributions for the same user, and the first contribution updates metadata, leaves, or moves to another origin +- **THEN** the application World state SHALL expose one exact-origin group whose users and origin groups retain first-appearance stable order; the metadata SHALL use the first active contribution's current ChatSite value after a metadata-only update and SHALL promote the next active contribution only after that first contribution leaves or moves, without exposing the source identities used by the adapter + +#### Scenario: World subscription closes the snapshot gap + +- **WHEN** a World application consumer establishes both state observation and an initial state read while upstream presence changes +- **THEN** the adapter SHALL subscribe before its projected upstream snapshot and the consumer SHALL receive no missing state between `onState` and `getState`, without the adapter retaining private source contribution authority + +#### Scenario: World fake-port replacement + +- **WHEN** a fake World application port drives state updates and errors +- **THEN** a World application consumer SHALL operate using only the exact-origin projected state and exact public protocol value types, while `WorldDomain` alone retains Runtime snapshot/source handling diff --git a/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/tasks.md b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/tasks.md new file mode 100644 index 00000000..54072081 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-refactor-to-shared-webrtc-runtime/tasks.md @@ -0,0 +1,364 @@ +> **Completion status (2026-07-28):** The Owner stopped the remaining agent-run product matrix, moved the unexecuted scenarios to Owner-local verification, and ultimately accepted the final lineage ending at exact `5a7bb4a12c301bd4e51b4904253739e41091a4f8`. Fresh exact-bound Actions run `30298778315` passed independent `linter`, `tests`, and `build` checks, including Chrome MV3 and Firefox MV2 verification. PR #69 merged through `6627e8fc1b27aa7754e79ca1c40ae77321fd7fc0`; `develop` later received Owner commit `fff7da8e4e6f320a86d45670e108cd97451955e5` (`feat!: release WebChat 2.0`). Master publication then merged `develop` through `3cad760b9988041995234c4cee762c3ea5598b66`; semantic-release completed `v2.0.0` at `fcd1c372e107629c442751015d5e61847cecf09e` and created tag `v2.0.0`. These remain separate history points outside this archived change. In this historical ledger, a checked item means implemented, superseded by a later exact, or explicitly closed by the Owner. It does not retroactively turn a blocked exact, stopped gate, or unexecuted scenario into PASS. QA task #156 and Reviewer task #157 passed round-14 exact `b1fcb6687...`; task #158 remains administratively closed without a final product-QA verdict. + +## 1. Inventory And Provenance + +- [x] 1.1 Record base HEAD/branch/worktree status before implementation; do not disturb unrelated user changes. +- [x] 1.2 Inventory every page-owned WebRTC artifact: `Peer` singleton usage, `ChatRoom`/`WorldRoom` impls, connection/session/presence ownership per page lifecycle, and every consumer. +- [x] 1.3 Inventory every v1 protocol artifact: `src/protocol/**` schemas, the JSONR wire-codec adapter, reaction toggle, history upsert and HLC-only cursor paths, and their consumers. +- [x] 1.4 Verify the store can provide cross-tab atomic insert-if-absent on event ids (IndexedDB `add()` semantics via a mature library first, hand-rolled only if none fits). + +## 2. Headless Runtime Core + +- [x] 2.1 Create the headless Remesh Runtime core with no dependency on React, DOM, content-script globals, Offscreen structure, `chrome.*`/`browser.*`, Worker globals, or UI components; forbid `window/document` reads and UI-state operations. +- [x] 2.2 Define injectable externs: host lifecycle, transport, comctx port, clock, minimal runtime-state recovery, identity. +- [x] 2.3 Implement `LifecycleDomain` (host singleton, domain lease/ref-count, unified 5-second grace timer, single-flight create/destroy, per-domain serial reconnect) with observable state. +- [x] 2.4 Implement `DeliveryDomain` (per-domain event sequence, un-ACK buffer, idempotent completion) with observable state. +- [x] 2.5 Define the page-facing Command/Query/Event contracts; keep comctx contracts internal-only; dev-only `debug:'event'` via compile-time `import.meta.env.DEV`. +- [x] 2.6 Implement `NetworkDomain` as the sole Remesh owner of domain rooms, trusted sessions, World snapshots, central id/HLC, requester/provider history sessions, and routing; inject wire/codec, clock, identity, and comctx page-port externs; keep `Server.ts` as graph/comctx boundary only. + +## 3. Hosts And Coordinator + +- [x] 3.1 Add the Chrome/Edge Offscreen Document host entry (lifecycle-only adaptation injecting externs). +- [x] 3.2 Add the Firefox long-lived Background Page host entry using the same shared core. +- [x] 3.3 Implement the background sole host coordinator with single-flight creation and supported-host recovery while any domain page is online: Chrome's Service Worker retains authoritative page-lease/host-phase truth outside Offscreen and automatically rebuilds a destroyed host; Firefox directly owns and fully disposes/swaps one in-context `HostOwner`, while process restart relies on browser Background Page recreation and idempotent page reattach; failed creation surfaces explicit `connecting/ready/unavailable`, never page-owned fallback. +- [x] 3.4 Separate startup readiness waiting from steady-state heartbeat (no fixed-1s uncaught provider errors); page subscriptions are releasable and idempotent by pageId, with F5 attach disposing old callbacks and replaying un-ACKed events to the new page. +- [x] 3.5 Register host entries in `wxt.config.ts`/manifest for both browser targets. + +## 4. v2 Wire Protocol + +- [x] 4.1 Implement the transport extern delivering trusted `(roomId, sourcePeerId, rawPayload)`; remove all trust in payload-reported identity. +- [x] 4.2 Implement strict closed Chat union and strict World schema with v2 room namespaces, and the unified frame `base64(deflate(UTF8(JSON)))` via a replaceable codec extern (standard zlib `deflate`, native `CompressionStream`, only scoped `core-js` global-patch imports `core-js/actual/typed-array/from-base64` and `core-js/actual/typed-array/to-base64`, standard `Uint8Array.fromBase64/toBase64`, strict canonical re-encode equality); serialize encode→send per room and decode→validate→apply per source; bound per-source decode queues to ≤8 frames and ≤256KiB; eliminate the JSONR wire codec and its adapter. +- [x] 4.3 Implement the predecessor World presence shape. The final public names/structure are superseded by task 16: exact `WorldRoomMessage extends ChatSession {sites:ChatSite[]}`, no payload `type`, trusted World room parser, complete snapshots, and no `host/hostname/href`. +- [x] 4.4 Implement the predecessor Chat types. The final public names/structure are superseded by task 16: `MentionedUser extends ChatUser {ranges}` includes avatar; exact `TextMessage`, `ReactionMessage`, `ChatMessage`, and `ChatRoomMessage` apply. +- [x] 4.5 Implement HLC validation: non-negative safe integers, 5-minute future-skew rejection without clock advance, composite `(hlc, id)` ordering everywhere. +- [x] 4.6 Enforce limits: ≤64KiB wire bytes, ≤256KiB decompressed JSON with streaming pre-parse cancel, `MAX_CHAT_EVENT_BYTES = 48KiB`, `ChatUser` JSON ≤ 8KiB, array/string/nesting limits, history/session budgets counted in decoded durable event bytes; bound each domain's volatile durable-ACK buffer to 512 records/8MiB with atomic history-batch admission; unknown/oversized/malformed payloads are dropped per source with rate-limited logs and never reconnect the room. + +## 5. Store And Delivery + +- [x] 5.1 Convert the store to `LocalRecord = DurableEventRecord | SystemNoticeRecord`: key durable records by `event.id` via atomic `add` and system notices by deterministic notice id; first durable value wins; conflicts recorded without overwrite; no history upsert. +- [x] 5.2 Split projections: UI projections include independent `SystemNoticeRecord`; peer-history projections return only Text/Reaction; notice ids deterministic. +- [x] 5.3 Implement `DurableEventRecord = {event, user, receivedAt, status}` and `LocalRecord = DurableEventRecord | SystemNoticeRecord`; every durable record satisfies `user.id === event.userId`; `status: 'pending' | 'sent' | 'received'` means persisted-not-accepted, transport-accepted-only, or peer-event-persisted respectively; event kind is carried only by `event.type`; local metadata never reaches the wire; history responses derive `users[]` covering event users. +- [x] 5.4 Implement durable-first send: Runtime central clock allocates id/HLC; page persists `{event, status:'pending'}`; Runtime transmits; same-id retry on host death/partial send; `pending` is persisted but not transport-accepted, `sent` is transport-accepted-only, `received` is peer-event-persisted; Runtime-unavailable sends keep the draft with no fake sent record. +- [x] 5.5 Implement inbound delivery: stable-id events to all same-domain pages; atomic insert-if-absent; first insert only triggers side effects; ACK only after successful add or confirmed-exists, never on failure. +- [x] 5.6 Implement store watch for local tab-to-tab history synchronization; echo to other attached pages carries no buffer/ACK and is not a second persistence path. +- [x] 5.7 Freeze canonical store native schema ownership (final port name is `ChatRecordStore` under task 16): add private colocated `MESSAGE_STORE_VERSION = 2` as the name for the existing `openDB(..., 2)` schema authority, without itself advancing the schema or adding an upgrade; only a real compatible store/index/key/persisted-value migration increments it. Preserve `records`/`conflicts` and their diagnostics through ordered 0/1→2 transactional upgrades; remove package-version/`VERSION_STORAGE_KEY`-driven IndexedDB clearing without adding an unstorage migration marker, cleanup epoch, import, or conversion. Add fake-indexeddb coverage for initial create, existing-schema upgrade/preservation, repeated open/no clear, and canonical-record survival across app-version changes. + +## 6. History Sync + +- [x] 6.1 Implement `history-request`/`history-response` recent-first pagination over the 180-day candidate window (`HISTORY_WINDOW_DAYS = 180`, single constant truth, no obsolete-window wording): the requester freezes its own `requester wall clock - 180 days` cutoff at sync start, accepts remote response messages exactly at that cutoff and rejects only earlier remote response messages; at each provider supply/session admission the provider separately freezes its own `provider wall clock - 180 days` cutoff: local candidates exactly at that provider cutoff are eligible and only earlier local candidates are excluded without deletion; it retains that provider cutoff through local query/subsequent cursor/local page failover, while a dormant successor freezes its own provider cutoff at admission and retains it after promotion. The values are neither transmitted nor required to match; requester validation has final acceptance authority, so remote data cannot expand its window although clock skew can omit boundary candidates. Requester pagination/retry/provider-failover retains its cutoff without drift. Preserve flat `{syncId, users, messages, done}` responses; composite `(hlc, id)` cursor; `before/messages/done` pagination; <64KiB and ≤100 messages per response, one outstanding request per source, 10-second timeout keyed by domain/`syncId`/unique sync token, ≤8MiB or ≤10,000 messages per session from that window, keep-most-recent and stop without false `done`. +- [x] 6.2 Runtime selects one online local page as history supplier per request via explicit one-way `supplyId` request/cancel plus resolve/reject RPC; each query receives an `AbortSignal` wired to IndexedDB transaction abort, and failover/release/successor promotion waits for the page's physical-settlement confirmation. Runtime stores no history copy; one four-active/32-request/8KiB admitted pipeline covers supplier→encode→send→settle, with one bounded same-source replacement successor promoted only after the prior physical job settles; history application triggers no notifications/unread/prompts. + +## 7. Domain Migration + +- [x] 7.1 Migrate ChatRoom ownership into the Runtime: one connection per `(domain, remotePeer)` shared by all local pages; domains isolated; routing (targets, session binding, live event validation) inside the Runtime. +- [x] 7.2 Migrate WorldRoom into the Runtime: browser-singleton v2 membership, registry-driven full snapshots, grace-aligned exit, self-recovery on failure. +- [x] 7.3 Convert content pages to pure-UI comctx consumers (Command/Query/Event); remove all page-held connection/session/routing state; teardown only detaches lease. +- [x] 7.4 Implement snapshot consumption on attach/refresh within grace: no false offline/online, no presence flicker; replay un-ACKed events by sequence. +- [x] 7.5 Add the actions-menu "Reconnect this site" entry rebuilding only the current domain's ChatRoom; leave WorldRoom untouched; no Options global reconnect. + +## 8. Removal Of Old Architecture And Protocol + +- [x] 8.1 Delete every page-owned WebRTC creation path and the old per-page `Peer`/room ownership; prove zero remnants by search. +- [x] 8.2 Delete the v1 protocol: old `src/protocol` schemas, the JSONR wire-codec adapter, reaction toggle, history upsert, HLC-only cursor, and their consumers; clean unmerged dev residues. +- [x] 8.3 Prove the candidate contains no dual-architecture and no v1/v2 bridge path. + +## 9. Tests And Gates + +- [x] 9.1 Add fake-extern deterministic tests for the normalization head: canonical World/Chat schema and strict trusted-context rejection, durable `status` transitions, concurrent single-flight creation, unified 5-second release (preserving durable `status:'pending'` events), F5 resume from snapshot, duplicate ACK/idempotent delivery, Chrome Offscreen stale-provider and host-identity destruction with Service Worker-coordinator automatic rebuild, DOM-free MV3 adapter probing, Firefox deterministic `HostOwner` dispose/swap, failed-replacement unavailable state, **coordinator restart rebuilding lease/host-phase truth without duplicates**, domain reconnect isolation from WorldRoom, real page-cleanup chains (F5/two-tab close). +- [x] 9.2 Add protocol tests: v1/v2 namespace isolation, strict-union rejection of unknown/oversized payloads and forbidden envelope fields, scoped core-js canonical Base64 round-trip/rejection covering leading/embedded/trailing ASCII whitespace, missing or multiple padding, illegal and URL-safe alphabets, non-zero padding bits, and canonical padded round-trip, codec round-trip and ordering (serialized async pipeline, bounded per-source queues, malformed/truncated frames dropped source-locally), HLC skew rejection without clock advance, reaction LWW replay idempotence, history budget/stop behavior, snapshot replace/delete semantics, immediate A→B provider handoff, unique requester/provider timeout tokens, cross-domain/provider/source isolation, single-send replacement successor continuation plus peer/domain/timeout invalidation, one supplier→send concurrency pool, cleanup-stable count/byte admission, and one-way page-port success/failure/cancel/dispose settlement, IndexedDB transaction abort, and 5.001-second stepped-clock proof that failover/successor waits for physical page settlement. +- [x] 9.3 Add send-reliability tests: Runtime-unavailable draft preservation, same-id retry after host death, no fake sent records, and exact `pending`/`sent`/`received` status semantics. +- [x] **Closed or superseded:** 9.4 Run `lint`, `check`, Chrome and Firefox `build`, and OpenSpec status/strict validation/doctor; all pass with raw evidence on the normalization exact; record actual Chrome/Firefox references to only the two scoped core-js imports, size diff, and no unrelated polyfill residue; run CI green on the PR head. +- [x] **Closed or superseded:** 9.5 Freeze the normalization exact (SHA/tree/parent) with a handoff covering architecture mapping, protocol contract, canonical Base64 evidence, test evidence, and known limitations; no `pnpm dev`, no manual agent browsing. + +## 10. Acceptance + +- [x] **Closed or superseded:** 10.1 Automated real-extension acceptance gates pass on the exact candidate, executed by QA with preserved evidence (build artifact identity, consoles/logs, traces, and cleanup checks): + - Chrome MV3: Playwright bundled Chromium with the production unpacked build, covering startup/readiness, same-domain dual-tab connection sharing, F5 continuity, last-tab 5-second release, WorldRoom cross-domain counts, v1/v2 isolation, direct Offscreen destruction while the Service Worker coordinator retains an active page lease, automatic host rebuild, domain manual reconnect isolation, and send reliability under Runtime restart. + - Firefox MV2: Selenium + geckodriver `installAddon` with a test-owned Firefox process and the production build, covering startup/readiness, same-domain dual-tab sharing, F5 continuity, last-tab release, WorldRoom cross-domain counts, v1/v2 isolation, termination/restart of that Firefox process with the same isolated profile and restored target tab, exactly one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection. If Firefox removes a temporary XPI at process exit, the harness MAY reinstall the same exact XPI only as setup; profile and tab continuity SHALL be asserted separately, and this SHALL NOT be reported as product auto-reinstallation. Direct `backgroundView.close()` is negative platform-boundary evidence rather than a product failure; event pages, reload watchdogs, and business fallbacks are out of scope. Deterministic `HostOwner` dispose/swap coverage remains required. + - Every test run SHALL use an isolated temporary profile with tracked process handles, close contexts/browsers/drivers in `finally`, delete the temporary profile, and attach a no-residual-process check; global process kills are forbidden. +- [x] **Closed or superseded:** 10.2 Branded Chrome/Edge smoke: only at release-candidate stage, the Owner performs one minimal manual install-and-start smoke on branded Chrome and/or Edge (unpacked side-load is not automatable on branded builds). +- [x] **Closed or superseded:** 10.3 Owner explicitly authorizes PR merge; merge to `master` (semantic-release publish) remains a separate explicit authorization. + +## 11. Superseded Protocol Normalization Head + +These tasks record the predecessor normalization work that produced `3fc528f...`; that exact is now rejected for final acceptance by the Owner's public protocol boundary blocker. Its Reviewer/QA/browser evidence is historical only and does not transfer. The new boundary-repair tasks below are the active contract and deliberately remain unchecked. + +- [x] 11.1 Prepare and validate the updated `proposal -> specs -> design -> tasks` contract: canonical World payload without `type`, Chat `userId`, `history-request/history-response`, shared `ChatSession`, strict trusted-context rejection, canonical Base64 decode/re-encode, LocalRecord union/status semantics, no compatibility or migration; run the full change-directory old-term scan and strict OpenSpec validation/doctor/status. Final contract freeze occurs only after PM review. +- [x] 11.2 Implement the pending normalization items in Sections 4.2-4.4, 5.1-5.4, and 6.1 on a new exact; this is the only implementation-routing stage and includes the two scoped core-js global-patch Base64 imports. +- [x] 11.3 Preserve and update necessary comments on the new exact for trusted transport, codec/schema/Network validation, durable-first/ACK/LWW, history pagination/cancellation/budgets, status semantics, and World/Chat session lifecycles; reject line-by-line narration. +- [x] 11.4 Add or revise deterministic tests in Sections 9.1-9.3, including canonical malformed-Base64 rejection, bundle/static residue checks, strict forbidden-envelope rejection, record/status semantics, and no-compatibility/no-migration paths. +- [x] **Closed or superseded:** 11.5 On the new exact, run the full-directory contract/old-term scan and independent Reviewer static review across schema, types, functions, tests, comments, and documentation; require naming symmetry for paired concepts and zero stale canonical-term violations. +- [x] **Closed or superseded:** 11.6 On the same new exact, run `lint`, `check`, Chrome and Firefox `build`, OpenSpec strict validation/doctor/status, CI, and QA's full 10.1 Chrome MV3 + Firefox MV2 acceptance with preserved evidence; only then permit QC/Owner merge and release gates. + +## 12. Public Protocol Boundary Repair + +These tasks are the active post-review contract. They require a new immutable replacement exact; no task below is complete until its implementation and exact-bound evidence exist. + +- [x] 12.1 Freeze `src/protocol/index.ts` as the third-party-facing public peer module: export only wire types/interfaces/discriminants, strict schemas, pure parse/check/validation, public limits/constants, and `WireCodec`/`NativeWireCodec` reference implementation/`WireCodecError`; do not add a package, publishing flow, or SDK. +- [x] 12.2 Keep protocol validation and codec algorithms bounded and deterministic: preserve fixed `base64(deflate(UTF8(JSON)))`, canonical Base64, fatal UTF-8, bounded `readLimited`, explicit public constants `MAX_WIRE_BYTES = 64KiB`, `MAX_DECODED_JSON_BYTES = 256KiB`, `MAX_CHAT_EVENT_BYTES = 48KiB`, `MAX_USER_BYTES = 8KiB`, and history responses capped at 100 messages and strictly below `MAX_WIRE_BYTES`; preserve necessary shape/HLC/mention/history-reference validation and explicit `now` injection; remove hidden `Date.now()` calls and any browser-extension/host/app configuration dependency while allowing the standard Web codec APIs used by `NativeWireCodec`. Add resource-bomb tests for decompressed JSON, ChatUser JSON, message, and history-response boundaries. +- [x] 12.3 Move `LocalRecord`, `DurableEventRecord`, `RecordStatus`, `SystemNoticeRecord`, local `TextMessage`/`LocalMessage`, record getters/type guards, and all `MessageProjection` logic out of `src/protocol` into application/page Domain/model owners with the origin store; preserve the exact `LocalRecord = DurableEventRecord | SystemNoticeRecord` union, durable `{event,user,receivedAt,status}` shape, `user.id === event.userId`, no outer duplicate type, exact `pending|sent|received` meanings, deterministic local notice ids, local metadata/system notices excluded from wire/history, atomic first-value-wins/conflict-no-overwrite, durable-first persistence, same-id retry, and no history upsert across the application/page Domain and headless Runtime boundary. +- [x] 12.4 Move `compareHLC`/`compareEventPosition` implementations to the application/page Domain/model layer, or to a shared Domain/model module when both pages and Runtime consume them; keep only the public canonical `(hlc, id)` ordering rule in protocol. Move `WirePipeline`, per-room send tails, per-source decode queues, apply/drop/flush behavior, concurrency/resource scheduling, network/history orchestration, and all internal page-host Runtime RPC contracts to headless Runtime-owned modules. Preserve Runtime Chat session-first binding, same-session user immutability, live `userId` checks, one-shot fresh history on new incarnation, future-HLC rejection without central-clock advance, and history timeout/physical-settlement/successor invariants. +- [x] 12.5 Enforce one-way dependencies and exports: `src/protocol/**` SHALL NOT depend on `domain/runtime/service/app/UI/storage/comctx/browser-extension APIs/globals`, DOM/window/document, host lifecycle APIs, or app configuration; standard Web codec APIs used by `NativeWireCodec` and the two scoped `core-js` imports are allowed. Protocol-owned limits/byte tools SHALL not come through app config/utils; `index.ts` SHALL export no local/UI/internal Runtime/Pipeline symbol. +- [x] 12.6 Add public-entry contract tests importing only from `@/protocol` for Chat/World schemas, public limit constants, codec round-trip/canonical malformed/resource-bomb rejection (`64KiB` frame, `256KiB` decompressed JSON, `48KiB` event, `8KiB` ChatUser, `<64KiB` history response, ≤100 messages), explicit-now HLC validation, accepted extra history users, duplicate-user rejection, and export visibility. Add application/page Domain/model tests for exact LocalRecord/status/locality semantics, origin-store atomic first-value-wins/conflict behavior, normative send preparation (visible draft ≤500 characters, image about 30KiB Markdown data URL, reject only event encoding `>48KiB`), projection/LWW/order, and record helpers. Add headless Runtime/adapter tests for Chat session/incarnation, future-HLC clock preservation, separately requester-owned and provider-owned 180-day cutoffs: requester remote-response equality acceptance/earlier-event rejection and final acceptance authority without drift through requester pagination/retry/provider-failover; provider local-candidate equality acceptance/only-earlier non-destructive exclusion at provider freeze on supply/session admission, then retain through local query/subsequent cursor/local page failover; dormant successor freeze at its own admission and retain after promotion; assert the values are not transmitted and need not match, and clock skew can omit but cannot expand the requester window, 5-second page supply cancellation with AbortSignal-gated physical query/projection/filter/sort settlement, per-source decode queue/drop bounds, volatile ACK buffer atomic admission (512/8MiB), history budgets/timeouts, dormant successor automatic promotion without peer resend while counted in global 32-request/8KiB admission, final-send slot release and stale-token isolation, and internal RPC contracts. +- [x] 12.7 Add dependency/export/residue scans, move affected tests, update comments and Archify canonical JSON + generated HTML to show public protocol/reference codec, internal Wire adapter/Pipeline, internal Runtime contracts, application/page Domain origin-store and projection/ordering ownership, and the dependency direction; prove regeneration is byte-identical. +- [x] **Closed or superseded:** 12.8 Run full typecheck/format/lint/tests, dual builds/bundle probes/audit/OpenSpec/Archify/CI on the new exact; then rerun Reviewer and QA from the immutable replacement identity, including Chrome scenarios 1-9 and Firefox's complete 10.1 acceptance matrix. Firefox scenario 7 SHALL remain two explicitly separate complementary runs within that full rerun, not the only Firefox coverage. Owner alone performs final branded Chrome/Edge smoke and separately authorizes merge. + +## 13. Superseded Replaceable Application Port Repair + +These checked tasks record the implemented `b5b87074...` baseline only. Owner's final task 16 contract supersedes the ChatRoom surface, session-event split, protocol names/fields, store ownership, and timer injection described here; checked historical boxes do not authorize retaining those superseded APIs. + +These tasks extended the predecessor replacement-only contract. They combine with the unfinished 5.7 IndexedDB work and task #37 comment preservation in one final sole-child of `dde73e99...`. Old-exact verdicts do not automatically transfer: only gates whose related code, tests, config, dependencies, artifact entrypoints, and candidate bytes are individually proven unchanged may inherit under the Owner rule; new identity/CI, changed-input gates, and Firefox/browser candidate bytes are always fresh. + +- [x] 13.1 Keep `RuntimeServer/PagePort.onLocalSession` internal for local-self convergence; project it with remote session/snapshot/replay information inside the page adapter. Rebuild ChatRoomExtern as a zero-Runtime-import Domain port containing only `join(user,site): Promise`, `sendText`, `sendReaction`, `onRecord((RecordUpdate)=>void): disposer`, `onMembership((MembershipState)=>void): disposer`, `onError((Error)=>void): disposer`, and `reconnect`. Freeze `MembershipState = {users: ChatUser[], changes: MembershipChange[]}` and opaque `MembershipChange = {id,type:'join'|'leave',user,occurredAt}`: first join returns only projected users plus exactly self-join change, refresh uses no changes, replay keeps a logical change id stable, join/leave ids differ, remote first/last user transitions dedupe, and id is local-notice idempotence only. Freeze `RecordUpdate={record:DurableEventRecord,realtime:boolean}`: only first durable live-peer insertion is true; history/replay/local are false; duplicate/failed insert has no update. Sends return canonical records, not a second `sent` result. Retain only real Chat consumer `UserListQuery`/`JoinIsFinishedQuery`, join/send/reconnect Commands, and `SendTextMessageEvent`/`OnTextMessageEvent`/peer-free `SelfJoinRoomEvent`/`OnErrorEvent`; remove the specified zero-consumer Chat events/queries and peer/runtime/snapshot/retry residue. +- [x] 13.2 Rebuild WorldRoomExtern as a zero-Runtime-import `getState(): Promise` / `onState((WorldState)=>void): disposer` / `onError((Error)=>void): disposer` port, where `WorldState=(Site & {users:ChatUser[]})[]`; adapter subscribes before upstream snapshot. Keep `(source,origin)` aggregation internal: first appearance gives stable order, update does not reorder, new contribution appends, and groups/users project in that order. Every active contribution produces one users entry, including duplicate user ids; each origin is one group whose metadata is first active contribution's current Site value, updated in place while first and promoted only on its leave/move to another origin. Retain only World UserList/JoinFinished, Join command, and OnError; remove PeerId/PresenceChanged/SelfJoin zero-consumer surface. Keep the internal raw PagePort Runtime boundary unchanged. +- [x] 13.3 Eliminate application implementation bypasses: only content `index.tsx`, options `main.tsx`, and background/Offscreen composition roots may wire `domain/impls/**` or `impls/runtime/**`. Reconnect/open-options are application commands; readiness is immediate-replay `onState(callback): disposer` over only `connecting|ready|unavailable`; profile feedback is an application feedback Domain command over existing `ToastExtern`, never direct `ToastImpl.value`, never mandatory reuse of room-coupled `ToastDomain`, and never Chat/World dependencies in Options. Runtime-backed Chat adapter takes explicit `MessageStore` dependency/factory from composition root. Remove MessageStore's test-only conflict-count API; reduce Storage to `get/set/watch` with returned disposer; move `ClockExtern` to `src/domain/runtime/externs/Clock.ts`, plain `RoomTransport` to `src/runtime/RoomTransport.ts`, and delete the dead Remesh RoomTransport extern. Do not modify Notification, Identity, Wire, Danmaku, or Toast extern/impl/API/behavior; do not change peer wire/API/limits, Runtime network/history/lifecycle, the 180-day contract, or Firefox repair behavior. +- [x] 13.4 Add parent FAIL-before and static scans for raw Runtime imports/API residue, concrete-implementation bypasses, and zero-consumer/test-only members only across Chat, World, MessageStore, Storage, RoomTransport, Clock, AppAction, Readiness, and listed Chat/World consumer surface; Danmaku/Toast are Owner-scoped exceptions. Add fake Domain-owned Chat coverage for initial self notice, identity refresh, first/final remote membership and multi-session dedupe, record-subscription realtime rules, readiness immediate replay, reconnect, records/errors, and state rehydration with `changes:[]`; fake ports have no history or host API. Add fake World coverage for exact-origin multiset, stable ordering, first contribution metadata update-in-place, leave/move promotion, and subscribe-before-snapshot gap closure. Retain internal history supply/cancel, same-domain local-self fan-out, one-physical-room, host recovery, dead-callback detach/dispose regressions. Update the canonical Archify JSON and generated HTML only to show raw Runtime events terminating in the page adapter and application Domains receiving projected state; render and prove byte-identical reproduction. + +## 14. Physical Room-Readiness Repair + +These tasks explicitly supersede only 13.3's prior prohibition on Runtime network/lifecycle or Firefox repair behavior. They do not reopen the application-port scope, peer wire protocol, Danmaku, Toast, the 180-day history contract, or task 12.8. The blocked `b5b87074...` browser evidence remains historical and transfers to no new exact. + +- [x] 14.1 First capture deterministic old-exact FAIL-before using a fake transport that separates logical desired-room registration from delayed physical Chat/World room readiness. Start an existing-user automatic join, prove that current code commits `DomainsState` and Wire logical membership, rejects the initial World send, returns no successful local projection, does not retry when physical rooms later open, and may accept a later remote session/presence. The fail-before must also prove the old World-send-fail test and synchronous fake join do not cover this state. +- [x] 14.2 Define and implement Runtime-owned provisional join acceptance: physical Chat/World acceptance precedes operation success and local session/World projection; transient not-ready rooms use bounded wait/retry or atomic rollback without a terminal remote-only half-join. Fence attempts by host/domain lifecycle generation; cover cancellation, last-page release, explicit reconnect, host replacement, and late room-open callbacks. Preserve exactly one physical room per required id and exactly one initial local session/World presence per logical join, with no page-owned queue, manual reload/reconnect requirement, or Firefox-specific business branch. +- [x] 14.3 Add deterministic post-repair coverage for cold existing-user startup, normal startup, and same-profile host/process recovery with delayed readiness plus later remote arrival. Assert eventual exact local-plus-remote Chat membership and World presence, no duplicate session/presence/room, stale-attempt isolation, and no regression of same-domain sharing, five-second lease grace, or World reconnect isolation. The earlier durable same-id send-retry clause is superseded by Owner decision B in Section 17 and is not a current acceptance requirement. +- [x] **Closed or superseded:** 14.4 Update QA acceptance harnesses only after the replacement exact exists: an already-mounted usable textarea is immediately ready; a distinct bounded eventual membership/presence wait guards only against hangs. Do not classify elapsed helper wait as product latency, and do not reinterpret the five-second last-page lease grace as a UI convergence deadline. Rerun all affected exact-bound browser evidence; no previous Chrome/Firefox result transfers. + +## 15. Provisional Event and Late-Completion Repair + +These tasks refine the already-approved atomic provisional-join, stale-attempt, and exact-membership contract after independent review of `58b1baa0...`. They do not reopen completed task 14 work, application-port scope, peer wire, Danmaku, Toast, history, or browser task 14.4. Exact `58b1baa0...` remains immutable and blocked evidence; no automated gate or browser result from it authorizes a later candidate unless its related inputs are proven byte-identical. + +- [x] 15.1 Preserve the Reviewer exact-bound deterministic FAIL-before for all three uncovered windows: peer-ready after initial broadcast but before commit receives no local Chat/World catch-up; a reconnect-pending remote session leaks to the page and is then overwritten; a timed-out World recovery late-opens into trusted/joined state without republishing current presence. Bind the two stable run logs and Reviewer report to `58b1baa0...`; do not reinterpret them as browser timing evidence. +- [x] 15.2 Keep peer-ready and inbound session events attempt-owned during provisional operation. After successful commit, deliver exactly one targeted current Chat session and World presence to each peer that missed the accepted initial broadcasts, and include each buffered reconnect session exactly once in the replacement snapshot. Failed, cancelled, or superseded attempts emit neither catch-up nor page-visible remote state. +- [x] 15.3 Make World recovery timeout ownership generation-safe: a terminal timeout invalidates and leaves or fences the underlying join before late readiness can register trusted membership; an owned automatic retry remains provisional until current full World presence is accepted. No settled physical/trusted/joined World state may survive without the matching current presence publication. +- [x] 15.4 Add deterministic candidate tests derived from all three Reviewer probes. Prove no pre-commit page projection, exact post-commit session retention/catch-up without duplicates, stale-attempt silence, and coherent timeout/late-open rollback or retry. Keep the old exact failing under the same assertions and retain existing focused room/readiness/reconnect/recovery coverage. +- [x] 15.5 Freeze a new immutable, clean, unpushed replacement with exact identity, sole lineage, source/test/OpenSpec scope, reproducible patch hashes, old-exact FAIL-before hashes, focused/affected/full test results, static gates, dual production builds, OpenSpec/architecture gates, and inherited-versus-fresh evidence classification. Keep tasks 12.8, 14.4, and all browser/Owner/merge gates open. +- [x] **Closed or superseded:** 15.6 Route that exact through independent Planner evidence/contract review and Reviewer Runtime/concurrency/test-sensitivity review. Only dual exact-specific PASS may release task 14.4 fresh browser QA. + +## 16. Owner-Final Protocol, ChatRoom, And Cumulative Replacement + +This remains the active protocol, ChatRoom, Runtime-race, timer, and cumulative-candidate contract and supersedes conflicting public structures or application-port wording in Sections 4, 7, 12, 13, 14, and 15. Section 17 separately supersedes every storage-record, MessageStore replacement, delivery-status, durable-first, outbox, and same-id crash-retry clause in Sections 5, 9, 11-14, and this Section. No implementation may add or rename any frozen field/type/method, alias old wire keys, or expose an extension without stopping and obtaining explicit @molvqingtai intervention. Tasks 12.8, 14.4, and 15.6 remain open until their exact-specific downstream gates actually complete. + +- [x] 16.1 Freeze one self-contained requirements exact containing the Owner's literal structures: protocol `ChatUser`, `ChatSession`, `HLC`, `MentionedUser.ranges`, `SessionMessage`, `TextMessage`, `ReactionMessage`, `ChatMessage`, `HistoryCursor`, `HistoryRequestMessage.syncId`, `HistoryResponseMessage.messages`, `ChatRoomMessage`, `ChatSite`, `WorldRoomMessage`; and Domain `JoinRoomCommand`, `SendTextCommand`, `SendReactionCommand`, `SendMessageCommand`, `Unsubscribe`, plus exactly the eight ChatRoom methods. Strict-validate all six artifacts and scan for active semantic contradictions before routing the frozen requirements exact to a requirements-only independent Planner gate. +- [x] 16.2 Only after independent Planner FINAL PASS on the frozen requirements exact, replace protocol source/schemas/parsers/exports/tests literally. Make `requestId -> syncId`, mention `positions -> ranges`, and history response `events -> messages` one intentional breaking clean cutover. Accept only new fields; reject old-only, old+new, aliases, missing required keys, unknown keys, and any extension before projection/store write. Update golden JSON/codec bytes and prove the unchanged `NativeWireCodec` envelope algorithm, independent Chat/World strict parsers, all existing limits, and zero active old-field residue. Mixed predecessor/current peers are unsupported only for message families whose strict shape changed; no compatibility layer is permitted. +- [x] 16.3 Replace `domain/externs/ChatRoom` literally with the frozen imports, commands, and eight methods. `impls` create `sessionId`, `id`, `hlc`, and `userId`; `sendMessage` returns that call's exact allocated-and-transported `ChatMessage` only after MessageStore settlement and exposes no insert result, collision winner, status, record, or Runtime provenance. No Runtime/peer/source/joinedAt/timer/IDB/concrete type crosses the extern. Replace split local/remote/leave/snapshot adapter channels with one private Runtime session-event path. Prove `onSessions` is the only state truth, live snapshot precedes one join/leave fact, initialization/recovery/replay emit snapshots only, and `onMessage` emits only first durably accepted remote live `ChatMessage` while excluding control/history/local/duplicate/store paths. +- [x] 16.4 Close the former `ChatRecordStore`/status task as superseded without source completion or evidence transfer. Owner decision B removes that port, `add/updateStatus/getAll`, delivery status, durable outbox, and same-id crash retry; Section 17 is the only active persistence route. Independently remove naked timer injection/externs from production adapters, use `globalThis.setTimeout/clearTimeout` and Vitest fake timers, and retain generation-safe cleanup. +- [x] 16.5 Complete tasks 15.2-15.4 in the same source tree: post-commit targeted catch-up only for missed peers/rooms, attempt-owned reconnect sessions invisible before commit and retained exactly once after commit, and generation-safe World timeout fencing/retry that cannot resurrect trusted state without current full presence. Preserve the four deterministic old-source failures and all C2 focused passes; do not split these repairs from the final public-boundary/store/timer implementation candidate. +- [x] 16.6 Finish all production code, test code, comments, OpenSpec task-state updates, Archify source/generated artifact, dependency/residue scans, and exact manifest before running the cumulative candidate gate phase. Then run focused/affected/full tests, typecheck, format, lint, dual production builds, bundle ownership/residue, OpenSpec strict/status/doctor, Archify byte reproduction, and `git diff --check`, preserving first results and exact input hashes. +- [x] 16.7 Create one clean, unpushed, self-contained sole child of `b5b870749f6d27ddc6500a3fa633942fc4dbef77` that mechanically combines the approved requirements delta, C2 repairs, final structures, Section 17 Database/MessageRecord/send-first work, timer/session-path work, tests, comments, and architecture artifacts. Report SHA/tree/sole parent, paths/stats, reproducible patch/manifest/result hashes, old-source FAIL-before evidence, fresh/inherited evidence classification, and leave 12.8/14.4/15.6/browser/push/merge tasks open. +- [x] **Closed or superseded:** 16.8 Route the immutable exact first to independent Planner full requirements/evidence/task-state review, then Reviewer cumulative protocol/Runtime/concurrency/Database/record/test-sensitivity review. Only dual FINAL PASS may release fresh exact-bound Chrome/Firefox QA; no old browser evidence transfers. +- [x] **Closed or superseded:** 16.9 After dual PASS, correct and independently review the F5/readiness helper without turning Runtime's 10-second private hang guard into a browser performance SLA, then run fresh production-built Chrome/Firefox acceptance with exact avatar multisets, isolated first-result evidence, and cleanup. Owner branded smoke, push, merge, and release remain separately unauthorized until explicit Owner decisions. + +## 17. Owner-Final Database, MessageRecord, And Send-First Replacement + +This is the only active persistence and local-send contract. It requires a complete clean-cut structural replacement, not a minimal repair around the current Store: no compatibility wrapper, alias, dual path, dead facade, hidden state channel, or test-only accommodation may preserve the obsolete architecture. It supersedes all `LocalRecord`, `DurableEventRecord`, `event` wrapper, `RecordStatus`, `ChatRecordStore`, replaceable MessageStore, pending/sent/received, durable-first, outbox, and same-id crash-retry wording in every earlier section. Earlier checked boxes remain historical evidence only and do not authorize retaining removed source, aliases, tests, or behavior. Requirements exact `c877b259d424b69e30ee9d634bf45032f2df972a` is superseded because it predates the Owner-final outer record correction. Requirements exact `c9e77a27001db486760e3014006f7c52bc569d98` is also superseded evidence because it froze `sendMessage(...): Promise` before candidate-sensitive tests proved that contract cannot provide causal local projection. Requirements exact `55f14ffeee22235dfe08ed412c6af1bed6346894` is now superseded because it retained the ambiguous `list(signal?)` MessageStore read instead of the Owner-approved default-all typed `query(query?)` contract. No verdict transfers from any superseded exact to the corrected docs child. Protocol DTOs, the exact eight-method count, C2 race repairs, one internal session path, global timers, later exact review, and browser gates remain unchanged. If implementation or parity testing proves another frozen definition insufficient, work on that boundary SHALL stop and the implementer SHALL report only to `@PM`; PM SHALL verify the gap and ask `@molvqingtai` to decide the exact minimum change. No alias, field, method, overload, fallback, or relaxed semantic may be added autonomously. + +- [x] 17.1 Freeze one self-contained docs-only corrected replacement exact containing the Owner's literal readonly `Database`/transaction/query/item/insert contracts; neutral `Unsubscribe`; exact `MESSAGE_RECORD_TYPE`, `ChatMessageRecord`, `Notice`, `SystemNoticeRecord`, and `MessageRecord` union; uniform outer `record.id`; all three Database-item/record/payload equality invariants; outer-type-only strict decoding; shared cross-variant id uniqueness; readonly `InsertMessageResult`; four-method internal concrete MessageStore; reliability option B; and transport-success-before-local-insert ordering. Reconcile proposal/design/spec/tasks, preserve peer/world artifacts byte-identically, strict-validate, scan active contradictions, record exact hashes, and keep source implementation paused. Preserve `c877b259...` without amend as superseded evidence. +- [x] 17.2 Route only the corrected immutable requirements exact to an independent Planner contract/implementability review. Planner SHALL verify type relationships, transaction/watch/cancellation/value-isolation semantics, private schema/factory boundary, MessageStore non-extern status, exact record fields, outer-type discrimination, all key/id/user invariants, Notice HLC/order semantics, removed standalone `SYSTEM_NOTICE`/property-presence/key discrimination, history separation, B removal completeness, send-first loss semantics, task states, and the mandatory Owner-stop rule. FINAL PASS releases source implementation only. +- [x] 17.3 Implement the Database extern and default IndexedDB/Memory adapters with private `MessageDatabaseSchema` configuration. Enforce non-empty scope, type-negative contracts, consistent snapshots, atomic all-or-nothing writes, exact existing-winner insert, primary/index range/order/tie-break, transaction-only abort, callback active-window, cross-context commit invalidation, idempotent close/drain, canonical plain values, by-value isolation, and one unchanged backend suite. Keep `MESSAGE_STORE_VERSION = 2` unless a real compatible migration is implemented. +- [x] 17.4 Clean-cut application records to the exact outer-type/outer-id `ChatMessageRecord | SystemNoticeRecord = MessageRecord` union with nested `message`/`notice` payloads and no old aliases or status. Implement the one internal concrete Database-backed MessageStore with `insert/query/clear/watch`, strict runtime decode by `record.type`, `DatabaseItem.key === record.id`, Chat `record.id === record.message.id`, notice `record.id === record.notice.id`, shared cross-variant id uniqueness, Chat user identity, message/notice HLC helpers, same-content replay, bounded different-content conflicts, default-all and optional exact-type query, query-owned AbortSignal settlement, explicit-only clear, and canonical-record watch. The query performs a full canonical scan, strictly decodes every item before type filtering, accepts no criteria besides `type` and `signal`, and adds no index/schema migration/version bump. Delete MessageStore `list`, standalone `SYSTEM_NOTICE`, outer notice `hlc`/`body`/`noticeType`, property-presence/key discrimination, and every dual-read/alias. Do not create a Remesh extern, public-barrel export, host injection point, backend-specific MessageStore, or fake replacement. +- [x] 17.5 Implement local sending as `allocate -> complete transport acceptance -> MessageStore.insert -> return the allocated ChatMessage`. Resolve only after the insert operation settles successfully; return the exact message allocated and transported by that call, not an insert result, same-id existing winner, status, or MessageRecord. The application uses that returned identity for exactly one local success projection without visible-record diff, body/mention matching, `onMessage`, or a hidden channel. A rejected/unavailable transport or rejected local insert returns no message, emits no success projection, and preserves the draft. Accept and test remote-visible/local-missing behavior when local insertion fails or never starts after handoff. Remove delivery status, mark APIs, durable outbox, startup/host retry, hidden fallback, recovery scans, status projections/comments, and their old tests without replacing them with another state channel. +- [x] 17.6 Keep history orchestration outside Database/MessageStore: use only the exact outer Chat record type query, preserve frozen requester/provider cutoffs, cursor/order/limit/done behavior outside that query, and exclude SystemNotice. Add unchanged-backend, exact record-shape/strict-decoder/key-id-user parity, cross-variant collision, property/key-discrimination rejection, MessageStore default/type/abort/invalid-query parity plus `list` removal, send-first/loss-window, causal returned-message projection under store-watch ordering and same-body concurrent tabs, first-remote-live callback, history/duplicate/conflict silence, static import/export/residue, and old-source-sensitive tests. Run focused/affected/full Vitest, typecheck, format, lint, dual production builds, bundle ownership, OpenSpec strict/status/doctor, Archify byte reproduction, and diff-check. +- [x] 17.7 Freeze one clean, unpushed, self-contained sole child of `b5b870749f6d27ddc6500a3fa633942fc4dbef77` containing all approved protocol/ChatRoom/session/timer/C2 work plus this corrected Section 17 source/tests/docs/architecture. Report exact/tree/parent, reproducible patches/manifests/results, removed-term scans, old-source FAIL sensitivity, fresh/inherited evidence, and keep browser/push/merge gates open. +- [x] **Closed or superseded:** 17.8 Route the immutable source exact through fresh Planner and Reviewer cumulative review. Only dual exact-specific FINAL PASS may release QA; no `8bae8ef...`, `58b1baa0...`, `c877b259...`, prior Store, build, or browser verdict transfers to changed inputs. +- [x] **Closed or superseded:** 17.9 After dual PASS, run fresh production-built Chrome/Firefox acceptance with exact peer/session/presence assertions plus local send success/failure and remote-live/history persistence behavior under B. Do not claim crash-window recovery. Owner smoke, push, merge, and release remain separately unauthorized. +- [x] 17.10 Capture the implementation-discovered causal-local-projection gap and explicit Owner authorization. Replace only `sendMessage(...): Promise` with `Promise` while retaining exactly eight methods; define the result as the exact allocated-and-transported message returned only after MessageStore settlement, never a status/record/collision winner; keep `onMessage` remote-only; reconcile proposal/design/spec/tasks; strict-validate the docs-only child; and keep source interface work paused. +- [x] 17.11 Route only causal-send requirements exact `55f14ffeee22235dfe08ed412c6af1bed6346894` to independent Planner contract/implementability review. Planner verified causal identity under store-watch ordering, same-content concurrent tabs, same-id existing-winner handling, rejection/draft behavior, no local `onMessage`, no hidden side channel/status/storage leak, eight-method identity, requirements lineage, and task truth. That FINAL PASS released only the return-type/local-projection source repair and does not transfer to this changed query contract. +- [x] 17.12 Capture the Owner's MessageStore naming/query decision. Replace `list(signal?)` cleanly with `query(query?: Readonly<{type?: MessageRecord['type']; signal?: AbortSignal}>)`; make omitted criteria return all canonical records; strictly decode every physical item before optional exact-type filtering; retain only primary-scan order; reject unknown fields/types; add no index, migration, or v2 version bump; keep history cutoff/range/order/limit/cursor/syncId outside; reconcile proposal/design/spec/tasks; and preserve the frozen temporary source exact without modification. +- [x] 17.13 Route only query-contract requirements exact `a8dd61a934b8dc103d889cd6757be60945867855` to independent Planner review. Planner task #63 issued exact-specific FINAL PASS after reproducing tree `87edbbcf065c4ca65e33ef0433502ddc0fda9ae2`, sole-parent lineage, four-method/default/type/abort/invalid-query/decode-before-filter semantics, no list/alias/index/migration/version change, history and causal-send boundaries, protected peer/world identity, hashes, gates, and truthful task state. PM independently accepted that verdict and released only the query clean cut; no temporary-source, prior-review, QA, or browser verdict transfers. +- [x] 17.14 After Planner PASS, create a fresh detached worktree from `b5b870749f6d27ddc6500a3fa633942fc4dbef77`, mechanically transfer the immutable temporary candidate's cumulative tree, apply only the approved query rename/semantics plus tests/docs/residue, and freeze one new clean unpushed no-ref sole child of `b5b87074...`. Do not amend/reset the temporary exact, publish it as final, or transfer any old review/QA/browser verdict. + +## 18. Artico Per-Target Send Isolation Repair + +This section clarifies the existing physical-acceptance and atomic-provisional contract after cumulative Reviewer task #66 found a ready-to-closing provider window in exact `b818f5045c004035c982dbc77969cf8613dc226c`. It does not change protocol bytes, the eight-method ChatRoom API, Wire/RoomTransport signatures, Database, MessageRecord, MessageStore query, causal send, reliability option B, history, C2 generation ownership, timers, hosts, package/dependency versions, or browser acceptance scope. Exact `b818f504...` remains immutable blocked evidence. Requirements commits remain authority inputs only and SHALL NOT become source ancestors. Tasks 12.8, 14.4, 15.6, 16.8, 16.9, 17.8, and 17.9 remain open until the fresh replacement passes their actual downstream gates. + +- [x] 18.1 Independently reproduce and bind the `b818f504...` Reviewer report, adapter probe, and actual pinned `@rtco/client@0.3.6` probe. Prove that a remembered-ready call can enter `closing` before Room `leave`, a synchronous first-target rejection aborts the pinned target-array iteration, and a later sendable peer receives nothing. Reconcile proposal/design/runtime spec/tasks into one strict-valid docs-only authority exact while keeping source, tests, protocol, package, lockfile, architecture, builds, and prior exacts byte-identical. +- [x] 18.2 Route only the immutable Section 18 requirements exact to a fresh independent Planner review. Planner SHALL verify deterministic target selection/de-duplication/order, target-local versus room-level settlement, no partial-publication rollback, untargeted and explicit multi-target semantics, all four production flows, no reliability/API/protocol/persistence expansion, lineage/scope/hashes, protected input identity, and truthful task states. FINAL PASS releases only the Section 18 source repair. +- [x] 18.3 After Planner PASS, create a fresh detached source worktree from `b5b870749f6d27ddc6500a3fa633942fc4dbef77`, mechanically transfer the immutable `b818f504...` cumulative tree plus the approved Section 18 docs, and keep production byte-identical while adding final candidate-sensitive adapter, pinned-provider, and Network tests. Run the exact tests against unchanged `b818f504...` production twice and preserve stable FAIL-before evidence for ready-to-closing isolation across initial Chat, initial World, committed World presence, World recovery, untargeted send, and explicit multi-target catch-up. +- [x] 18.4 Implement only per-target physical-send isolation: snapshot remembered peers for untargeted sends or use explicit targets, de-duplicate in deterministic order, attempt every distinct target independently, contain target-local provider rejection, continue later targets exactly once, and settle after all attempts including none. Keep missing-room and pre-provider validation/codec rejection as operation-level failures. Add no provider introspection dependency, retry, outbox, status, fallback, callback, delivery acknowledgement, wire/API signature, persistence, schema, migration, package, or lockfile change. +- [x] 18.5 Prove the repaired exact passes the final Section 18 matrix and all cumulative focused/affected/full tests, typecheck, format, lint, OpenSpec target/all strict/status/doctor, Archify check/byte reproduction, dual production builds, bundle ownership/residue, protected-input identity, and `git diff --check`. Freeze one clean, detached, unpushed, no-ref sole child of `b5b87074...` with reproducible identity/scope/patch/evidence/result hashes; keep `b818f504...`, `a2748e03...`, and their evidence unchanged. +- [x] **Closed or superseded:** 18.6 Route only that immutable replacement through fresh Planner cumulative contract/evidence/task-truth review and fresh Reviewer implementation/concurrency/test-sensitivity review. Only dual exact-specific FINAL PASS may close 15.6/16.8/17.8 and release QA; no prior Planner, Reviewer, test, build, browser, or waiver verdict transfers. +- [x] **Closed or superseded:** 18.7 After dual PASS, run the still-open fresh Chrome MV3 and Firefox MV2 acceptance, then Owner branded smoke/acceptance. Push/PR #69 update may follow passed gates without separate authorization; merge and release remain separate explicit Owner decisions. + +## 19. Owner-Final Remesh DDD + CQRS Runtime Authority Replacement + +This is the final pre-merge Runtime architecture contract and supersedes every conflicting final-candidate route or monolithic `NetworkDomain`/stateful ChatRoom/Wire-adapter ownership statement in Sections 2, 7, 9, 12, and 14-18. Exact `9c90bb0e53328ebaf7e9aec8cf104a2943f307c4` remains the frozen functional and structural fail-before baseline, not the final merge candidate. Its prior Reviewer/QA/browser/CI evidence is baseline-only and transfers to no changed input. Requirements commits are authority inputs and SHALL NOT become source ancestors. Archify tooling exact `167e8817becc85a5328c9909bf4a75212b99df88` is an independent tooling line and SHALL NOT become the Runtime replacement parent. The initial Section 19 authority exact remains frozen evidence but is superseded after implementation audit found that its dependency whitelist omitted the already-required `Session -> Delivery` live-admission edge and `History -> Session` binding-validation edge; the correction changes no owner, protocol, API, persistence, reliability, product behavior, or acceptance scope and requires a fresh Planner review before source work resumes. + +- [x] 19.1 Freeze one requirements-only OpenSpec authority and synchronized architecture JSON/HTML for **Remesh DDD + CQRS**: exact eight-method `ChatRoomExtern`; state-free Runtime client; application Query/Command/Event-only rule; Command/Query/Event/Effect/Extern semantics; unique Lifecycle/Connection/Session/World/History/Delivery/Wire owner matrix; explicit Session/Chat/History/World peer-value-to-owner mapping; acyclic `Session -> Delivery` live admission and `History -> Session` binding validation; Coordinator physical-port facts versus Lifecycle domain-lease authority; immutable peer-wire versus clean-cut internal comctx boundary; private `RoomTransportExtern`/Artico provider boundary; conditional real `Remesh.module` rule; structural fail-before matrix; clean-cut forbidden paths; cumulative acceptance matrix. Change no Runtime source and keep the authority commit out of source ancestry. +- [x] 19.2 Route only the immutable authority exact to Planner. Planner SHALL review requirements consistency, audit Remesh APIs/dependencies and Domain-cycle risk, choose the shortest context-appropriate final filenames/directories without changing owner semantics, decide each existing/possible Module by the isomorphism rule, decompose the one clean-cut replacement, and route implementation/review/QA/browser dependencies. Planner SHALL NOT issue a source-candidate or evidence verdict. +- [x] 19.3 From clean `9c90bb0...`, freeze candidate-sensitive structural FAIL-before twice before production repair. Prove intended failures for ChatRoom command/session/local-session/published-session/recovery authority; catch-all Network ownership; absent private RoomTransport Extern/Wire Domain; Query I/O/Event-as-command/Effect direct-write or cross-State access; false `*Module` naming; provider leakage; dependency cycles; old diagram topology. Keep protected peer-wire schema/codec/golden-byte controls passing and preserve exact test/manifest hashes. +- [x] 19.4 Establish the private provider boundary first: create Runtime-private `RoomTransportExtern`, make `WireDomain` the sole trusted room/source/codec/schema/size/ordering/queue/provider-callback owner, inject Artico only through its provider implementation/composition, run one provider contract suite against deterministic fake and Artico, and remove the former imperative `WireExtern` route in the same step. Preserve target-local send isolation and every current peer byte. +- [x] 19.5 Move Runtime authority one owner at a time into Lifecycle (leases/grace), Connection (join/leave/reconnect/generation), Session (committed sessions/snapshot/id/HLC), World (presence), History (requester/provider/cutoff/cursor/batch), and Delivery (sequence/un-ACK/replay). Immediately delete each old `NetworkDomain` State, read, write, Effect, test, and comment after its owner moves. Enforce the documented acyclic Domain dependency direction, including Session-owned live validation before its one Delivery admit Command and History's Session Query before history-state transition, and allow no dual read/write, alias, fallback, generic lock, mutable Server map, or compatibility bridge. +- [x] 19.6 Make `src/domain/impls/runtime/ChatRoom.ts` a state-free Runtime client/Event bridge plus persistence-before-ACK and history-supply boundary. Remove every retained join command, session/local/published-session map, Runtime generation, recovery queue, and snapshot authority. Clean-cut internal comctx Commands/Events as needed, leaving no old aliases/listeners/routes and preserving exactly eight public methods, causal `sendMessage`, remote-only `onMessage`, inbound persistence-before-ACK, page-owned history supply, readiness, manual reconnect, and product behavior. +- [x] 19.7 Resolve module integrity after owner migration: every surviving production `*Module` SHALL be a genuine reusable/configurable `Remesh.module` with at least two semantically isomorphic host flows, or lose the Module name. Attempt/generation becomes a Module only with identical transition/cancellation/failure/late-completion semantics; Delivery remains a direct owner without a second isomorphic consumer; codec/parser/order/range/size/provider callbacks remain pure functions or Extern implementation logic. Add structural coverage for the final decisions. +- [x] 19.8 Finish all production code, tests, necessary owner/CQRS/provider/module comments, OpenSpec task truth, architecture JSON/generated HTML, dependency/export/residue scans, protected-input manifests, and old-owner deletion before running validation. Then Coder SHALL run focused/affected/full tests, provider parity, exact peer-wire golden controls, typecheck, format, lint, OpenSpec strict/status/doctor, Archify validation/byte reproduction, dual production builds, bundle ownership, `git diff --check`, and every unchanged Database/MessageStore/Section 18 control on the frozen inputs. +- [x] 19.9 Freeze one clean, detached, unpushed, no-ref, self-contained sole child of `9c90bb0...` with no authority or Archify-tooling commit in its ancestry. Report exact/tree/sole parent, scope/stats, canonical patches, fail-before/final manifests and result hashes, protected peer-wire/Database/MessageStore identities, comments, architecture reproduction, and fresh versus unchanged-input evidence. Coder stops; Planner only routes fresh Reviewer. +- [x] **Closed or superseded:** 19.10 Fresh Reviewer independently audits only the replacement exact: owner matrix, CQRS semantics, Domain dependency direction, state-free adapter, private provider/Artico imports, provider parity sensitivity, truthful Modules, clean-cut residue, immutable peer wire, unchanged persistence/reliability, concurrency correctness, tests/comments/artifacts, and exact-bound gates. Only `FINAL PASS` releases QA; any replacement exact requires a fresh verdict. +- [x] **Closed or superseded:** 19.11 QA independently runs the complete production-built Chrome MV3 and Firefox MV2 acceptance on the Reviewer-passed exact, including startup, dual-page sharing, F5, host rebuild/process restart, reconnect isolation, last-page five-second grace, exact Chat/World convergence, initial and committed presence, history ACK/replay, send-first causal exactly-once projection, Section 18 target isolation, cleanup, and no product/UI regression. No prior browser result transfers. +- [x] **Closed or superseded:** 19.12 After QA PASS, Owner performs one branded Chrome/Edge smoke on the exact candidate. On Owner acceptance, push/update PR #69 and run exact-bound CI without requesting separate commit/push authorization. Only after all final gates pass may PM ask `@molvqingtai` for the separate explicit merge authorization; release remains separately governed. + +## 20. Retained-Record, Logical-Presence, And Notice-Grouping Combined Replacement + +This is the active post-exact06 repair contract. Published reconciliation exact `06b5de65969a11c4b51274178f9e59c4c6b70501`, PR #69, and every earlier report/evidence artifact remain immutable. Retained-only exact `bce30208f617b7cc65f096194341e16b1b479536` is immutable pre-scope evidence and SHALL NOT be the combined candidate's parent. The combined candidate SHALL be one clean detached sole child of exact06. The Owner authorized only the required opaque SESSION logical-generation field and strict ordered/idempotent SESSION_END fact, explicitly without backward compatibility; `ChatMessage`, history, World, codec, limits, exact eight-method `ChatRoomExtern`, four-method MessageStore, `RuntimeServer`, database schema, and every unrelated wire/public boundary remain frozen. + +- [x] 20.1 Freeze the fail-first controls and authorized contract: exact06 retained-row reproduction; preparation-baseline timing; A/B/C/D six-timepoint lifecycle; causal generation/end classification; private session-storage lease/observer persistence; immediate generation-scoped self notice; final observer leave; true-return join; post-sort UI grouping and streaming-history reprojection. Preserve exact06 and `bce30208...` without amend, rebase, merge, ref, or publication. +- [x] 20.2 Implement Session-owned logical presence. Require opaque `presenceId` on SESSION, add strict SESSION_END, persist private active lease plus bounded observer ledger through `PresenceStoreExtern`, retain preparation baseline, treat PeerLeft as provisional, restore the same generation across reconnect/recovery/reattach/host replacement, retire the lease and publish end before physical leave, make duplicate/additional-session/end facts idempotent, reject ended-generation resurrection, and allocate a fresh generation only on initial join or true return. Add no timer/debounce/source-peer/session-id classifier, public contract expansion, MessageStore schema, or compatibility decoder. +- [x] 20.3 Reproduce retained-row isolation directly from exact06: strict Valibot validation on insert/inbound/load; invalid inbound rejected before persistence/barrage/list publication with request-local diagnostics and ACK recovery; retained rows decoded independently and left physical; valid rows remain visible; bounded per-key deduplicated private diagnostics use the existing conflicts store without record-watch notification; diagnostic failure cannot hide valid results. +- [x] 20.4 Implement application/UI behavior and controls: consume Runtime join provenance for immediate generation-scoped self-notice persistence; keep notices observer-local and out of history; group only maximal adjacent post-sort SystemNotice runs; keep singleton unchanged; show latest plus count and icon expand/collapse for runs; let non-notice rows split groups; recompute after late canonical history. Preserve concise ownership comments and deterministic actual-stack tests for baseline, duplicates, additional session C, transient loss, replacement host D, final end/leave, late duplicates, and true return. +- [x] 20.5 Run the complete exact-bound development gates on the finished tree: focused lifecycle/presence/retained/grouping suites; full Vitest; `pnpm check`; format and repository lint; protocol/golden/persistence/provider/cumulative Reviewer probes; OpenSpec target/all strict/status/doctor; architecture/dependency/residue audits; Chrome MV3 and Firefox MV2 production builds; bundle ownership; and `git diff --check`. Preserve self-verifying evidence manifests without self-referential hashes. +- [x] 20.6 Create exactly one clean detached commit whose sole parent is exact06. Freeze SHA/tree/parent, one-commit ancestry, patch/stat/scope, protected-input identities, result hashes, clean status, and evidence manifest. Do not push, replace PR #69, update Owner checkout, or add a ref before fresh review and QA authorization. +- [x] **Closed or superseded:** 20.7 Route only the immutable combined exact to fresh Reviewer. Reviewer SHALL audit generation/end causality, baseline and additional-session semantics, final release ordering, private storage persistence, local/observer notice ownership, retained-row request-local fences/diagnostics, UI grouping/reprojection, frozen public boundaries, test sensitivity, and cumulative Runtime invariants. Only exact-specific `FINAL PASS` may release QA. +- [x] **Closed or superseded:** 20.8 After Reviewer PASS, QA independently runs fresh production-built Chrome MV3 and Firefox MV2 acceptance using preserved Owner profiles/sessions: retained invalid row plus valid live message; first logical join; refresh/reconnect/recovery/host replacement without flapping; final observer leave; true return; notice grouping expand/collapse; late-history regrouping; existing startup/F5/World/history/send/provider controls; exact artifact identity and cleanup. No prior browser verdict transfers. +- [x] **Closed or superseded:** 20.9 Only after development gates, Reviewer, and QA PASS may Owner run retained-profile branded smoke and authorize publication replacement. PR #69, `origin/refactor/webrtc-runtime`, and `refs/pull/69/head` remain frozen at exact06 until that decision. Merge and release remain separate explicit Owner authorizations. + +## 21. Lifecycle-Blocked Exact Replacement + +Exact `6511a33d22e1e9f5fe58f9de3380625f41ff7ab1` completed tasks 20.1-20.6 but received fresh Reviewer `FINAL BLOCKED`: the application reconnect composition entered final release, and rejected durable retirement still published SESSION_END plus physically departed. That exact, its report/probes/evidence, and its worktree remain immutable. The replacement SHALL be a new clean detached single-commit sole child of exact06, never a child or mutation of the blocked exact. It SHALL retain all task-20 behavior while repairing only these lifecycle defects and their necessary ordering controls. + +- [x] 21.1 Bind the Reviewer's unchanged report/probe/config and reproduce both failures twice on immutable exact `6511a33...`. Preserve that input as historical fail-before. Create the separately authorized candidate-bound control that corrects only the impossible retirement expectation to the same active `userId`/`presenceId`/status and adds Chat/World membership plus storage-recovery retry/order; prove this control also fails twice on exact `6511a33...`. +- [x] 21.2 Keep the public application `leaveRoom()` then `joinRoom(command)` reconnect composition while mapping the Runtime adapter's first call to current-domain reconnect, reusing `presenceId`, rotating only physical Chat `sessionId`, leaving World joined, and emitting no SESSION_END/logical deltas/notices. For final release, retain committed active presence until retired persistence succeeds; on rejection clear only the pending request and preserve the active durable/in-memory lease, joined snapshot, World/History state, and Chat/World physical membership. After storage recovery, retry the same generation in strict `durable retired -> one SESSION_END settled -> physical departure` order. Fence SESSION_END send rejection from physical departure and permit idempotent end retry. +- [x] 21.3 Run the candidate-bound lifecycle controls, cumulative exact probes, focused/full tests, static/OpenSpec/architecture/protected-boundary gates, dual production builds, bundle ownership, and clean-tree checks. Freeze a new self-verifying evidence manifest without self-reference. +- [x] 21.4 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze SHA/tree/parent, one-commit ancestry, diff/scope/protected identities, patch ID, result hashes, clean status, and ref-free/unpushed identity. Do not modify or publish PR #69, remote refs, the Owner checkout/profile, or any blocked exact/evidence. +- [x] 21.5 Route only the new immutable exact to a fresh Reviewer review from zero. Only exact-specific `FINAL PASS` may release fresh Chrome MV3 and Firefox MV2 QA; only QA PASS may release PR replacement, fresh CI, Owner retained-profile smoke, publication, merge, or release under their existing separate authorizations. + +## 22. Pending-Final-End Host-Replacement Repair + +Exact `7dc906a91cfd2575243d31f4defc171604d2ef7a` completed tasks 21.1-21.5 but received fresh Reviewer `FINAL BLOCKED`: after durable retirement and a rejected SESSION_END, the final-end identity existed only in volatile Session state, so host replacement allocated a new `presenceId` and permanently orphaned the observer's old active generation. Exact `7dc906a...`, exact `6511a33...`, retained-only `bce30208...`, and all prior evidence remain immutable. The replacement SHALL again be one clean detached single-commit sole child of exact06, preserve every independently passed task-20/task-21 behavior, and repair only this private durable-state gap without expanding public or persistence boundaries. + +- [x] 22.1 Bind the Reviewer's unchanged exact7dc report/probe/config and reproduce the host-replacement failure twice on immutable exact `7dc906a...`: the replacement physical host uses a different `presenceId`, no observer leave is produced after its final release, and the five prior lifecycle controls remain green. +- [x] 22.2 Extend the strict private `PresenceDomainRecord` with a mutually exclusive pending-final-end identity. Persist that marker only after SESSION_END explicitly rejects and before surfacing the failure. On same-host final-release retry, durably clear the marker before resending the same idempotent end; on supported replacement-host join by the same user, restore the marker as the same active generation before publishing SESSION. Successful settlement SHALL leave no marker or outbound retry and only then permit physical departure. Preserve the two-method `PresenceStoreExtern`, eight-method `ChatRoomExtern`, four-method MessageStore, `RuntimeServer`, origin Database schema, Chat/history/World wire, and all task-20/task-21 behavior. +- [x] 22.3 Add deterministic actual-stack owner controls for `durable retired -> SESSION_END reject -> host crash/replacement -> same presenceId/new sessionId -> no join/leave flap -> later final release -> exactly one observer leave`, including durable marker presence after rejection and absence after recovery/settlement. Make the Reviewer control pass unchanged, keep candidate-bound `5/5`, and rerun every prior focused/full/cumulative/static/OpenSpec/architecture/protected/build/bundle gate. +- [x] 22.4 Create exactly one clean detached replacement commit whose sole parent is exact06, then freeze exact/tree/parent/one-commit ancestry, patch ID, diff/scope/protected identities, clean/ref-free/unpushed state, exact-bound results, and a self-verifying evidence manifest. Do not mutate or publish any blocked exact, PR #69, remote ref, Owner checkout/profile, QA artifact, CI state, or downstream gate. +- [x] 22.5 Route only the new immutable exact to another fresh Reviewer review from zero. No verdict or evidence transfers from exact7dc; only a new exact-specific `FINAL PASS` may release fresh QA. + +## 23. Unsettled-Final-End Durability Repair + +Exact `c04d40262a380e4d0fb8ea7e09c9d17f378b7a80` completed tasks 22.1-22.5 but received fresh Reviewer `FINAL BLOCKED`: first and retry SESSION_END attempts removed every durable generation identity until the send rejected, so a supported host replacement while END remained unsettled allocated a new `presenceId` and orphaned the observer generation. Exact `c04d402...`, exact `7dc906a...`, exact `6511a33...`, retained-only `bce30208...`, and all prior evidence remain immutable. The next replacement SHALL again be one clean detached single-commit sole child of exact06 and SHALL preserve every independently passed prior behavior while closing only the unsettled-END and post-settlement-cleanup windows. + +- [x] 23.1 Bind the Reviewer task #122 report/probe/config unchanged and reproduce the held-END failure twice on immutable exact `c04d402...`: the six prior controls stay green, host replacement uses a different `presenceId`, and later release produces zero observer leaves. Preserve the original probe/hash as historical input. +- [x] 23.2 Keep one private durable final-generation identity throughout every END attempt. Persist mutually exclusive `inflightEnd` before the first send, transition retryable `pendingEnd -> inflightEnd` durably before retry, and transition `inflightEnd -> no marker` only after END settlement. A failed transition or cleanup SHALL retain the prior durable identity, preserve Chat/World physical membership, and remain retryable. Only successful cleanup may release physical membership. Supported same-user host replacement may restore either marker as the same active `presenceId`; a different user SHALL consume neither. Preserve the two-method `PresenceStoreExtern`, eight-method `ChatRoomExtern`, four-method MessageStore, `RuntimeServer`, origin Database schema, Chat/history/World wire, and all prior behavior. +- [x] 23.3 Add deterministic actual-stack owner controls for first-attempt held END, retry held END, retry-transition rejection, same-generation host replacement, cleanup rejection/retry, exactly one observer leave, and zero final `local`/`inflightEnd`/`pendingEnd` residue. Keep the Reviewer original probe immutable; in the authorized candidate-bound copy count `!record.local && (hasFinalEndIdentity || !endSettled)` as retirement, exclude only post-settlement marker-free cleanup, and separately assert one cleanup write plus zero residue. Prove the same retirement target test still fails for its original reason on exact651, exact7dc, and exact c04, then passes on the replacement draft. +- [x] 23.4 Rerun every prior focused/full/cumulative/static/OpenSpec/architecture/protected/build/bundle gate. Create exactly one clean detached replacement commit whose sole parent is exact06, then freeze exact/tree/parent/one-commit ancestry, patch ID, diff/scope/protected identities, clean/ref-free/unpushed state, exact-bound results, and a self-verifying evidence manifest. Do not mutate or publish any blocked exact, PR #69, remote ref, Owner checkout/profile, QA artifact, CI state, or downstream gate. +- [x] 23.5 Route only the new immutable exact to another fresh Reviewer review from zero. No verdict or evidence transfers from exact c04 or any prior exact; only a new exact-specific `FINAL PASS` may release fresh QA. + +## 24. Settled-End Cleanup Ownership Repair + +Exact `80174e889d196c7b3502a33a8350d3e4e8a2d5b8` completed tasks 23.1-23.5 but received independent Reviewer and QA `FINAL BLOCKED`: after observers accepted SESSION_END, marker-removal rejection left the durable record as `inflightEnd`, byte-identical to an unsettled send. Same-user host replacement therefore restored the observer-ended generation as active, published a replacement SESSION that the observer correctly rejected, and split local from observer membership. Exact `80174e88...`, exact c04, exact7dc, exact651, retained-only bce, and all prior reports/probes/evidence remain immutable. The replacement SHALL again be one clean detached single-commit sole child of exact06 and SHALL preserve all independently verified prior behavior while repairing only this private settlement/cleanup ambiguity. + +- [x] 24.1 Bind the Reviewer task #125 report/probe/config unchanged and reproduce the combined cleanup-rejection -> one observer leave -> old-host crash -> same-user replacement failure twice on immutable exact `80174e88...`. Preserve the original inputs and exact as historical fail-before. +- [x] 24.2 Extend the strict private `PresenceDomainRecord` with mutually exclusive `settledEnd` cleanup ownership. END settlement SHALL durably transition `inflightEnd -> settledEnd` before marker removal. Marker-removal rejection SHALL retain `settledEnd`; retry SHALL remove it without another END. A replacement loading `settledEnd` SHALL perform cleanup only: no active lease, Chat/World join, SESSION, SESSION_END, live-message authority, or observer transition. Another user SHALL consume no marker. +- [x] 24.3 Treat `inflightEnd`/`pendingEnd` replacement as continuation of the same final-END transaction rather than a successful active join. The replacement MAY physically rebind the same `presenceId` only to publish the same idempotent END; it SHALL fence live sends, clear all markers, and return no active session. If recording `settledEnd` itself rejects after observers accepted END, the retained unsettled identity SHALL still take this safe idempotent continuation path. Only a later explicit join after cleanup SHALL allocate a new `presenceId` and exactly one self/observer join. +- [x] 24.4 Add deterministic actual-stack owner controls for marker-removal rejection/retry, settled-marker write rejection, first/retry unsettled host replacement, settled-cleanup host replacement with zero replacement SESSION/END, cross-user rejection, exactly-one observer leave, fresh-generation explicit return, and zero final `local`/`inflightEnd`/`pendingEnd`/`settledEnd` residue. Preserve the two-method PresenceStoreExtern, eight-method ChatRoomExtern, four-method MessageStore, RuntimeServer, origin Database schema, Chat/history/World wire, and every unrelated protected boundary. +- [x] 24.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/801 scope and protected identities, clean/ref-free/unpushed state, and development-diagnostic hashes. Do not run or claim an independent release test matrix, and do not mutate or publish any blocked exact, PR #69, remote ref, Owner checkout/profile, QA artifact, CI state, or downstream gate. +- [x] **Closed or superseded:** 24.6 Route the frozen exact to fresh independent QA and Reviewer work from zero in parallel. Only dual exact-specific PASS may release Chrome MV3 plus Firefox MV2 product QA; PR replacement, fresh CI, Owner smoke, publication, merge, and release remain separate frozen gates. + +## 25. Authoritative Final-Release Message Fence Repair + +Exact `1ca306ab0985278de802618f6e9e9a322e71cd91` completed tasks 24.1-24.5 but received fresh Reviewer `FINAL BLOCKED`: after SESSION_END settled and cleanup rejected, the ordinary same host retained its committed Runtime domain and physical membership but was absent from the volatile replacement-only `finalizingDomains` Set. It could therefore accept and publish a live event for an observer-ended generation. Exact1ca, exact801, c04, 7dc, 651, retained-only bce, and all prior reports/probes/evidence remain immutable. The replacement SHALL again be one clean detached single-commit sole child of exact06, preserve all prior settlement/recovery behavior, and repair only message authority during final release. + +- [x] 25.1 Bind Reviewer task #128's original report/probe/config/results unchanged and reproduce the settled-cleanup same-host live-send failure twice on immutable exact1ca as `1 failed | 14 skipped`, including the observer's `message arrived before session binding` drop. +- [x] 25.2 Replace the volatile replacement-host fence with one Session-authoritative private finalization Query derived from every pending release phase or restored `inflightEnd`/`pendingEnd`/`settledEnd` marker. Server preflight and Session text/reaction allocation plus send Commands SHALL all reject while it is true. Marker cleanup success SHALL keep the pending fence through physical Domain release. +- [x] 25.3 Preserve initial retirement persistence rejection as active: remove its pending release, retain the same `local` lease and Chat/World membership, and permit allocation/send. Add deterministic same-host owner controls for rejected retirement usability plus held/rejected unsettled END and settled-cleanup rejection, proving allocation/send rejection and zero extra live wire frames for every non-active case. +- [x] 25.4 Preserve the two-method PresenceStoreExtern, eight-method ChatRoomExtern, four-method MessageStore, RuntimeServer, origin Database schema, existing presence wire exception, Chat/history/World wire, four-state durability/replacement semantics, observer exactly-once ledger, and every unrelated protected boundary. Synchronize proposal/design/Runtime spec without a new route, method, state owner, or protocol shape. +- [x] 25.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/1ca scope, protected identities, clean/ref-free/unpushed state, original fail-before hashes, and narrowed development-diagnostic hashes. Do not run or claim the independent release matrix. +- [x] **Closed or superseded:** 25.6 Route the frozen exact to fresh independent QA and Reviewer work from zero in parallel, then to Chrome MV3 plus Firefox MV2 product QA only after dual exact-specific PASS. PR #69, remote refs, CI, Owner checkout/profile, smoke, publication, merge, and release remain frozen. + +## 26. Chrome MV3 Offscreen PresenceStore Assembly Repair (Round 7) + +Round 6 exact `b9385857fab9946c1884e3b074a5be2dd328770f` completed tasks 25.1-25.5 but received browser product QA `FINAL BLOCKED (P1)`: Chrome MV3 Offscreen does not expose `chrome.storage`, so shared host bootstrap synchronously dereferenced unavailable `browser.storage.session` before Runtime provider mount. exact06 mounted under the same launcher and Firefox MV2 mounted with the same candidate. Round 6 and all prior exacts/evidence remain immutable. The round 7 replacement SHALL again be one clean detached single-commit sole child of exact06 and SHALL preserve all round 6 lifecycle, message-fence, validation, notice, grouping, protocol, and protected-boundary behavior. + +- [x] 26.1 Bind QA task #131's original Chrome launcher/control and reproduce the immutable b938 mount timeout twice; retain exact06 same-launcher mount controls and zero owned-profile/process residue. +- [x] 26.2 Make shared `startHost` accept the existing two-method PresenceStore dependency instead of reading browser storage. Chrome MV3 SHALL construct the concrete session-backed store in the background Service Worker and expose only `load`/`save` through a private internal comctx namespace; Offscreen SHALL inject it without a page/tab relay or volatile fallback. Firefox MV2 SHALL pass the same concrete store directly from its persistent Background Page. +- [x] 26.3 Keep unrelated Offscreen injector namespaces outside the Runtime provider-to-tab relay while preserving malformed, forged Runtime namespace, direction, target, and recycled-tab rejection. Add focused controls proving background storage `set/get` through the Offscreen proxy, listener cleanup, Chrome mount plus setup completion, and unchanged Firefox host assembly. +- [x] 26.4 Run focused and full Runtime/application regressions, TypeScript, format/lint, OpenSpec strict validation, architecture/protected audits, Chrome MV3 and Firefox MV2 production builds, and exact-bound Chrome mount/Runtime availability diagnostics. Preserve four-state durability, request-local persistence failures, live-message fencing, and every round 6 owner control. +- [x] 26.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/b938 scope, protected identities, clean/ref-free/unpushed state, original fail-before hashes, and narrowed development-diagnostic hashes. Do not run or claim independent release acceptance. +- [x] 26.6 Route the frozen round 7 exact to fresh independent QA and Reviewer work from zero in parallel. Only dual exact-specific PASS may release the complete Chrome MV3 plus Firefox MV2 product matrix; PR #69, remote refs, CI, Owner checkout/profile, smoke, publication, merge, and release remain frozen. + +## 27. Chrome MV3 PresenceStore Source-Boundary Repair (Round 8) + +Round 7 exact `886a11ebacbbbdb180c00a961c2327ebec7959d0` completed tasks 26.1-26.5 but received fresh Reviewer `FINAL BLOCKED (P1)`: its generic provider accepted valid PresenceStore injector envelopes from content/options senders, while the generic Offscreen injector discarded provider transport identity. A content sender could read the active lease and durably replace it with a Session-never-authored `settledEnd`. Namespace knowledge was incorrectly treated as privacy. exact886 and every prior exact/evidence remain immutable. The round 8 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve the round 7 mount repair and all cumulative lifecycle/fence/retained/notice behavior, and repair the complete bidirectional source boundary in one batch. + +- [x] 27.1 Bind Reviewer task #133's original source-boundary probe/config unchanged and reproduce unauthorized active-lease read plus forged `settledEnd` write twice on immutable exact886. Preserve original and copied inputs and both blocked runs as historical fail-before. +- [x] 27.2 Replace only PresenceStore's generic broadcast adapters with a dedicated comctx adapter on a point-to-point Runtime Port. Background SHALL validate exact runtime id + Offscreen URL + no tab at `Port.sender` before dispatch; Offscreen SHALL receive responses only on its background endpoint. Port name/namespace SHALL not be authorization. Keep generic Runtime/page adapters unchanged. +- [x] 27.3 Add one combined source matrix: legitimate Offscreen `load/save`, storage rejection propagation, provider restart/reconnect, content read rejection, options write rejection, durable bytes unchanged, forged broadcast provider response ignored, malformed/wrong namespace/direction isolation, Port/listener cleanup, and Firefox direct-store preservation. Authenticated Port disconnect SHALL reject an in-flight request locally rather than hang or acknowledge it. +- [x] 27.4 Run the complete cumulative suite in one cycle: Reviewer source control, all historical lifecycle/fence probes, focused/full Vitest, TypeScript, format/lint, OpenSpec, architecture/protected, Chrome/Firefox builds and bundle audit, then exact-bound Chrome mount/durable storage/source controls plus Firefox dual-origin diagnostics. These remain Coder development diagnostics, not independent acceptance. +- [x] 27.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/886 scope, protected identities, clean/ref-free/unpushed state, original fail-before hashes, real Chrome sender facts, and development-diagnostic hashes. Do not run or claim independent release acceptance. +- [x] 27.6 Route the frozen round 8 exact to fresh independent QA and Reviewer work from zero in parallel. QA task #135 passed exact `df980dd...`, but Reviewer task #136 returned `FINAL BLOCKED` on two same-batch Port-generation lifecycle defects, so no product matrix or publication gate was released. + +## 28. Chrome MV3 PresenceStore Port-Generation Settlement Repair (Round 9) + +Round 8 exact `df980dd596c16411a32e8882ff06f08abd814bdc` completed tasks 27.1-27.6 but received fresh Reviewer `FINAL BLOCKED (P1 x2)`: a late response whose original request Port had detached fell back to `this.active` and crossed into a replacement Port, while a synchronous disconnected-Port send failure removed the disconnect listener and rejected only the current call, leaving another request on that terminal binding hanging. exact df98, exact886, every earlier blocked exact, retained-only bce, QA #135's exact-bound PASS, and all reports/probes/evidence remain immutable. The round 9 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve the complete source-authentication and cumulative lifecycle behavior, and repair both failures as one transport-generation settlement batch. + +- [x] 28.1 Bind Reviewer task #136's report/evidence/probe/config byte-for-byte and reproduce both failures twice on immutable exact df98: old completion reaches the replacement Port, and the older in-flight request times out after a later send throws. +- [x] 28.2 Correlate every provider response only through its original request binding. Removing, disconnecting, or replacing that binding SHALL delete its correlations; a later completion with no live original binding SHALL be dropped and SHALL never fall back to `active` or another Port. +- [x] 28.3 Associate every Offscreen pending operation with its exact Port binding. Disconnect, replacement, provider loss, synchronous send failure, and dispose SHALL reject all pending operations owned by the terminal binding without replay. A response SHALL be admitted only for a live matching pending request on that same binding. Port liveness SHALL satisfy the dedicated adapter's heartbeat preflight locally so only actual one-shot `load`/`save` operations cross the Port and are binding-owned. +- [x] 28.4 Add repository controls for old-binding completion after replacement, concurrent in-flight send failure, exact-once local rejection, legal new-generation recovery, uncorrelated response rejection, source admission, storage rejection, and cleanup. Run the unchanged Reviewer probe plus cumulative focused/full/static/OpenSpec/architecture/protected/build/bundle/browser development diagnostics. +- [x] 28.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/df98 scope, protected identities, clean/ref-free/unpushed state, original fail-before hashes, and development-diagnostic hashes. Do not mutate or publish df98, any blocked exact, PR #69, remote refs, CI, Owner checkout/profile, or downstream evidence. +- [x] 28.6 Route only the frozen round 9 exact to fresh independent QA and Reviewer work from zero in parallel. QA task #137 passed exact `dc31066a...`, but Reviewer task #138 returned `FINAL BLOCKED` for a pre-pending Port-generation migration, so no product matrix or publication gate was released. + +## 29. Chrome MV3 PresenceStore Pre-Pending Generation Repair (Round 10) + +Round 9 exact `dc31066a281f60ed6536a48497116925b1b4a7d3` completed tasks 28.1-28.6 but received fresh Reviewer `FINAL BLOCKED (P1)`: comctx generated the one-shot request ID and response subscription opened Port A before injector `apply` entered the binding-owned pending registry. If A disconnected in that microtask window, the same call opened Port B, posted the original `save(record)` bytes, and resolved without a later application call or new request ID. exact dc31, exact df98, exact886, every earlier blocked exact, QA #137's exact-bound PASS, and all reports/probes/evidence remain immutable. The round 10 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve the complete source-authentication and cumulative lifecycle behavior, and close only this pre-pending generation gap. + +- [x] 29.1 Bind Reviewer task #138's report/evidence/probe/config byte-for-byte and reproduce the pre-send migration twice on immutable exact dc31 as `6 passed / 1 failed`: Port A has zero `apply` posts before disconnect, the same call posts its original `save` bytes to Port B, and that replacement response resolves it. +- [x] 29.2 Reserve one ordered transport generation when comctx registers each request-ID response callback. Generic subscription SHALL open no Port. Local heartbeat callback registration SHALL remove itself before the actual `apply`; the operation SHALL consume its oldest remaining generation reservation. If that generation terminated, the operation SHALL reject before any replacement connect/post. Dispose SHALL likewise reject a prepared first call with zero Port creation. +- [x] 29.3 Preserve round 9 response-to-original-binding correlation, binding-wide pending rejection on disconnect/send failure/dispose, zero replay, stale/uncorrelated response drop, later new-request recovery, exact Offscreen source admission, Chrome background durable authority, Firefox direct store, Runtime callback re-registration, lifecycle/fence/observer/retained/notice behavior, and every protected/public boundary. +- [x] 29.4 Add repository and candidate-bound controls for multiple pre-send calls against a live terminal generation, zero original bytes on a replacement Port, immediate pre-send dispose, all task #136 terminal controls, and later fresh-call/new-ID recovery. Run focused/cumulative/full/static/OpenSpec/architecture/protected/build/bundle/browser development diagnostics and freeze reproducible evidence. +- [x] 29.5 Create exactly one clean detached replacement commit whose sole parent is exact06; freeze exact/tree/parent/one-commit ancestry, patch ID, exact06/dc31 scope, protected identities, clean/ref-free/unpushed state, fail-before hashes, and development-diagnostic hashes. Do not mutate or publish dc31, any blocked exact, PR #69, remote refs, CI, Owner checkout/profile, or downstream evidence. +- [x] 29.6 Route only the frozen round 10 exact to fresh independent QA and Reviewer work from zero in parallel. QA task #139 returned exact-bound `FINAL PASS`, but Reviewer task #140 returned `FINAL BLOCKED (P1)` because terminal pre-send rejection retained each adapter-owned one-shot response entry. The Owner clarified that only provider-owned long-lived callback handles are exempt from application cleanup, so round 10 remains immutable blocked and released no product or publication gate. + +## 30. Chrome MV3 PresenceStore Terminal Response Cleanup (Round 11) + +Round 10 exact `ffb21c792289e458084c0604590460f0385a474a` closed pre-pending cross-generation migration but retained each rejected one-shot response callback in the Offscreen adapter until disposal. exact ffb2, dc31, df98, exact886, every earlier blocked exact, QA #139's PASS, Reviewer #140's report/correction history, and all evidence remain immutable. The round 11 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve every round 10 transport-generation and source-authentication behavior, and close the complete adapter-owned terminal response-entry family without modifying provider-owned long-lived callback lifetime. + +- [x] 30.1 Bind Reviewer task #140's final report/evidence/probe/config byte-for-byte and reproduce its cleanup control twice on immutable exact ffb2 as `6 passed / 1 failed`, with two settled pre-send operations leaving `callbacks.size === 2` instead of zero. +- [x] 30.2 Associate each ordered request preparation with its adapter-owned response entry. On terminal connect failure or generation termination, remove entries for the consumed operation and every queued sibling while retaining their original terminal generation reservations until `sendMessage` rejects them. Preserve pending synthetic rejection, exact binding correlation, zero replacement bytes, no replay, and later fresh-call/new-ID recovery. +- [x] 30.3 Cover the complete terminal family in repository and candidate-bound controls: pre-pending/pending connect failure, post failure, disconnect/terminate, and dispose; same/replacement Port behavior; callback/pending/preparation registries returning to baseline; stale/late/uncorrelated response isolation; on-time settlement; and fresh recovery. Audit the adapter's pending map, preparation queue, Port listeners, active/current generation ownership, provider request map, and absence of timers for another operation-lifetime leak. +- [x] 30.4 Synchronize proposal/design/spec documentation, run focused/cumulative/full/static/OpenSpec/architecture/protected/build/bundle development diagnostics, and freeze exactly one detached clean ref-free unpushed commit whose sole parent is exact06. Record exact/tree/parent/patch ID/scope/blocked ancestry and immutable evidence hashes without mutating PR #69, remotes, CI, Owner checkout/profile, or any blocked exact. +- [x] 30.5 Route only the frozen round 11 exact to fresh independent QA and Reviewer work from zero in parallel. Both gates SHALL assess the full same-domain terminal matrix and all historical blocker controls in one batch. QA task #142 and Reviewer task #143 returned exact-bound PASS, releasing product QA task #145; product QA then blocked exact047 on the grouped-row render defect below. PR replacement, fresh CI, Owner smoke, publication, merge, and release remained frozen. + +## 31. Virtuoso Grouped-Notice Identity Repair (Round 12) + +Round 11 exact `047454768da6f4cfaccc101b06e58ad0f8b8fc29` passed independent QA and Reviewer gates but received product QA `FINAL BLOCKED (P1)`: every run of two or more adjacent SystemNotice messages produced a `NoticeGroup` React element without `props.data`, while `MessageList` dereferenced `item.props.data.id` in Virtuoso's `computeItemKey`. The first grouped row therefore threw during render and unmounted the chat list/composer. exact047, every earlier blocked exact, QA #142, Reviewer #143, product QA #145, and all prior evidence remain immutable. The round 12 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve every round 11 Runtime/adapter/lifecycle byte and behavior, and repair only total stable UI row identity plus its missing Virtuoso-boundary regression. + +- [x] 31.1 Bind product QA task #145's manifest and render evidence, then reproduce the exact production-build `reading 'id'` failure and composer unmount twice on immutable exact047 with two schema-valid adjacent retained notices and zero owned-process residue. +- [x] 31.2 Key every MessageList row through its existing projected React key rather than component-specific props. Anchor a multi-notice group to its first persistent notice ID so extending the run preserves identity; missing keys SHALL fail explicitly, with no array-index, `first`/`last`, or expand/collapse-state fallback. +- [x] 31.3 Add a Virtuoso-boundary integration regression that invokes the configured `computeItemKey` across text, singleton-notice, and two-or-more-notice group rows; prove stable identity when a run extends and collision-free identities when late canonical history splits it. Preserve count/latest/toggle/order and every canonical record unchanged. +- [x] 31.4 Run the complete round 11 terminal/cumulative/focused/full/static/OpenSpec/architecture/protected/build/bundle development diagnostics plus exact-bound production Chrome pass-after, then freeze exactly one detached clean ref-free unpushed commit whose sole parent is exact06. Record exact/tree/parent/patch ID/scope/blocked ancestry and immutable evidence hashes without mutating PR #69, remotes, CI, Owner checkout/profile, or any blocked exact. +- [x] 31.5 Route only the frozen round 12 exact to fresh independent QA and Reviewer work from zero in parallel. QA task #146 returned exact-bound `FINAL PASS`, but Reviewer task #147 returned `FINAL BLOCKED (P1)`: an arbitrary wire-valid text ID could equal a synthetic group ID after a late-history split, producing duplicate Virtuoso identity. exact472 and both verdicts remain immutable; no product or publication gate was released. + +## 32. Disjoint Virtual Row Identity Namespaces (Round 13) + +Round 12 exact `472e061b8e0ccf1818aaa579fe5a61a13abd5b6e` repaired the grouped-row crash and anchored each group to its first persistent notice, but left text and singleton rows on raw IDs. Because wire-valid IDs are opaque bounded strings, a text ID could equal `notice-group:` after late history split a run, leaving three rows with only two Virtuoso identities. exact472, exact047, every earlier blocked exact, QA #146, Reviewer #147, and all prior evidence remain immutable. The round 13 replacement SHALL again be one clean detached single-commit sole child of exact06, preserve every round 12 Runtime and stable-group behavior, and make row kind part of every React/Virtuoso identity. + +- [x] 32.1 Bind Reviewer task #147's final report/evidence/probe/config byte-for-byte and reproduce its prefix-mimicking late-split control twice on immutable exact472 as `3 passed / 1 failed`, with three projected rows yielding only two unique keys. +- [x] 32.2 Project text as `message:`, singleton notice as `single-notice:`, and grouped notice as `notice-group:` before React and Virtuoso. Preserve the first-notice group anchor, missing-key explicit failure, and prohibition on raw-ID, index, edge-flag, or expansion-state fallback. +- [x] 32.3 Extend the Virtuoso-boundary regression with IDs that mimic all three namespace prefixes; cover append, late split, collapse/expand, count, DOM identity, and scroll-anchor behavior while proving every projected identity remains unique and tied to its actual row kind. +- [x] 32.4 Run the complete round 12 terminal/UI/cumulative/focused/full/static/OpenSpec/architecture/protected/build/bundle development diagnostics plus exact-bound production Chrome pass-after, then freeze exactly one detached clean ref-free unpushed commit whose sole parent is exact06. Record exact/tree/parent/patch ID/scope/blocked ancestry and immutable evidence hashes without mutating PR #69, remotes, CI, Owner checkout/profile, or any blocked exact. +- [x] 32.5 Route only the frozen round 13 exact to fresh independent QA and Reviewer work from zero in parallel. QA task #148 and Reviewer task #149 returned exact-specific PASS, releasing product QA task #150. Product QA then returned FINAL BLOCKED under the zero-pageerror contract because the inherited Notification/AppAction RPC collision below produced one unhandled `reading 'apply'` error per delivered remote text. No publication gate was released. + +## 33. Background Service RPC Isolation (Round 14) + +Round 13 exact `b0db28878d014f148e3f260cc07cee953a2db0fc` passed independent QA and Reviewer gates but product QA task #150 returned FINAL BLOCKED: Notification and AppAction providers and injectors shared `browser.runtime.id` as one comctx namespace, so every Notification `push` reached both providers, AppAction resolved an undefined method, and the dropped RPC rejection became one pageerror per delivered remote text. QA task #152 proved exact06 and b0db have the same deterministic inherited behavior and retain exactly-once store/list/barrage projection; Reviewer task #153 independently identified the byte-identical source path. exact b0db, every prior exact/evidence, QA #152, and Reviewer #153 remain immutable. The round 14 replacement SHALL again be one clean detached single-commit sole child of exact06, mechanically preserve the complete 38-path round 13 tree delta, and repair only service routing plus request-local Notification failure containment. + +- [x] 33.1 Bind QA task #152 and Reviewer task #153 as cumulative fail-before: retain QA's frozen b0db browser success, one independent clean-profile b0db success with three delivered texts producing three exact apply errors and exact projections, the unchanged Reviewer probe twice, and the separately classified signaling stalls where no delivery produced no apply error. Preserve all owned-profile/process cleanup evidence. +- [x] 33.2 Define mutually exclusive versioned Notification and AppAction namespaces in one shared internal proxy contract used by both production providers and injectors. Keep heartbeat enabled. Add a symmetric production-adapter control proving one request reaches one provider, yields one response and one side effect, and returns request listeners to the provider baseline for both services. +- [x] 33.3 Consume each rejected Notification `push` Promise at its request boundary with one scoped observable diagnostic while preserving the existing synchronous `PushEvent` and later healthy requests. Add a focused control for one rejected then one successful request with no unhandled rejection and no AppAction, chat list, barrage, input, product-copy, retry, or public-contract change. +- [x] 33.4 Run the complete round 13 terminal/UI/cumulative/focused/full/static/OpenSpec/architecture/protected/build/bundle development diagnostics plus exact-bound production service pass-after. Freeze exactly one detached clean ref-free unpushed commit whose sole parent is exact06; record exact/tree/parent/patch ID, exact06/b0db scope, blocked ancestry, mechanical round-13 equivalence, fail-before hashes, and pass-after hashes without mutating PR #69, remotes, CI, Owner checkout/profile, task #151, or any prior exact/evidence. +- [x] 33.5 QA task #156 and Reviewer task #157 returned exact-specific PASS for frozen round-14 exact `b1fcb668...`. Product QA task #158 independently passed bad-record isolation, Offscreen close/rebuild, source authentication, apply-error-free notification behavior, Chrome artifact/mount, and adversarial row projection, then the Owner stopped the remaining signaling-dependent matrix and moved it to local testing. The incomplete presence, restart, full list/grouping UI, and Firefox scenarios have no final QA verdict. The exact was published and synchronized for Owner testing; this task closeout does not authorize merge or release. + +## 34. Notice Disclosure and Reconnect Feedback + +Owner product testing on 2026-07-27 first superseded task 34.4's shell-level/closed-panel Toaster clause, then task #208 superseded the resulting paint-before-operation and launcher-owned models. Exact `4a26a59e...` restored the original direct `AppMain -> ` and passed a corrected Reviewer report plus `43/43` files and `326/326` tests, but Owner testing then found three remaining presentation authorities: App fixed Runtime readiness output, pre-App bootstrap unavailable/Retry DOM, and `reconnect-toast`/`useReconnectToast` ownership of the shared Toaster lifecycle. Reviewer issued SECOND CORRECTION - FINAL BLOCKED, P0/P1/P2=`0/1/0`, report SHA-256 `fb81f0e11715cbe8e6de23982b67c8482f4ace360054b68beb18c5fb7eaffa43`. Task #215 supersedes that exact and freezes Toast as one generic application capability. Reconnect, Runtime readiness, and unrelated notifications are business sources into generic Toast APIs and the one original AppMain Toaster; no reconnect-specific hook/module/presenter/renderer/lifecycle truth or alternate fixed status UI remains. When App/Toaster is absent, inability to show Toast creates no loading, unavailable, Retry, status, or result fallback and cannot gate bootstrap, Runtime, or reconnect. ChatRoom still owns one authoritative reconnect request, immediate panel-independent leave/join dispatch, duplicate rejection, button spin, operation outcome, and stale fencing; generic presentation may return only bounded paint/absence acknowledgements correlated to that request. Tasks 34.3-34.5 remain historical implementation and automated-gate records; they do not prove the generic Toast ownership contract. Round-17 exact `d2a6364a...` remains immutable and blocked: source call order did not prove rendered feedback, panel remount lost active loading, and unscoped dismissal either missed closed-period terminal feedback or removed unrelated Toasts. Round-18 exact `a74d55b741a2e124aac7006967a9c6f3982ba784` is also immutable and blocked by Reviewer task #182 (`P0/P1/P2=0/1/0`, report SHA-256 `21db7d606bfd349e4c9ca2777ed5bc1f12293eba45324ffe838428991ee02eaf`): Sonner's default fixed positioning escaped completely to the host viewport when the left-side panel settled to `transform: none`; the right-side panel passed only because its retained transform incidentally created a containing block. JSX nesting and isolated Toaster lifecycle controls did not exercise that geometry. Round-19 source exact `2a73231a1b60d3b57f055ea596cc4af085454fa5` remains immutable and blocked by Reviewer task #185 (`P0/P1/P2=0/1/1`, report SHA-256 `08d1e8c727565934cabe1227d0735f9e447a480d7b6c48dd52f8daea8795c1d6`): its panel-local root combined top and bottom bounds with automatic height while retaining pointer authority, so the transparent root covered 82% of the panel and intercepted ordinary controls outside the visible Toast; its current process text was also stale. Round-20 source exact `889bc7faead26ebba8569e4b4e6bb80ce2b2d912` remains immutable and blocked by Reviewer task #189 (`P0/P1/P2=0/1/0`, report SHA-256 `2d95b47701c4e03ec2e5c74375aca0f308b517783e30dfaaeba345fa963c08c3`): unconditional item pointer authority left Sonner's opacity-zero pre-mount and 200ms retirement nodes hit-testable, while inherited `::before`/`::after` pointer authority extended beyond item rectangles during expanded, swipe, and removal states; direct synthetic `.click()` did not exercise hit testing. Round-21 source exact `252885484bf370ea5f2ef0efdac9fc007b7920cc` remains immutable and blocked by Owner product testing, corrected Reviewer task #192 (`P0/P1/P2=0/2/0`, report SHA-256 `f36940646b576d0bd0deaa63983140ff1834065efa7bac982ff4ccc368d50474`), and QA task #194 (`P0/P1/P2=0/1/0`, report SHA-256 `841c8776a61e392cdc218a7c5fff0a80dc3f372d1999043949ee8da9e83a7322`). Its inherited root `width:min(356px, calc(100% - 32px))`, `--width:100%`, and item `width:100%; maxWidth:100%` overrode the accepted content-fit item presentation; fast terminal settlement had no 300ms minimum visible dwell; and eligibility tracking could strand a pre-mount Toast when no later attribute mutation arrived. The earlier Reviewer PASS is withdrawn and cannot release evidence. Tasks 34.6-34.10 are closed only by the final accepted lineage through readiness exact `b13a5e294...`, CI exact `5a7bb4a12...`, fresh Review, exact-bound Actions, Owner acceptance, and PR #69 merge. All five fail-before families remain immutable facts for their exacts, but their launcher/panel geometry, content-fit, pointer, and eligibility remedies belong to superseded presentation models and do not transfer into the restored original Toaster contract. The task #215 replacement follows exactly Coder freeze without gates -> fresh Reviewer logic/diff/scope/architecture review without gates -> same-exact `pnpm test` -> Owner. Create no browser or QA task, gate, matrix, or verdict. Historical Reviewer, test, and QA records remain immutable facts for their own exacts and transfer no evidence. This decision does not reopen notice count, grouping, row identity, Runtime, public API, protocol, persistence, Notification, or WorldRoom behavior. + +Owner product testing on round-27 exact `f4d9c40199e3f73455a16b0194848ec888d7e78c` then found normal Runtime `Ready to chat` success feedback repeating continuously. The loop is deterministic: ClientLease refreshes the page lease and checks host health about every five seconds, Readiness treats the repeated equal ready value as a new transition, application feedback publishes the stable success entry again, and Sonner's approximately four-second success lifetime lets the next callback recreate it. Surface mount/remount also republishes the current ready snapshot. Stable ID update semantics do not make the notification one-shot. Owner task #228 removes the normal Runtime ready success mapping and makes ReadinessDomain the unique mapped-state transition dedup authority. Equal input performs no State write and emits no `StateChangedEvent`; a real `connecting | ready | unavailable` change emits exactly once. ClientLease retains the five-second heartbeat, 15-second page lease TTL, host generation/identity/page-attachment checks, immediate callback, and real recovery; no second filter or readiness truth is added elsewhere. Connecting loading and unavailable error remain; a genuine or mounted-snapshot ready observation performs at most an ID-scoped dismiss of `webchat-runtime-readiness`, and dismiss/auto-close/acknowledgement/settlement cannot republish it. Manual reconnect loading/300ms dwell/success/error, unrelated Toasts, and all protected boundaries remain unchanged. Exact `f4d9c401...` and its Reviewer/source/smoke facts are immutable history and transfer no acceptance. The replacement follows Coder freeze without gates -> fresh Reviewer logic/diff/scope/architecture review without gates -> same-exact `pnpm test` -> Owner, with no browser or QA route. + +- [x] 34.1 Remove the numeric count from grouped SystemNotice presentation while retaining the latest notice as the accessible expand/collapse control anchor and preserving canonical order, group identity, Virtuoso keys, and scroll ownership. +- [x] 34.2 Animate earlier notices through one bounded height/opacity disclosure transition, and honor reduced-motion preference without changing the rendered content or control semantics. +- [x] 34.3 Model one request-local application reconnect lifecycle around the existing `leaveRoom()` then retained `joinRoom(input)` composition. Ignore duplicate activation while active, show one loading toast, dismiss it on every terminal path, and emit the existing failure toast only after dismissal. +- [x] **Superseded by Owner product decision (2026-07-27):** 34.4 Keep the Toast domain and Toaster mounted while the main panel is closed, show a spinning disabled reconnect control while active, and preserve the shared WorldRoom, exact eight-method ChatRoom extern, Runtime, protocol, persistence, and notification behavior. +- [x] 34.5 Add focused count-free disclosure and reconnect success/failure/duplicate controls, then pass full Vitest, TypeScript, strict formatting/lint, OpenSpec validation, architecture checks, and Chrome MV3 plus Firefox MV2 production builds. +- [x] 34.6 Preserve the original generic Toast surface exactly: render one direct `` child inside `AppMain` using the existing Motion translate containing mechanism and original `richColors`, current `themeMode`, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and dark Toast classes. Remove every reconnect wrapper, reconnect-specific Toaster/hook/module/presenter/renderer name, launcher layer, second renderer, and custom geometry/style. The generic surface must continue carrying unrelated application Toasts. +- [x] 34.7 Remove `App.tsx`'s fixed readiness query/output and its view-only icons; remove `content/index.tsx`'s pre-App `WebChat unavailable`/Retry DOM. Retain lifecycle `connecting | ready | unavailable` truth for Runtime recovery and send preconditions. ReadinessDomain is the sole mapped-state transition authority: equal input performs no State write or `StateChangedEvent`, while a changed input emits exactly once. Map normal Runtime connecting to the stable generic loading descriptor and unavailable to the stable generic error descriptor while AppMain/Toaster exists; a genuine ready transition or mounted ready Query may only dismiss `webchat-runtime-readiness` by ID and may never publish a success descriptor. If Toast cannot render, create no alternate loading, unavailable, Retry, status, or result view and never block bootstrap, Runtime, reconnect, or network outcome. +- [x] 34.8 Retain one authoritative reconnect request identity for immediate `leaveRoom()` then `joinRoom(input)`, `joined && !reconnecting` availability, duplicate rejection, panel preservation, button pending/spin, operation outcome, and stale fencing. A generic Toast entry may correlate to that ID for one mounted loading-to-terminal presentation, accepted 300ms visible dwell, bounded absence, and ID-scoped cleanup, but generic Toast cannot become a second reconnect lifecycle or pending truth. Add focused controls for generic presenter business-source isolation, unrelated Toast coexistence, readiness mapping, absence of both fixed status views, deleted reconnect presenter residue, mounted/unmounted/open-during-active behavior, stale callbacks, request-local dismissal, exact Toaster parameters, and unchanged original Motion behavior. +- [x] 34.9 Implement task #214 as a fresh source sole child of task #215's docs exact, carrying forward only the intended source behavior from blocked `4a26a59e...`. Delete `reconnect-toast.tsx`/`useReconnectToast` and replace them only with generic Toast Domain/presentation contracts that have no direct business operation authority; do not change the existing generic Toast Extern/implementation/public API unless separately authorized. Preserve the eight-method ChatRoom extern, Runtime/protocol/persistence/Notification/WorldRoom/dependencies/public APIs and Owner's `.vscode` deletion. Coder freezes identity/scope without tests or gates; fresh Reviewer reviews logic/diff/scope/architecture without gates; after Reviewer PASS, the same exact must pass `pnpm --dir /Users/molvqingtai/Developer/WebChat test` before Owner handoff. Create no browser/QA work and do not touch `develop`, remote/PR, CI, merge, or release. No prior exact or evidence transfers. +- [x] 34.10 From task #228's docs exact, make `ReadinessDomain.SetStateCommand` the single equal-state dedup authority: compare mapped input with current State, return no write/Event for equal values, and update plus emit exactly once for a real `connecting | ready | unavailable` transition. Preserve current Query/immediate replay, and do not add duplicate filtering or a second State in ClientLease, the Readiness adapter, AppFeedback, or Toast presentation. Keep ClientLease's five-second heartbeat, 15-second lease TTL, register/probe/snapshot checks, and real generation/host-id/page-attachment recovery unchanged. Remove the normal Runtime `ready -> Ready to chat` success mapping from both real-transition and mounted-snapshot paths; genuine/mounted ready only ID-scoped-dismisses `webchat-runtime-readiness`, while equal ready health samples reach no AppFeedback command. Preserve connecting loading, unavailable error, manual reconnect success/error, 300ms dwell, button/stale fencing, and unrelated Toasts. Add exact-bound controls for equal values in every state, real transition sequences, initial Query/replay, periodic healthy watchdog lease renewal with zero readiness Event/ChatRoom recovery/Toast, mounted ready dismissal, loading-to-ready scoped cleanup, unrelated Toast coexistence, reconnect success preservation, and zero Runtime `Ready to chat` descriptors. Preserve Toast/public/Runtime/protocol/persistence/Notification/WorldRoom/dependency contracts. Freeze a fresh sole child of this docs exact without gates; route fresh Reviewer without gates, then same-exact `pnpm --dir /Users/molvqingtai/Developer/WebChat test`, then Owner. No browser/QA, remote/PR/develop/merge/release work, or evidence transfer. diff --git a/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/.openspec.yaml b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/.openspec.yaml new file mode 100644 index 00000000..9e5b8a19 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-23 diff --git a/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/design.md b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/design.md new file mode 100644 index 00000000..fa6c8c9b --- /dev/null +++ b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/design.md @@ -0,0 +1,83 @@ +## Context + +At clean `develop@b140b68dc8b2635e95ade977dfa504c94b7663c2`, root `package.json` defines `check` as `tsc --noEmit` and `postinstall` as `wxt prepare`. The CI linter job installs dependencies, runs formatter/linter fixes plus `git diff --exit-code`, and then runs `pnpm run check`. A restored dependency tree does not guarantee that the current checkout's ignored `.wxt/tsconfig.json` exists or that the root lifecycle regenerated it. + +The independent Archify PR #73 has immutable head `5ee76d1a195f26958e0866df87aa97ece4e28a22`. Its fresh CI and the current `develop` CI both fail type checking with the same missing generated WXT environment and unresolved `#imports`/existing `@/...` aliases. The Archify exact does not touch the affected root WXT/TypeScript contract, so it must stay frozen while the base is repaired separately. + +## Goals / Non-Goals + +**Goals:** + +- Make the canonical TypeScript gate self-contained in fresh and cache-restored environments. +- Preserve strict `tsc --noEmit` behavior and existing aliases. +- Keep one local/CI entry point and one minimal source delta. +- Preserve ignored generated-output ownership. +- Unblock an exact-bound CI rerun for the unchanged Archify head after the base repair lands. + +**Non-Goals:** + +- Workflow or dependency-cache redesign. +- TypeScript config relaxation, import rewrites, source changes, dependency updates, or committed `.wxt` output. +- Any Archify, Runtime, protocol, persistence, product, browser, or UI change. +- Any merge or release decision. + +## Decisions + +### 1. The canonical check script owns WXT preparation + +Root `package.json` SHALL define the exact script: + +```json +"check": "wxt prepare && tsc --noEmit" +``` + +The left side creates the checkout-specific ignored WXT TypeScript environment; shell short-circuiting ensures TypeScript runs only after successful preparation. The existing CI command `pnpm run check` therefore gains the same readiness behavior as a local invocation without a duplicated workflow-only sequence. + +Alternative rejected: add only a CI workflow step. That leaves local and other automation entry points dependent on hidden order and creates two typecheck contracts. + +Alternative rejected: depend only on `postinstall`. Cache restoration or an already-installed dependency tree can leave the current checkout without generated WXT state. + +### 2. Keep postinstall but treat it as an optimization, not authority + +The existing `postinstall: wxt prepare` MAY remain unchanged for normal installation ergonomics. It does not satisfy or replace the check script's own preparation requirement. + +Alternative rejected: remove `postinstall` in this repair. Its broader developer/build implications are unnecessary to resolve the gate. + +### 3. Generated WXT state remains disposable and untracked + +`.wxt/**` remains ignored and SHALL NOT enter the commit, cache key authority, or review surface. Verification deletes or starts without `.wxt`, runs the canonical check, proves `.wxt/tsconfig.json` was created, and confirms the tracked worktree remains clean. + +Alternative rejected: commit `.wxt/tsconfig.json`. It is generated checkout state and would couple source history to WXT output. + +### 4. Preserve failure semantics and quality gates + +If `wxt prepare` fails, the gate fails without running TypeScript. If `tsc --noEmit` finds a real error, that error remains visible and fails the gate. No skip, waiver, alias rewrite, error suppression, or `continue-on-error` is allowed. Existing format/lint fix-plus-diff behavior and Chrome/Firefox build gates remain unchanged. + +### 5. Keep remediation and publication histories independent + +The implementation candidate is a sole child of clean `develop@b140b68...` with exactly root `package.json` as its non-OpenSpec source/config delta. It is reviewed, pushed, and proposed through its own PR. It must not amend, rebase, merge into, or become part of the immutable Archify head or Runtime ancestry. + +After explicit Owner authorization merges the base repair into `develop`, PR #73 keeps head `5ee76d1...` unchanged and receives a fresh CI run against the repaired base. PR #73 merge remains a separate explicit Owner decision. + +## Risks / Trade-offs + +- [Preparation adds time to every typecheck] -> Accept the small deterministic cost in exchange for a self-contained gate. +- [WXT preparation changes ignored files] -> Assert `.wxt` remains ignored and tracked diff stays clean across repeated checks. +- [Preparation failure hides TypeScript output] -> This is intentional ordering; report the earlier causal failure rather than running against invalid generated state. +- [A broader CI issue appears after this fix] -> Stop and report the new exact failure; do not widen this candidate. +- [Archify PR is treated as fixed by inheritance alone] -> Require a fresh PR #73 CI run against the repaired `develop` base; no verdict transfers. + +## Migration Plan + +1. Freeze the clean `develop` identity and reproduce the current missing-`.wxt` failure without modifying source. +2. Create a detached clean child and change only root `package.json` `check` to the exact frozen script. +3. Starting without `.wxt`, run the canonical check twice and verify generated readiness, TypeScript success, and zero tracked diff. +4. Run repository format/lint checks, OpenSpec strict validation, Chrome and Firefox builds, and diff/scope checks on the candidate. +5. Freeze one immutable exact, route independent Reviewer validation, then push/open a separate base-remediation PR and run fresh exact-bound CI. +6. Ask the Owner for explicit merge authorization only after all gates pass. After merge, rerun PR #73 CI against the repaired base without changing its head. + +Rollback is code-only: revert the one root script change. No user data, extension state, protocol, schema, or migration is involved. + +## Open Questions + +None. The repair is intentionally limited to making the existing strict typecheck gate establish its required WXT generated environment. diff --git a/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/proposal.md b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/proposal.md new file mode 100644 index 00000000..4706de8c --- /dev/null +++ b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/proposal.md @@ -0,0 +1,47 @@ +## Why + +The `develop` CI typecheck can run after `node_modules` has been restored without a usable generated `.wxt/tsconfig.json`. The current `check` script invokes `tsc --noEmit` directly and therefore assumes that an earlier install lifecycle generated WXT's TypeScript environment. That hidden precondition makes the same repository exact fail with unresolved `#imports` and existing `@/...` aliases in CI. + +This inherited base failure also blocks the independent Archify PR even though its 55-path tooling delta does not touch WXT, TypeScript, application source, or the root workflow. The canonical typecheck gate must establish its own generated prerequisite so fresh and cache-restored environments behave the same. + +## What Changes + +- Make the canonical root `check` script run the installed WXT prepare command before `tsc --noEmit`. +- Freeze the exact script contract as `wxt prepare && tsc --noEmit`. +- Keep CI invoking `pnpm run check` so local and CI type checking use one self-contained gate. +- Keep the existing `postinstall` WXT preparation, but do not rely on install lifecycle execution as the only typecheck prerequisite. +- Keep `.wxt/**` generated, ignored, and uncommitted. +- Deliver the repair as an independent clean-`develop` child and separate PR before re-running the unchanged Archify PR against the repaired base. + +## Capabilities + +### New Capabilities + +- `wxt-typecheck-readiness`: Self-contained WXT generation and TypeScript gate behavior across fresh and cache-restored environments. + +### Modified Capabilities + +- None. + +## Impact + +- Root `package.json` `check` script only for source implementation. +- New OpenSpec authority files for this repair. +- No application source, root workflow, dependency, lockfile, WXT configuration, generated `.wxt`, Archify, Runtime, protocol, persistence, browser behavior, or UI change. + +## Non-Goals + +- No TypeScript relaxation, alias rewrite, source import rewrite, skip, waiver, `continue-on-error`, or generated-file commit. +- No dependency or WXT version change. +- No cache redesign, CI workflow refactor, or unrelated quality-tooling change. +- No amendment, rebase, or scope expansion of Archify exact `5ee76d1...` or Runtime PR #69. +- No merge or release authorization. + +## Acceptance Criteria + +- With `.wxt` absent and installed dependencies present, `pnpm run check` first generates the WXT TypeScript environment and then passes `tsc --noEmit` on the unchanged base source. +- A WXT prepare failure prevents TypeScript from running and fails the gate; a TypeScript failure remains visible and fails the gate. +- Repeated `pnpm run check` calls are successful and do not create a tracked diff. +- `.wxt/tsconfig.json` exists after preparation but `.wxt/**` remains ignored and absent from the candidate commit. +- The source candidate is a clean, detached, unpushed, no-ref sole child of `develop@b140b68dc8b2635e95ade977dfa504c94b7663c2` whose production/config delta is exactly root `package.json`. +- Fresh independent review and exact-bound CI pass before the base repair may be merged; merge requires explicit Owner authorization. diff --git a/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/specs/wxt-typecheck-readiness/spec.md b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/specs/wxt-typecheck-readiness/spec.md new file mode 100644 index 00000000..254bf917 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/specs/wxt-typecheck-readiness/spec.md @@ -0,0 +1,76 @@ +## ADDED Requirements + +### Requirement: Canonical TypeScript gate prepares WXT state + +The root `check` script SHALL be exactly `wxt prepare && tsc --noEmit`. The installed WXT prepare command SHALL settle successfully before strict TypeScript checking begins. CI and local automation SHALL invoke this canonical script rather than depend on an earlier install lifecycle as the only WXT-generation prerequisite. + +#### Scenario: Typecheck starts without generated WXT state + +- **WHEN** installed dependencies are present but `.wxt` is absent +- **THEN** `pnpm run check` SHALL generate `.wxt/tsconfig.json` and then run `tsc --noEmit` successfully against the unchanged source + +#### Scenario: Typecheck starts with existing generated WXT state + +- **WHEN** `.wxt` already contains current generated state +- **THEN** `pnpm run check` SHALL safely refresh that state and run the same strict TypeScript gate + +#### Scenario: WXT preparation fails + +- **WHEN** `wxt prepare` rejects or exits non-zero +- **THEN** the canonical check SHALL fail immediately and SHALL NOT report a successful or independently executed TypeScript gate + +#### Scenario: TypeScript detects a real error + +- **WHEN** WXT preparation succeeds but `tsc --noEmit` reports a type error +- **THEN** the canonical check SHALL fail with that error and SHALL NOT suppress, waive, or rewrite it + +### Requirement: Generated WXT state remains disposable + +`.wxt/**` SHALL remain ignored generated checkout state. The repair SHALL NOT commit `.wxt/tsconfig.json` or any other generated WXT file, and repeated canonical checks SHALL leave no tracked diff. + +#### Scenario: Review generated output ownership + +- **WHEN** `pnpm run check` creates or refreshes `.wxt` +- **THEN** `.wxt/**` SHALL remain ignored, absent from the candidate commit, and removable before the next successful check + +#### Scenario: Repeat the canonical check + +- **WHEN** the canonical check is run twice on one unchanged checkout +- **THEN** both runs SHALL pass and the second run SHALL leave the tracked worktree clean + +### Requirement: Install lifecycle is not the typecheck authority + +The existing `postinstall` WXT preparation MAY remain for installation ergonomics, but a successful or previously cached install SHALL NOT be treated as proof that the current checkout is ready for TypeScript. The canonical check SHALL establish readiness itself. + +#### Scenario: Dependencies are restored from cache + +- **WHEN** `node_modules` is restored or already present while the checkout has no `.wxt` +- **THEN** `pnpm run check` SHALL not require lifecycle replay and SHALL prepare the current checkout before TypeScript + +### Requirement: Base remediation remains isolated + +The implementation SHALL be a clean `develop@b140b68dc8b2635e95ade977dfa504c94b7663c2` child whose only non-OpenSpec source/config change is root `package.json`. It SHALL NOT change the workflow, lockfile, dependency versions, TypeScript configuration, WXT configuration, application source, Archify files, Runtime files, protocol, persistence, browser behavior, or UI. + +#### Scenario: Inspect implementation scope + +- **WHEN** the candidate is compared with its clean `develop` parent +- **THEN** the executable/config delta SHALL contain only the exact root `package.json` check-script change and SHALL contain no generated `.wxt` file + +#### Scenario: Encounter another CI failure + +- **WHEN** a fresh gate exposes a failure not caused by missing WXT preparation +- **THEN** this candidate SHALL remain unchanged and the new failure SHALL be reported as a separate blocker rather than suppressed or folded into scope + +### Requirement: Publication gates remain exact-bound + +The base-remediation candidate SHALL pass independent review and fresh exact-bound CI before merge authorization is requested. Merging the base repair SHALL NOT transfer a PASS to the unchanged Archify head; PR #73 SHALL receive a fresh CI run against the repaired `develop` base. Each merge requires separate explicit Owner authorization. + +#### Scenario: Base remediation passes CI + +- **WHEN** the independent candidate reaches a successful exact-bound CI terminal state +- **THEN** the team MAY ask the Owner for that base-remediation PR's merge authorization without authorizing Archify or Runtime merge + +#### Scenario: Re-evaluate Archify after base repair + +- **WHEN** the repaired base is merged into `develop` +- **THEN** PR #73 SHALL retain exact head `5ee76d1a195f26958e0866df87aa97ece4e28a22` and run fresh CI against the new base before its own merge authorization can be requested diff --git a/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/tasks.md b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/tasks.md new file mode 100644 index 00000000..4e072966 --- /dev/null +++ b/openspec/changes/archive/2026-07-27-repair-wxt-typecheck-readiness/tasks.md @@ -0,0 +1,29 @@ +> **Completion status (2026-07-28):** The base repair completed on immutable exact `56ebff521299b3644d7bf3b2a4858958388e2918` after fresh independent Review PASS and exact-bound CI. PR #74 merged into `develop` through `e62c15b2f64f7ae8cb75bbfedcc5519340cf10fe`, and merge-triggered run `29999281047` passed. Unchanged Archify head `5ee76d1a195f26958e0866df87aa97ece4e28a22` then received fresh repaired-base validation and PR #73 merged through `28ff830ff7b52cb5cea4a97ed9b6ca655de1129c`. These records close only the WXT/Archify route and transfer no verdict to unrelated Runtime candidates. + +## 1. Baseline And FAIL-Before + +- [x] 1.1 Record clean `develop@b140b68dc8b2635e95ade977dfa504c94b7663c2` identity, tree, branch/ref state, workflow/package hashes, and the failed base/Archify CI run identities. +- [x] 1.2 In a disposable clean checkout with installed dependencies and `.wxt` absent, run the unchanged `pnpm run check` twice and preserve the unresolved `#imports`/existing-alias FAIL-before plus tracked-clean status. +- [x] 1.3 Prove the failure is absent from the 55 allowed Archify paths and do not transfer any Archify/Runtime verdict to this candidate. + +## 2. Minimal Repair + +- [x] 2.1 Create a detached clean child of `b140b68...` and change only root `package.json` `check` from `tsc --noEmit` to exact `wxt prepare && tsc --noEmit`. +- [x] 2.2 Keep `postinstall`, `.gitignore`, workflow, lockfile, dependency versions, TypeScript/WXT config, application source, Archify, Runtime, protocol, persistence, browser, and UI bytes unchanged. +- [x] 2.3 Add no generated `.wxt` path, skip, waiver, `continue-on-error`, alias rewrite, source workaround, or cache-specific branch. + +## 3. Candidate Verification + +- [x] 3.1 Remove ignored `.wxt`, run `pnpm run check`, and prove WXT creates `.wxt/tsconfig.json` before strict TypeScript passes. +- [x] 3.2 Run the canonical check a second time and prove successful idempotence, zero tracked diff, and no committed `.wxt` file. +- [x] 3.3 Prove failure propagation with a disposable WXT-prepare failure control and a disposable real TypeScript-error control without retaining fixture changes. +- [x] 3.4 Run applicable format/lint fix-plus-diff and read-only checks, Chrome and Firefox production builds, OpenSpec target/all strict/status/doctor, and `git diff --check` on frozen inputs. +- [x] 3.5 Freeze one clean, detached, unpushed, no-ref sole child with exact/tree/parent, one-path executable scope, patch hashes, fail-before/final evidence, and unchanged protected-input hashes; then STOP. + +## 4. Independent Review And Publication + +- [x] 4.1 Planner reviews only the requirements/decomposition authority; Planner does not issue a source verdict. +- [x] 4.2 Reviewer independently audits the frozen source exact, test sensitivity, one-path scope, failure propagation, unchanged gates, and evidence. Only `FINAL PASS` releases publication. +- [x] 4.3 Push the immutable exact on an independent branch, open a separate `develop`-base PR, and track fresh exact-bound CI to terminal success without merge. +- [x] 4.4 After successful review/CI, ask the Owner for explicit base-remediation merge authorization. Commit, push, and PR update require no separate authorization. +- [x] 4.5 After the repair merges to `develop`, keep PR #73 head exactly `5ee76d1a...`, run fresh CI against the repaired base, and keep Archify merge separately unauthorized until its own exact-bound gates and Owner decision. diff --git a/openspec/config.yaml b/openspec/config.yaml new file mode 100644 index 00000000..392946c6 --- /dev/null +++ b/openspec/config.yaml @@ -0,0 +1,20 @@ +schema: spec-driven + +# Project context (optional) +# This is shown to AI when creating artifacts. +# Add your tech stack, conventions, style guides, domain knowledge, etc. +# Example: +# context: | +# Tech stack: TypeScript, React, Node.js +# We use conventional commits +# Domain: e-commerce platform + +# Per-artifact rules (optional) +# Add custom rules for specific artifacts. +# Example: +# rules: +# proposal: +# - Keep proposals under 500 words +# - Always include a "Non-goals" section +# tasks: +# - Break tasks into chunks of max 2 hours diff --git a/openspec/specs/peer-wire-protocol/spec.md b/openspec/specs/peer-wire-protocol/spec.md new file mode 100644 index 00000000..d7b83e12 --- /dev/null +++ b/openspec/specs/peer-wire-protocol/spec.md @@ -0,0 +1,269 @@ +# peer-wire-protocol Specification + +## Purpose + +TBD - created by archiving change refactor-to-shared-webrtc-runtime. Update Purpose after archive. + +## Requirements + +### Requirement: Public protocol module is pure and explicitly bounded + +The code-level public module `src/protocol/index.ts` SHALL be the third-party-facing peer contract without introducing a package, publishing flow, or SDK. Its wire structures SHALL be exactly the Owner-frozen `ChatUser`, `ChatSession`, `HLC`, `MentionedUser`, `SessionMessage`, `SessionEndMessage`, `TextMessage`, `ReactionMessage`, `ChatMessage`, `HistoryCursor`, `HistoryRequestMessage`, `HistoryResponseMessage`, `ChatRoomMessage`, `ChatSite`, and `WorldRoomMessage` contracts. It SHALL additionally export only their strict schemas, pure parse/check/validation, public limits/constants, and the public codec surface (`WireCodec`, `NativeWireCodec` reference implementation, `WireCodecError`). It SHALL NOT add or rename any field/type, export a structural alias or compatibility DTO, or expose an optional/open metadata bag without explicit Owner intervention. Pure validation SHALL cover closed-union and unknown-key rejection, field/resource limits, mention `ranges`, user/message size, origin-only and uniqueness rules, history-response reference completeness, and explicit-`now` HLC rules. The `NativeWireCodec` SHALL own only the fixed codec/security algorithm; the public protocol SHALL NOT export local persistence/UI models, projections, ordering implementations, Runtime lifecycle or page-host RPC contracts, WirePipeline queue/drop/apply/flush types, or application orchestration. + +`src/protocol/**` SHALL NOT depend on `domain/runtime`, `service`, `app`, UI, storage, comctx, browser-extension APIs/globals (`chrome.*`/`browser.*`), DOM/window/document, host lifecycle APIs, or app configuration. The public `NativeWireCodec` MAY use the standard Web codec APIs it implements (`CompressionStream`, `DecompressionStream`, `Blob`, `ReadableStream`, `TextEncoder`, and `TextDecoder`) and exactly the two scoped `core-js` imports; no whole-package polyfill is permitted. Protocol-owned limits and pure byte utilities SHALL be defined within the protocol boundary. Runtime and Domain code SHALL depend on the public protocol one way; the protocol SHALL NOT import Runtime or Domain code. + +The normative public structures SHALL be byte-for-byte equivalent to these declarations: + +```ts +interface ChatUser { + id: string + name: string + avatar: string +} +interface ChatSession { + sessionId: string + user: ChatUser +} +interface HLC { + timestamp: number + counter: number +} +interface MentionedUser extends ChatUser { + ranges: [number, number][] +} +interface SessionMessage extends ChatSession { + type: 'session' + presenceId: string +} +interface SessionEndMessage { + type: 'session-end' + presenceId: string +} +interface TextMessage { + type: 'text' + id: string + hlc: HLC + userId: string + body: string + mentions: MentionedUser[] +} +interface ReactionMessage { + type: 'reaction' + id: string + hlc: HLC + targetId: string + userId: string + reaction: 'like' | 'hate' + active: boolean +} +type ChatMessage = TextMessage | ReactionMessage +interface HistoryCursor { + hlc: HLC + id: string +} +interface HistoryRequestMessage { + type: 'history-request' + syncId: string + before?: HistoryCursor +} +interface HistoryResponseMessage { + type: 'history-response' + syncId: string + users: ChatUser[] + messages: ChatMessage[] + done: boolean +} +type ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage +interface ChatSite { + origin: string + title?: string + icon?: string + description?: string +} +interface WorldRoomMessage extends ChatSession { + sites: ChatSite[] +} +``` + +#### Scenario: Public entry has no local or internal exports + +- **WHEN** a consumer imports from `@/protocol` +- **THEN** it SHALL see only the documented peer wire types, schemas, validators, `WireCodec` interface, `NativeWireCodec` reference implementation, `WireCodecError`, and protocol constants; `LocalRecord`, UI models, projections, Runtime RPC, queue/drop types, and internal Runtime symbols SHALL not be reachable + +#### Scenario: Public protocol dependency direction + +- **WHEN** the protocol dependency graph is inspected +- **THEN** every protocol dependency SHALL remain cross-target compatible, and no reverse import from protocol into Runtime, Domain, storage, service, app, UI, comctx, browser-extension APIs/globals, DOM/window/document, host lifecycle APIs, or app configuration SHALL exist; standard Web codec APIs used by `NativeWireCodec` are allowed + +### Requirement: Current v2 peer wire remains immutable during the Runtime architecture replacement + +Except for the Owner-authorized logical-presence exception below, the Remesh DDD + CQRS Runtime replacement SHALL NOT change the current v2 peer message types, field names, strict schemas, parser behavior, room namespaces, limits, codec algorithm, reference codec exports, canonical JSON, or encoded golden bytes from baseline `9c90bb0...`. The exception adds required opaque `presenceId` to `SessionMessage` and the strict `SessionEndMessage`; it changes no `ChatMessage`, history, World, room namespace, limit, or codec algorithm. The internal page-to-Runtime comctx control contract and Runtime Domain CQRS surface MAY change cleanly because neither is peer wire, but those changes SHALL NOT add a peer field, alias, envelope, fallback, compatibility path, or different canonical encoding. Protected-input and golden-byte checks SHALL bind this immutability to the exact baseline files and fixtures. + +#### Scenario: Internal CQRS refactor preserves peer bytes + +- **WHEN** the same canonical Chat and World values are encoded before and after the Runtime architecture replacement +- **THEN** their schemas, validation result, canonical JSON, room selection, and final encoded bytes SHALL be identical to baseline `9c90bb0...` + +#### Scenario: comctx is not peer protocol + +- **WHEN** internal page-host Commands, Events, snapshots, or subscription contracts change to express the new Domain graph +- **THEN** no corresponding peer-wire field, message type, schema branch, codec change, alias, or version bridge SHALL be introduced + +### Requirement: Transport delivers trusted context + +`WireDomain` SHALL receive inbound data only from the Runtime-private `RoomTransportExtern`, which delivers `(roomId, sourcePeerId, rawPayload)`. It SHALL compose the public `WireCodec` contract and validators; production/default wiring MAY use the `NativeWireCodec` reference implementation. Fatal UTF-8 decoding and bounded `readLimited` belong to that public codec implementation. `WireDomain` SHALL bind routing and session decisions to the provider-supplied `roomId` and `sourcePeerId`, and SHALL NOT trust any peer identity, room, or sender field self-reported inside a payload. It SHALL emit only strict, typed Events to the Runtime; raw frames and unvalidated decoded values SHALL not cross the Domain boundary. The public protocol module itself SHALL contain no transport routing, queue orchestration, provider type, or Runtime Extern. + +#### Scenario: Payload self-reports identity + +- **WHEN** a payload contains a peerId, room, or sender field, whether conflicting with or matching the transport context +- **THEN** the adapter SHALL reject the entire source-local frame as a forbidden envelope instead of stripping or ignoring the claim; accepted messages use the transport-provided `roomId` and `sourcePeerId` + +#### Scenario: Fixed codec representation + +- **WHEN** wire payloads are encoded or decoded +- **THEN** the public `NativeWireCodec` reference implementation SHALL provide `base64(deflate(UTF8(JSON)))`, native `CompressionStream`/`DecompressionStream`, fatal UTF-8 decoding, bounded `readLimited`, and a direct runtime dependency on exactly these scoped `core-js` global-patch imports: `core-js/actual/typed-array/from-base64` and `core-js/actual/typed-array/to-base64`. Business code SHALL call standard `Uint8Array.fromBase64(value, {alphabet:'base64', lastChunkHandling:'strict'})`, re-encode with `bytes.toBase64()`, and accept only exact equality with the input; no whole-package entry, pure export, JSONR special-value layer, or interop adapter SHALL exist + +### Requirement: WireDomain composes the public codec and typed message boundary + +The public protocol SHALL export `WireCodec` as the codec interface, `NativeWireCodec` as the reference implementation, and `WireCodecError` for codec failures. `WireDomain` SHALL expose a typed send Command whose value is logically `{roomId, targetPeerIds?, message}` and typed accepted-message Events whose value is logically `{roomId, sourcePeerId, message}`; exact local symbol names MAY follow the concise Runtime directory context. It SHALL compose the public `WireCodec` contract and validators; production/default wiring MAY use the public `NativeWireCodec` reference implementation. It SHALL enforce operational queue/drop/apply/flush policies, select parsers by trusted `roomId`, perform strict schema parsing, and bind source context. Queue/drop types SHALL remain private Wire State; concurrency/resource scheduling belongs to its named Runtime owner and SHALL NOT become a public protocol export. Other Runtime Domains SHALL receive typed Wire Events only and SHALL NOT receive `decoded: unknown`, encoded strings, provider callbacks, or Artico values. `targetPeerIds` SHALL remain optional in the logical send value; omission SHALL mean room broadcast. `sourcePeerId` SHALL always be the single transport-confirmed inbound source. + +#### Scenario: Typed inbound boundary + +- **WHEN** an inbound frame is accepted +- **THEN** `WireDomain` SHALL emit a typed message fact together with `{roomId, sourcePeerId}` and SHALL not expose an unvalidated decoded value or encoded frame to another Domain + +#### Scenario: Symmetric CQRS boundary + +- **WHEN** Runtime code sends or receives a wire message +- **THEN** it SHALL use the paired typed send Command and accepted-message Event values with `targetPeerIds`/`sourcePeerId` semantics; provider lifecycle callbacks SHALL enter as independent Wire Commands/Events rather than a public imperative adapter + +### Requirement: Wire messages are strict closed unions with limits + +The public protocol SHALL define the closed schemas, pure limits, and malformed-input validation. `WireDomain` SHALL call those validators and may apply source-local operational policies, but queue/drop/apply/flush scheduling, rate-limited logging, reconnect behavior, and delivery admission are not public protocol semantics. + +Chat wire messages SHALL form a strict, closed discriminated union keyed by `type`; World wire payloads SHALL use one strict schema selected by trusted v2 `roomId` and SHALL NOT carry a payload `type`. The public protocol SHALL export and enforce these fixed limits: `MAX_WIRE_BYTES = 64KiB` for final encoded frames, `MAX_DECODED_JSON_BYTES = 256KiB` for streaming decompressed JSON before parse, `MAX_CHAT_EVENT_BYTES = 48KiB` for one canonical message, `MAX_USER_BYTES = 8KiB` for one `ChatUser` JSON value, and at most 100 messages in one history response. Every string, array, nesting depth, and final encoded byte size SHALL have an explicit public limit. Unknown types, unknown keys, forbidden envelope/context fields, non-canonical or malformed Base64, limit violations, and malformed payloads SHALL produce a public validation failure for the complete frame; the Runtime decides how to drop or log that failure. + +#### Scenario: Unknown or oversized message + +- **WHEN** the public validator receives an unknown type or a message exceeding `MAX_WIRE_BYTES = 64KiB` +- **THEN** it SHALL reject the complete message before any Runtime application, without specifying queue, retry, reconnect, or logging behavior + +#### Scenario: Decompression and field resource limits + +- **WHEN** decompression would produce more than `MAX_DECODED_JSON_BYTES = 256KiB`, one `ChatUser` JSON value exceeds `MAX_USER_BYTES = 8KiB`, or one canonical `ChatMessage` exceeds `MAX_CHAT_EVENT_BYTES = 48KiB` +- **THEN** the public codec/validator SHALL reject the complete frame before application, without prescribing Runtime queue, retry, reconnect, or logging behavior + +#### Scenario: Redundant envelope fields + +- **WHEN** any wire type is defined +- **THEN** its strict schema SHALL reject a frame carrying room, sender peerId, version, sentAt, receivedAt, or any other forbidden envelope/context field; `receivedAt` exists only as receiver-local metadata and no field SHALL be stripped or tolerated + +### Requirement: HLC is strictly validated + +Hybrid Logical Clock values on wire `ChatMessage` values SHALL be finite non-negative safe integers for both timestamp and counter. The public validator SHALL receive the receiver's current time as an explicit `now` argument; it SHALL NOT call `Date.now()` or any hidden clock. An event whose HLC timestamp exceeds `now` by more than 5 minutes SHALL produce a validation failure. The protocol SHALL define the canonical total-ordering, cursor, and last-writer-wins rule as composite `(hlc, id)`; the comparison implementation belongs to the application/page Domain/model layer, or a shared Domain/model module when both pages and Runtime consume it. + +#### Scenario: Future-poisoned event + +- **WHEN** the public validator receives an event with an HLC timestamp more than 5 minutes in the future and an explicit `now` +- **THEN** it SHALL return a validation failure without calling a hidden clock or mutating the caller's clock state + +#### Scenario: Same-clock different messages + +- **WHEN** two messages share an HLC timestamp and counter but have different ids +- **THEN** they SHALL be ordered and deduplicated by `(hlc, id)`, never by HLC alone + +### Requirement: World presence is a full peer snapshot + +World presence wire data SHALL be exactly `WorldRoomMessage extends ChatSession {sites: ChatSite[]}`, where `ChatSession = {sessionId, user: ChatUser}` and `ChatSite = {origin, title?, icon?, description?}`. It SHALL have no payload `type`. The trusted World `roomId` SHALL select this strict parser. World and Chat SHALL use the same structures while maintaining separate session instances and room protocols. The public schema SHALL require a complete `sites` array and SHALL reject unknown or forbidden fields. Runtime registry aggregation, snapshot publication, source replacement, peer leave handling, and per-domain counting are specified by the `world-room-presence` capability. `host`, `hostname`, and `href` SHALL NOT appear on the wire. + +#### Scenario: World snapshot wire shape + +- **WHEN** a peer publishes World presence +- **THEN** the payload SHALL contain exactly `sessionId`, `user`, and `sites`, with each site limited to `origin` and optional `title`, `icon`, and `description`; the payload SHALL not contain a discriminator or page URL fields + +### Requirement: Chat wire uses immutable typed messages + +Chat wire SHALL be exactly `ChatRoomMessage = SessionMessage | SessionEndMessage | ChatMessage | HistoryRequestMessage | HistoryResponseMessage`, where `ChatMessage = TextMessage | ReactionMessage`, `SessionMessage extends ChatSession {type:'session', presenceId:string}`, `SessionEndMessage = {type:'session-end', presenceId:string}`, and `ChatSession = {sessionId, user:ChatUser}`. `ChatUser` SHALL be exactly `{id,name,avatar}`. `MentionedUser extends ChatUser` and SHALL add exactly `ranges: [number, number][]`. Each pair SHALL be an inclusive `[start,end]` range in JavaScript string/UTF-16 code-unit indices with non-negative integers and `start <= end < body.length`. Text and reaction messages SHALL be immutable once created, and live fields SHALL use `userId`; Runtime session binding and application are specified by `webrtc-runtime`. + +#### Scenario: Chat union and text shape + +- **WHEN** a peer sends a Chat message +- **THEN** the strict wire union SHALL accept only the exact frozen fields; each mention SHALL contain exactly `id`, `name`, `avatar`, and `ranges`, each range SHALL be one inclusive valid `[start,end]` pair, and the canonical text message SHALL remain within `MAX_CHAT_EVENT_BYTES = 48KiB` + +#### Scenario: Reaction is explicit state + +- **WHEN** a peer sends a reaction message +- **THEN** it SHALL carry `userId` and `active: boolean` plus the documented target/reaction/HLC/id fields + +### Requirement: Chat presence uses causal generation and final-end facts + +Every `SessionMessage` SHALL carry a required opaque `presenceId` identifying one logical online generation independently of physical `sessionId` and transport `sourcePeerId`. A reconnect, refresh, recovery, reattach, duplicate publication, additional physical session, or supported Runtime host replacement SHALL reuse the active generation. Only an initial join or a return after the prior generation ended SHALL allocate a new generation. `SessionEndMessage` SHALL carry exactly `type:'session-end'` and that generation's `presenceId`; its strict schema SHALL reject missing or unknown fields. No old SESSION decoder, optional-field fallback, alias, dual schema, or compatibility bridge SHALL exist. + +A graceful final release SHALL first durably replace the private active lease with the same generation's unsettled final-end identity, then publish the end fact on the source-ordered Wire lane. Retirement persistence rejection SHALL send no end and preserve the active generation plus physical membership. The durable identity SHALL remain present throughout every unsettled first or retry send. Explicit end-send rejection SHALL durably mark that generation retryable; a same-host retry SHALL durably mark the same identity in flight again before resending the idempotent end. A same-user replacement that loads either unsettled marker SHALL continue that exact END transaction with the same `presenceId`; it SHALL NOT expose the generation as a successful active join or let it carry live messages. Successful send settlement SHALL durably replace the unsettled marker with private settled-cleanup ownership before removing the marker. A replacement that loads settled-cleanup ownership SHALL only retry marker removal and SHALL publish neither SESSION nor SESSION_END. Only successful marker removal may physically leave the Chat room. A failed transition or cleanup SHALL retain a safe durable identity and physical membership still owned by that host. Complete cleanup SHALL leave no persistent retry marker. Receivers SHALL apply duplicate SESSION and SESSION_END facts idempotently, reject SESSION after its accepted end, and classify logical joins/leaves from generation/end facts rather than wall clocks, debounce, transport loss, `sourcePeerId`, or physical `sessionId` conventions. `ChatMessage`, history, and World shapes remain unchanged. + +#### Scenario: Transport recovery reuses a generation + +- **WHEN** one logical user loses and restores a physical transport, refreshes, reattaches, or replaces a supported Runtime host without a final generation end +- **THEN** every replacement SESSION SHALL carry the same `presenceId`, and receivers SHALL not classify a new logical join or leave + +#### Scenario: Final end is ordered and idempotent + +- **WHEN** the last local owner gracefully releases a generation and each external stage succeeds +- **THEN** it SHALL durably retire the private active lease while retaining the generation identity, settle exactly the strict `SessionEndMessage`, durably record settled-cleanup ownership, remove that marker, then physically leave the room, and receivers SHALL classify at most one final logical leave even if the end fact is duplicated + +#### Scenario: Final end is fenced by an external rejection + +- **WHEN** private retirement persistence, an END-state transition, SESSION_END send, or post-settlement cleanup rejects +- **THEN** the peer SHALL NOT physically leave or publish a false local lifecycle end; retirement rejection SHALL retain the same active lease, while every later rejection SHALL retain a recoverable final-end identity for the already-retired generation + +#### Scenario: Unsettled end survives host replacement + +- **GIVEN** durable retirement succeeded and the first or retry SESSION_END is unsettled or explicitly rejected +- **WHEN** the Runtime host is replaced before END settlement +- **THEN** the replacement SHALL physically rebind the retained `presenceId` only to continue the same idempotent END transaction, SHALL expose no successful active join or live-message authority, and SHALL produce at most one observer leave while clearing every private final-end marker + +#### Scenario: Settled cleanup survives host replacement + +- **GIVEN** receivers accepted SESSION_END and the durable record contains settled-cleanup ownership because marker removal is incomplete +- **WHEN** the Runtime host is replaced +- **THEN** the replacement SHALL remove that marker without joining Chat or World, publishing SESSION or SESSION_END, reviving the ended generation, or changing the observer's exactly-once leave + +#### Scenario: Later return uses a fresh generation + +- **WHEN** the same user returns after its accepted final generation end +- **THEN** its SESSION SHALL use a different `presenceId` and receivers SHALL classify one fresh logical join + +### Requirement: History wire shapes are bounded and reference-complete + +The public peer protocol SHALL define only this exact history wire contract: `HistoryRequestMessage = {type:'history-request', syncId, before?: {hlc,id}}` and `HistoryResponseMessage = {type:'history-response', syncId, users:ChatUser[], messages:ChatMessage[], done}`. One `syncId` SHALL identify one complete paginated synchronization and remain stable while `before` advances. `before/messages/done` SHALL express cursor and completion semantics; the public protocol SHALL not prescribe local candidate windows, provider selection, timeout ownership, page cancellation, store effects, or Runtime queue scheduling. Each response SHALL carry at most 100 messages and remain strictly below `MAX_WIRE_BYTES = 64KiB` final encoded bytes. Every `message.userId` in a response SHALL resolve to exactly one matching `users[].id`; duplicate user ids SHALL be rejected, while additional users MAY be present. + +The current serialized `requestId` and history-response `events` keys SHALL be replaced by `syncId` and `messages` in one intentional breaking clean cutover. The existing mention `positions` key SHALL likewise be replaced by `ranges`. New schemas SHALL accept only `syncId`, `messages`, and `ranges`; any old key, old/new pair, alias, or unknown key SHALL reject the entire frame before projection or store writes. No dual-read, fallback, migration, or compatibility alias SHALL exist. Mixed-development-version peers therefore cannot exchange history request/response frames; Chat session/text/reaction and World parsing remain independently determined by their own strict schemas. The fixed `NativeWireCodec` envelope algorithm does not change, but canonical history JSON and encoded bytes do. + +#### Scenario: Pull pagination + +- **WHEN** two current peers exchange history +- **THEN** a peer MAY issue `history-request {syncId, before?}`, receive a flat byte-bounded `history-response`, and issue the next request using the same `syncId` plus the previous response's oldest `(hlc, id)` cursor; `done` SHALL state whether the provider has no more candidates for that sync + +#### Scenario: Complete history references + +- **WHEN** a `history-response` contains messages and users +- **THEN** every message `userId` SHALL have exactly one matching user entry, duplicate user ids SHALL cause the response to be rejected as a whole, and additional users SHALL remain accepted + +#### Scenario: History response wire limits + +- **WHEN** a `history-response` exceeds 100 messages or is greater than or equal to `MAX_WIRE_BYTES = 64KiB` after canonical encoding +- **THEN** the public codec/validator SHALL reject the response before Runtime application, without prescribing any retry, supplier, timeout, or queue behavior + +#### Scenario: Old and ambiguous history keys reject + +- **WHEN** a history frame contains `requestId`, contains both `requestId` and `syncId`, uses `events`, contains both `events` and `messages`, omits a required new key, or contains any compatibility alias; or a text mention uses `positions` or both mention keys +- **THEN** the strict schema SHALL reject the complete frame, the Runtime SHALL project and persist nothing from it, and no fallback SHALL run + +### Requirement: No old-protocol compatibility + +The peer protocol SHALL NOT bridge, translate, or interoperate with the released v1 wire protocol. v1 and v2 SHALL use isolated room namespaces so neither parses the other's wire traffic. Local-data migration and old-record handling are application/page Domain concerns defined by the `webrtc-runtime` capability. Unmerged development residues MAY be cleaned up directly. + +#### Scenario: v1/v2 cross-traffic + +- **WHEN** a v1 client and a v2 client meet in a shared physical environment +- **THEN** they SHALL use isolated room namespaces such that neither parses the other's protocol, and no compatibility fallback SHALL exist diff --git a/openspec/specs/source-quality-tooling/spec.md b/openspec/specs/source-quality-tooling/spec.md new file mode 100644 index 00000000..e07d86ed --- /dev/null +++ b/openspec/specs/source-quality-tooling/spec.md @@ -0,0 +1,182 @@ +# source-quality-tooling Specification + +## Purpose + +TBD - created by archiving change migrate-to-oxfmt-and-oxlint. Update Purpose after archive. + +## Requirements + +### Requirement: Oxc tools are the sole formatter and linter + +The repository SHALL use oxfmt as its only source formatter and oxlint as its only source linter. Active ESLint and Prettier dependencies, configuration, scripts, caches, editor integration, staged-file integration, and workflow integration MUST be removed. + +#### Scenario: Search active tooling references + +- **WHEN** the migration candidate is inspected outside immutable package-manager history and migration documentation +- **THEN** active quality-tool references SHALL resolve to oxfmt and oxlint and SHALL NOT invoke ESLint or Prettier + +#### Scenario: Install frozen dependencies + +- **WHEN** dependencies are installed from the committed manifest and lockfile +- **THEN** oxfmt and oxlint SHALL be available and removed ESLint/Prettier packages SHALL NOT be direct project dependencies + +### Requirement: Local commands can correct source + +The repository SHALL provide explicit local commands that allow oxfmt and oxlint to apply supported corrections to source files. It SHALL also provide read-only check commands that report violations without modifying files. + +#### Scenario: Run formatter fix + +- **WHEN** a developer runs the format command on nonconforming supported files +- **THEN** oxfmt SHALL rewrite those files to the repository format contract + +#### Scenario: Run linter fix + +- **WHEN** a developer runs the lint command on safely fixable violations +- **THEN** oxlint SHALL apply supported fixes and report remaining violations + +#### Scenario: Run read-only checks + +- **WHEN** a developer runs the format-check and lint-check commands on a clean tree +- **THEN** both commands SHALL exit successfully without changing files + +#### Scenario: Rerun fixes + +- **WHEN** the format and lint fix commands are rerun after a successful correction pass +- **THEN** they SHALL produce no additional source diff + +### Requirement: Type checking remains a separate gate + +The migration SHALL use non-type-aware oxlint and SHALL preserve TypeScript `tsc --noEmit` as the repository's type-analysis gate. + +#### Scenario: Run type checking + +- **WHEN** the source contains a TypeScript type error that non-type-aware oxlint does not detect +- **THEN** the TypeScript check command SHALL fail independently + +#### Scenario: Inspect oxlint configuration + +- **WHEN** the oxlint command and configuration are inspected +- **THEN** they SHALL NOT enable type-aware linting in this migration + +### Requirement: Staged files use Oxc corrections + +The repository's staged-file workflow SHALL run oxfmt and oxlint fixes for the supported staged source set and SHALL stage the corrected content through the existing lint-staged/Husky flow. + +#### Scenario: Commit a nonconforming staged source file + +- **WHEN** a developer commits a supported staged source file with fixable format or lint violations +- **THEN** the staged workflow SHALL apply oxfmt and oxlint corrections before commit validation completes + +#### Scenario: Commit an unfixable staged violation + +- **WHEN** oxlint reports an unfixable staged violation +- **THEN** the commit SHALL be blocked and the violation SHALL remain visible to the developer + +### Requirement: CI and CD fix then verify repository cleanliness + +CI and CD SHALL run the repository format and lint fix commands and then execute `git diff --exit-code` before accepting the quality gate. CI/CD MUST NOT automatically commit or push corrections. + +#### Scenario: CI encounters clean source + +- **WHEN** the committed source already conforms to oxfmt and oxlint +- **THEN** fix commands SHALL leave no diff and the cleanliness check SHALL pass + +#### Scenario: CI corrects committed source + +- **WHEN** a fix command changes committed source in CI or CD +- **THEN** `git diff --exit-code` SHALL fail the workflow and expose the uncommitted correction + +#### Scenario: CI correction behavior + +- **WHEN** CI or CD generates a source correction +- **THEN** the workflow SHALL NOT commit that correction, push it to the branch, or recursively trigger another run + +### Requirement: Existing quality and build gates remain + +The migration SHALL preserve Conventional Commit validation, TypeScript checking, and required Chrome and Firefox build behavior. The migration MUST NOT change extension permissions, runtime product behavior, protocols, storage contracts, or browser feature scope. + +#### Scenario: Validate migration candidate + +- **WHEN** the final migration candidate is verified +- **THEN** commit validation, TypeScript checking, Chrome build, and Firefox build SHALL pass under the updated quality workflow + +#### Scenario: Compare extension behavior + +- **WHEN** built Chrome and Firefox extensions are smoke-tested on the migration candidate +- **THEN** extension loading and core chat entry behavior SHALL match the pre-migration product contract + +### Requirement: CI reports linter and unit tests independently + +The GitHub Actions CI workflow SHALL expose `linter` and `tests` as separate named jobs/checks. Both jobs SHALL depend only on the shared setup job and SHALL be eligible to run independently after setup. `linter` SHALL retain the existing format/lint correction, repository-cleanliness verification, and WXT/TypeScript check. Because each job has an isolated workspace, `tests` SHALL generate WXT types in its own job before running the canonical `pnpm run test` command; it SHALL NOT depend on `linter` or absorb format, lint, TypeScript, or build responsibilities. The existing `build` job, workflow triggers, package scripts, application behavior, and browser scope SHALL remain unchanged. Merge acceptance SHALL require successful terminal results for `linter`, `tests`, and `build` on the same final exact. + +#### Scenario: Inspect the CI job graph + +- **WHEN** the final workflow is inspected after shared setup +- **THEN** `linter`, `tests`, and `build` SHALL be sibling jobs, `tests` SHALL generate WXT types before `pnpm run test`, `pnpm run test` SHALL appear only in `tests`, and neither `linter` nor `tests` SHALL depend on the other + +#### Scenario: Run tests in an isolated job workspace + +- **WHEN** the `tests` job starts from a clean checkout after shared setup +- **THEN** it SHALL generate the WXT types required by the repository TypeScript configuration before invoking `pnpm run test`, without relying on files generated by `linter` + +#### Scenario: Linter fails independently + +- **WHEN** setup succeeds and `linter` fails +- **THEN** the `linter` check SHALL report that failure while `tests` remains independently eligible to run and report its own terminal result + +#### Scenario: Unit tests fail independently + +- **WHEN** setup succeeds and `pnpm run test` fails +- **THEN** the `tests` check SHALL report that failure without relabeling it as a linter failure or suppressing the independent `linter` result + +#### Scenario: Accept the final pull request exact + +- **WHEN** the final exact is considered for merge +- **THEN** its separate `linter`, `tests`, and `build` checks SHALL all have successful terminal results + +### Requirement: Chrome Runtime control follows built manifest identity + +The Chrome Runtime CI harness SHALL derive the content isolated execution-context name from the `manifest.json` inside the selected built Chrome extension directory. It SHALL structurally parse that artifact, require a non-empty top-level string `name`, and match the execution context against that exact value. It SHALL NOT hardcode either a historical or current product display name. A missing, malformed, or invalid built manifest name SHALL fail explicitly. The extension display name, WXT configuration, Runtime behavior, workflow, browser scope, and existing Runtime-control assertions SHALL remain unchanged. + +#### Scenario: Product display name changes + +- **WHEN** a valid product display-name change is emitted as the selected Chrome artifact's manifest `name` +- **THEN** the Runtime control SHALL discover the content isolated execution context using that artifact value without requiring a harness-copy update + +#### Scenario: Built manifest identity is unavailable + +- **WHEN** the selected Chrome artifact's manifest is missing, malformed, or has no non-empty string `name` +- **THEN** the Runtime control SHALL fail with an explicit manifest-identity error instead of waiting for a hardcoded context name to time out + +#### Scenario: Preserve the existing Runtime boundary + +- **WHEN** the harness resolves the artifact-derived isolated context +- **THEN** all existing extension-target, Offscreen, service-worker, authenticated relay, presence, diagnostic, timeout, and cleanup controls SHALL continue unchanged + +### Requirement: Existing dependency-upgrade work is preserved and reconciled + +The existing uncommitted changes in `eslint.config.ts`, `package.json`, and `pnpm-lock.yaml` SHALL be treated as input to the migration. Compatible dependency upgrades SHALL be preserved, superseded ESLint/Prettier work SHALL be replaced, and user changes MUST NOT be silently discarded. + +#### Scenario: Establish migration provenance + +- **WHEN** implementation begins +- **THEN** the implementer SHALL record the base HEAD and the pre-existing diff for all three dirty files before editing + +#### Scenario: Review final dependency diff + +- **WHEN** the final candidate is reviewed +- **THEN** the handoff SHALL distinguish preserved pre-existing upgrades, removed superseded tooling, newly introduced Oxc dependencies, and lockfile consequences + +### Requirement: Source correction scope remains tooling-only + +Oxfmt and oxlint corrections MAY modify repository source and supported configuration files, but the migration SHALL NOT intentionally change product behavior or perform unrelated refactors. + +#### Scenario: Review changed source + +- **WHEN** a source change is not a deterministic formatter result or a safe oxlint fix +- **THEN** it SHALL be justified as necessary for the tooling migration or removed from the candidate + +#### Scenario: Detect product behavior change + +- **WHEN** verification finds a change to extension permissions, room behavior, message protocol, persistence, or visible user workflow +- **THEN** the migration candidate SHALL be blocked until that change is removed or separately specified through OpenSpec diff --git a/openspec/specs/webrtc-runtime/spec.md b/openspec/specs/webrtc-runtime/spec.md new file mode 100644 index 00000000..6ce45035 --- /dev/null +++ b/openspec/specs/webrtc-runtime/spec.md @@ -0,0 +1,1146 @@ +# webrtc-runtime Specification + +## Purpose + +TBD - created by archiving change refactor-to-shared-webrtc-runtime. Update Purpose after archive. + +## Requirements + +### Requirement: Shared Remesh Runtime owns all network state + +The headless Runtime SHALL consume the immutable public peer contract from `src/protocol/index.ts` through a one-way dependency. The application/page Domain/model layer SHALL own origin-store access, retained user input, local/UI read models, projections, LWW/order behavior, and record helpers. The uniquely owned Runtime Domains SHALL own network/history facts, Wire queue/drop scheduling, and internal page-host control contracts. None of these application or internal Runtime symbols SHALL be exported by the public protocol module. + +The system SHALL move all WebRTC peer connections, sessions, presence, and message routing into one shared Remesh DDD + CQRS Runtime per browser. Pages SHALL read Queries, issue Commands, and subscribe to Events only. They MAY retain join input and display projections, but SHALL NOT create, hold, or operate `RTCPeerConnection`, peer routing state, or a writable copy of Runtime network facts. Page-owned WebRTC SHALL be removed with no fallback path. + +#### Scenario: Page contains no network ownership + +- **WHEN** any content page is inspected or executed +- **THEN** it SHALL NOT construct or retain peer connections, sessions, or routing state, and SHALL consume the Runtime only through the internal comctx surface + +#### Scenario: No old-architecture fallback + +- **WHEN** Runtime host creation fails on a supported browser +- **THEN** the system SHALL present an explicit unavailable/retry state and SHALL NOT silently restore page-owned WebRTC + +### Requirement: Browser hosts adapt only lifecycle + +The system SHALL provide one shared Remesh Runtime core consumed by browser-specific hosts: Chrome and Edge SHALL use an Offscreen Document host and Firefox SHALL use an equivalent long-lived persistent Background Page host. Hosts SHALL only create, destroy, and inject browser capabilities into the Runtime and SHALL NOT duplicate ChatRoom or WorldRoom business logic. + +#### Scenario: Shared core across browsers + +- **WHEN** the Chrome/Edge and Firefox builds are compared +- **THEN** the Runtime business logic SHALL be the same shared Remesh implementation and host files SHALL contain only lifecycle adaptation + +#### Scenario: Host lifecycle adaptation + +- **WHEN** a browser destroys and recreates its host environment +- **THEN** the host SHALL re-instantiate the shared Runtime core without changing domain business semantics + +### Requirement: Background is the sole host coordinator + +The extension background SHALL be the only coordinator allowed to create or rebuild the Runtime host. Creation SHALL be single-flight: concurrent page requests SHALL wait for the same ready result. While at least one physical page port is online, a missing or destroyed Runtime within the live coordinator's supported host context SHALL be recreated automatically without user action. For Chrome/Edge, the Service Worker coordinator SHALL retain physical page-port liveness and host-phase observations outside the Offscreen host, so Offscreen destruction is recoverable; after host creation it SHALL replay idempotent attach Commands into `LifecycleDomain`. `LifecycleDomain` SHALL remain the unique owner of domain leases, ref-count, grace, and release State; the coordinator SHALL NOT keep a parallel domain lease/grace map. For Firefox, the coordinator and Runtime host SHALL share the persistent Background Page; supported recovery SHALL be limited to in-context `HostOwner` replacement, while a browser process restart SHALL be recovered when Firefox recreates the Background Page and a restored page idempotently reattaches. Direct `backgroundView.close()` SHALL be outside the supported recoverable lifecycle and SHALL NOT require an event page, reload watchdog, or business fallback. A page watchdog MAY supplement Chrome/Edge probing but SHALL NOT be the only controller. The background coordinator itself MAY be suspended or restarted by the browser; after such a supported restart it SHALL recover physical port/host observations from active ports or idempotent page reattach, reconstruct one host, and dispatch the resulting attach Commands without producing duplicate Lifecycle leases, hosts, or physical rooms. + +#### Scenario: Concurrent creation requests + +- **WHEN** multiple pages detect a missing Runtime at the same time +- **THEN** exactly one host creation SHALL proceed and all pages SHALL attach to its single ready result + +#### Scenario: Automatic rebuild in a supported host context + +- **WHEN** Chrome/Edge destroys its Offscreen host, or Firefox replaces an in-context Runtime provider, while a domain page remains online +- **THEN** the coordinator SHALL rebuild the supported host context and re-establish that domain's connections and the WorldRoom without user action + +#### Scenario: Stale Offscreen document + +- **WHEN** the Offscreen document still exists but the Runtime provider or its identity probe does not respond while a physical page port remains online +- **THEN** the background health sweep SHALL close and recreate the stale document, verify the replacement provider, and replay an idempotent attach Command into the replacement Lifecycle Domain without requiring a page watchdog or retaining a parallel lease map + +#### Scenario: DOM-free MV3 health probe + +- **WHEN** the Chrome/Edge background service worker probes a newly created or steady-state Offscreen Runtime +- **THEN** the injector SHALL operate without `window`, `document`, or content-page location metadata and SHALL validate the responding provider identity + +#### Scenario: Single Firefox replacement owner + +- **WHEN** the Firefox persistent Background Page replaces its in-context Runtime +- **THEN** it SHALL dispose the old comctx provider listener, Remesh store, room transport, and Artico peer before exposing one replacement, leaving exactly one provider and physical Runtime + +#### Scenario: Chrome MV3 Offscreen destruction recovery + +- **WHEN** the production Chrome MV3 Offscreen document is directly destroyed while the Service Worker coordinator retains an active physical page port +- **THEN** the coordinator SHALL automatically recreate the Offscreen host, re-instantiate the shared Runtime, replay one idempotent Lifecycle attach, and restore Runtime readiness and room participation without page-owned fallback or duplicate domain lease authority + +#### Scenario: Firefox MV2 process restart recovery + +- **WHEN** the test-owned Firefox process is terminated and restarted with the same isolated profile and the target tab is restored, with the harness reinstalling the same exact temporary XPI only as setup if process exit removed it +- **THEN** the test SHALL observe one persistent Background Page, Runtime rejoin, page `ONLINE`, and state re-projection; profile and tab continuity SHALL be asserted separately, and the harness SHALL NOT claim product auto-reinstallation + +#### Scenario: Firefox persistent-page boundary + +- **WHEN** a diagnostic harness directly closes the Firefox persistent Background Page through `backgroundView.close()` +- **THEN** the result SHALL be recorded as negative evidence of the platform's non-recoverable persistent-page boundary rather than a product failure, and SHALL NOT motivate an event page, reload watchdog, or business fallback + +#### Scenario: Deterministic Firefox HostOwner swap + +- **WHEN** the Firefox host is disposed and replaced during lifecycle recovery +- **THEN** deterministic `HostOwner` dispose/swap tests SHALL prove that the old provider, store, rooms, and peer are fully disposed before exactly one replacement is exposed + +#### Scenario: Observable steady-state host loss + +- **WHEN** the coordinator detects provider loss or replacement failure after startup +- **THEN** Lifecycle-backed Runtime snapshots exposed to pages SHALL report the resulting `connecting` or `unavailable` phase instead of a hard-coded `ready` value + +#### Scenario: Coordinator restart + +- **WHEN** the background coordinator itself is suspended or restarted by the browser +- **THEN** it SHALL recover physical page-port and host-phase observations from active ports or idempotent page reattach, reconstruct one host, and dispatch idempotent Lifecycle attach Commands without producing duplicate domain leases, hosts, or physical rooms + +### Requirement: Background service RPC routing is service-specific + +Notification and AppAction SHALL use mutually exclusive versioned comctx namespaces. Each service's background provider and content injector SHALL obtain its namespace from the same internal proxy contract combined with the current extension runtime id. A heartbeat SHALL therefore admit only the requested service provider. One Notification or AppAction request SHALL produce one matching provider response, execute its intended side effect exactly once, and return request listeners to the live-provider baseline. + +A rejected Notification `push` Promise SHALL be consumed by that request and produce one local observable diagnostic. It SHALL NOT surface as an unhandled Promise or global page error, terminate later notification requests, or remove or duplicate the chat list, barrage, input, OS notification, or AppAction behavior. The existing Notification request Event and all successful behavior SHALL remain unchanged. This isolation SHALL NOT add a public method, retry, fallback, product copy, shared failure authority, comctx fork, Runtime/PresenceStore change, or peer-wire change. + +#### Scenario: Notification reaches only its provider + +- **GIVEN** Notification and AppAction providers are both live on the extension runtime bus with heartbeat enabled +- **WHEN** the content injector calls Notification `push` +- **THEN** exactly one Notification provider SHALL answer and execute `push` once, AppAction SHALL execute nothing, the request SHALL receive one response, and request listeners SHALL return to baseline + +#### Scenario: AppAction reaches only its provider + +- **GIVEN** Notification and AppAction providers are both live on the extension runtime bus with heartbeat enabled +- **WHEN** the content injector calls AppAction `openOptionsPage` +- **THEN** exactly one AppAction provider SHALL answer and open Options once, Notification SHALL execute nothing, the request SHALL receive one response, and request listeners SHALL return to baseline + +#### Scenario: Notification rejection is request-local + +- **GIVEN** a Notification request rejects internally +- **WHEN** `NotificationDomain` issues that request and then issues a later healthy request +- **THEN** the first request SHALL emit one local diagnostic with no unhandled page error, both existing notification request Events SHALL occur, and the later request SHALL execute normally without harming the chat list, barrage, or input + +### Requirement: Domain connection sharing and isolation + +For each domain, `ConnectionDomain` SHALL maintain at most one connection to the same remote peer, shared by every local page of that domain. Connection, Session, World, History, and Delivery owners SHALL keep different domains isolated from one another. + +#### Scenario: Two tabs on one domain + +- **WHEN** two pages of the same domain are online +- **THEN** the Runtime SHALL hold exactly one connection to each remote peer of that domain, not one per page + +#### Scenario: Cross-domain isolation + +- **WHEN** pages of two domains are online +- **THEN** neither domain's connections, sessions, presence, buffered events, nor history-provider cleanup SHALL be visible to or affect the other domain + +### Requirement: Physical room acceptance commits a domain join + +A Runtime logical room registration or desired-room request SHALL NOT by itself constitute physical room readiness or successful domain join. `ConnectionDomain` SHALL keep every initial domain join provisional until the required physical Chat and World rooms accept the initial Chat `session` and World presence snapshot through `WireDomain`. It SHALL then command `SessionDomain` and `WorldDomain` to commit the domain's local-session and local-World facts and complete the join operation exactly once. + +For a transient provider/room-not-ready result, the Runtime SHALL use bounded Runtime-owned readiness waiting or automatic retry, or atomically roll back the provisional join before surfacing a retryable failure. It SHALL NOT leave a terminal state that can receive remote Chat sessions or World presence while the local page has no local-session/presence projection. Cold existing-user startup, normal startup, and supported host/process recovery SHALL automatically converge to exact local plus remote Chat membership and World presence without a page reload or manual reconnect. Pages SHALL NOT implement a fallback retry queue or browser-specific business logic. + +Each provisional attempt SHALL be fenced by the active host and domain lifecycle generation. Last-page release, explicit leave/reconnect, host replacement, and late provider/room-open callbacks SHALL cancel or supersede the prior attempt; a stale completion SHALL not alter state or emit a projection. One logical join SHALL create at most one physical room for each required room id and SHALL emit at most one initial local session and one local World presence. + +Peer-ready and inbound session events received during a provisional window SHALL belong to the active attempt rather than the retained committed runtime. A peer that becomes ready after an accepted initial Chat or World broadcast but before commit, and therefore could not receive that broadcast, SHALL receive exactly one targeted current local Chat session or World presence after commit for each missed room. The Runtime SHALL send no catch-up for a rolled-back or superseded attempt and no duplicate to a peer already covered by the accepted initial broadcast. A remote Chat session received during a provisional reconnect SHALL remain invisible to pages until commit and SHALL then appear exactly once in the replacement committed snapshot; it SHALL NOT leak through the previous committed runtime or be overwritten by an empty attempt snapshot. + +A bounded room-recovery timeout SHALL retain ownership of the underlying asynchronous join. A timeout surfaced as terminal for an attempt SHALL invalidate and leave or otherwise fence that attempt before a late completion can register trusted membership. An automatically retained retry SHALL remain provisional until its current presence/session is accepted. In either case, late physical readiness SHALL NOT leave a persistent physical or trusted World room, or an observable joined snapshot, without an accepted current World presence; a later recovery attempt SHALL publish the full current snapshot before commit. + +#### Scenario: Delayed physical room on cold existing-user join + +- **GIVEN** an existing page identity and an injected transport whose logical join records desired rooms while physical Chat and World rooms remain unavailable +- **WHEN** the page automatically joins and a remote peer later becomes available before the bounded Runtime retry/window ends +- **THEN** no failed provisional attempt may remain remote-receivable without local projection, the Runtime SHALL eventually emit exactly one local session and one local World presence, the page SHALL show the exact local-plus-remote memberships, and each required physical room/session/presence SHALL occur once + +#### Scenario: Cancelled or superseded delayed join + +- **GIVEN** a domain join is waiting for physical room acceptance +- **WHEN** its page lease releases, the host is replaced, the domain reconnects, or an earlier provider opens after a newer generation begins +- **THEN** the stale attempt SHALL not send a session/presence or mutate the current domain, and the current generation alone may converge + +#### Scenario: Peer becomes ready during provisional commit + +- **GIVEN** the initial Chat and World broadcasts are accepted or pending while the domain join remains provisional +- **WHEN** a peer becomes ready too late to receive either accepted initial broadcast and the same attempt later commits +- **THEN** the Runtime SHALL send that peer exactly one targeted current Chat session and one targeted current World presence for the missed rooms after commit, without duplicating an initial delivery or sending from a failed or stale attempt + +#### Scenario: Remote session arrives during provisional reconnect + +- **GIVEN** a committed domain is retained while a replacement Chat room reconnect remains provisional +- **WHEN** the replacement room receives a remote session before its initial local session send settles +- **THEN** the remote session SHALL remain attempt-owned and invisible to pages before commit, then appear exactly once in the committed replacement snapshot without entering the prior runtime or being overwritten + +#### Scenario: World recovery times out before physical readiness + +- **GIVEN** a World recovery join remains physically pending beyond its bounded timeout +- **WHEN** the timeout settles and the underlying provider reports readiness later +- **THEN** the timed-out completion SHALL be fenced from trusted membership, or an owned automatic retry SHALL remain provisional until the current full World presence is accepted; no settled state SHALL expose joined World membership without that publication + +### Requirement: Physical sends isolate per-target readiness transitions + +Artico Room readiness events SHALL be treated as advisory for recipient selection rather than as an atomic guarantee that every remembered call remains sendable. An untargeted send SHALL snapshot the currently remembered peer ids. An explicit one-or-many-target send SHALL use the supplied peer ids. The transport SHALL de-duplicate either set in deterministic order and attempt each distinct target independently. + +A target-local provider rejection after that call has become non-sendable but before Room `leave` SHALL be contained to that target. It SHALL NOT prevent a later target from being attempted exactly once, reject the whole room send, or cause a provisional domain join or World recovery to roll back after any other target has already accepted the fact. The operation SHALL settle after all target attempts, including an empty or no-longer-sendable target set. A missing physical room, an untrusted/stale room selection, or a codec/validation failure before provider target attempts SHALL remain an operation-level rejection. + +This per-target settlement is best-effort physical provider acceptance, not remote delivery acknowledgement. It SHALL add no retry, durable outbox, delivery status, fallback, callback channel, wire field, public ChatRoom method, or persistence behavior. A target that later becomes ready again participates only through the existing provider-ready/session/presence convergence paths. + +#### Scenario: Untargeted send continues after a ready call starts closing + +- **GIVEN** an untargeted Chat or World send snapshots two remembered-ready peers in order, the first call enters `closing` before Room `leave`, and the second call remains sendable +- **WHEN** the provider rejects the first target synchronously +- **THEN** the first target SHALL receive nothing from that attempt, the second target SHALL receive the fact exactly once, and the room send SHALL settle without a room-wide rejection + +#### Scenario: Explicit multi-target catch-up isolates a stale target + +- **GIVEN** a post-commit Chat-session or World-presence catch-up explicitly names multiple distinct peers and one named call becomes non-sendable before its delayed Room `leave` +- **WHEN** the transport attempts the explicit targets +- **THEN** that target-local miss SHALL NOT skip or duplicate any later sendable target, and every later sendable target SHALL receive its catch-up exactly once + +#### Scenario: Partial provisional publication is not rolled back by a later target miss + +- **GIVEN** an initial Chat session, initial World snapshot, or World recovery presence has been accepted by one provider target +- **WHEN** a later target in the same physical send rejects because its call crossed the ready-to-closing window +- **THEN** the target-local miss SHALL be contained, the physical send SHALL finish the remaining targets, and the Runtime SHALL NOT roll back the provisional attempt solely because of that miss or replay the already accepted target + +#### Scenario: Room-level failure remains an operation failure + +- **GIVEN** no current physical/trusted room exists, or wire validation/encoding rejects before any provider target attempt +- **WHEN** Chat or World tries to send +- **THEN** the operation SHALL reject through the existing generation-owned failure path, send to no target, and gain no retry, outbox, status, fallback, or local persistence success + +### Requirement: Unified five-second lifecycle grace + +When the last page of a domain disconnects, `LifecycleDomain` SHALL uniquely own one unified five-second grace phase/deadline. During it, Connection SHALL retain that domain's ChatRoom connection, Session/History SHALL retain domain State, Delivery SHALL retain the volatile inbound un-ACK buffer, and World SHALL retain domain presence. On grace expiry, the Lifecycle domain-released Event SHALL begin a fenced final release: Session SHALL persist the retired private presence record with an unsettled final-end identity before publishing SESSION_END, retain that identity until the send settles, durably replace it with settled-cleanup ownership, and then remove that marker. Session's authoritative finalization state SHALL reject text/reaction allocation and live send from pending retirement through physical release. Connection SHALL physically leave Chat or the last World room only after marker removal succeeds. A page that reconnects within the grace period SHALL cancel grace through Lifecycle and read the current Runtime snapshot without a false offline/online transition. No persistent outbound outbox or delivery-status retry survives a successfully completed grace release; only the separately specified volatile inbound un-ACK buffer participates in this lifecycle. + +#### Scenario: Refresh within grace + +- **WHEN** a user refreshes the only page of a domain and the new page attaches within 5 seconds +- **THEN** the domain connection and state SHALL continue without re-join flapping, presence flicker, or message loss caused by the refresh + +#### Scenario: Application reconnect preserves the logical generation + +- **GIVEN** the application Reconnect Effect retains the frozen `leaveRoom()` then `joinRoom(command)` composition +- **WHEN** the Runtime ChatRoom implementation executes that composition for an active domain +- **THEN** `leaveRoom()` SHALL invoke current-domain Runtime reconnect rather than final logical release, the replacement physical Chat session SHALL reuse the same `presenceId`, World SHALL remain physically joined, and local plus observer views SHALL receive snapshots without SESSION_END, logical join/leave, or another notice + +#### Scenario: Durable retirement rejects + +- **GIVEN** a committed active presence generation and a PresenceStore that rejects the retired record +- **WHEN** final release begins +- **THEN** the same active durable and in-memory lease, Chat/World physical membership, History state, World desired presence, and joined Runtime snapshot SHALL remain; no SESSION_END, observer leave, or physical departure SHALL occur; the pending release fence SHALL be removed so allocation and live send remain usable; and the existing Runtime error path SHALL surface a retryable request-local failure + +#### Scenario: Retirement succeeds after storage recovery + +- **GIVEN** a prior retirement attempt was fenced by storage rejection and the PresenceStore later recovers +- **WHEN** final release is requested again +- **THEN** the same generation SHALL persist one retired identity before exactly one SESSION_END settles, durably transition it to settled-cleanup ownership, remove that marker, and only then SHALL Connection physically leave Chat and the last World room while observers classify one leave + +#### Scenario: Every non-active final-release phase fences live authority + +- **GIVEN** Session has a pending release in `retiring`, `retrying`, `publishing`, `pending`, `settling`, `settlement-failed`, `cleaning`, or `cleanup-failed`, or has restored `inflightEnd`, `pendingEnd`, or `settledEnd` without an active `local` lease +- **WHEN** the current or replacement host requests text allocation, reaction allocation, or live Chat send +- **THEN** both Server preflight and the authoritative Session Command SHALL reject before HLC allocation or Wire send, no live frame SHALL be added, and successful marker cleanup SHALL retain that fence until physical domain release completes + +#### Scenario: SESSION_END send rejects + +- **GIVEN** durable retirement succeeded but the SESSION_END send rejects +- **WHEN** the send failure settles +- **THEN** Session SHALL durably transition that generation from in-flight to retryable pending final end, Connection SHALL retain Chat/World physical membership and publish no false local departure, and a later same-host final-release request SHALL durably transition the same marker back to in-flight before retrying the idempotent end + +#### Scenario: Host replacement continues an unsettled final end + +- **GIVEN** durable retirement succeeded and a first or retry SESSION_END is unsettled or explicitly rejected +- **WHEN** the Runtime host is replaced and the same user invokes join before END settlement +- **THEN** the replacement SHALL use the retained `presenceId` only to physically rebind and continue the same END transaction, SHALL expose no successful active join or live-message authority, and SHALL finish with at most one observer leave plus no persistent marker; a subsequent explicit join SHALL allocate a new generation + +#### Scenario: Post-settlement cleanup rejects + +- **GIVEN** SESSION_END settled and Session durably replaced the unsettled identity with private settled-cleanup ownership +- **WHEN** marker removal rejects +- **THEN** Session SHALL retain settled-cleanup ownership and Chat/World physical membership still owned by the current host, surface a request-local error, publish no second SESSION_END merely to retry cleanup in the same host, and permit physical departure only after later marker removal succeeds + +#### Scenario: Host replacement assumes settled cleanup ownership + +- **GIVEN** the observer ledger accepted SESSION_END and durable settled-cleanup ownership remains after a cleanup rejection +- **WHEN** the same user's replacement host invokes join +- **THEN** it SHALL only remove that marker, SHALL join neither Chat nor World, SHALL publish no SESSION or SESSION_END, SHALL expose no active session or live-message authority, and SHALL preserve the observer's exactly-once leave; only a later explicit join MAY allocate a fresh `presenceId` and one new logical join + +#### Scenario: Readiness helper distinguishes mounted UI from convergence + +- **WHEN** automated acceptance observes an already-mounted usable chat textarea after a refresh or restart +- **THEN** the helper SHALL accept that UI readiness immediately; a separate bounded eventual membership/presence wait MAY guard against a hang, and the five-second domain grace SHALL NOT be treated as a UI-convergence deadline + +#### Scenario: Grace expiry + +- **WHEN** no page of the domain reconnects within 5 seconds and durable retirement plus SESSION_END settlement succeed +- **THEN** the ChatRoom connection, Runtime domain state, volatile inbound un-ACK delivery buffer, and WorldRoom presence for that domain SHALL all be released or removed in the required causal order, with no persistent outbound status or same-id crash retry retained + +#### Scenario: Event outside grace + +- **WHEN** an inbound event targets a domain that is unregistered or past its grace period +- **THEN** the system SHALL discard the event because no persistence location exists for it + +### Requirement: Peer wire protocol is replaced with v2, without compatibility + +The peer-to-peer wire protocol SHALL be replaced with the v2 contract defined by the `peer-wire-protocol` capability. The system SHALL NOT bridge, translate, or interoperate with the released v1 protocol, and v1 and v2 clients SHALL be isolated by v2 room namespaces so neither parses the other's traffic. + +#### Scenario: v1/v2 isolation + +- **WHEN** a v1 client and a v2 client exchange traffic +- **THEN** they SHALL NOT share a room namespace and no compatibility fallback SHALL exist + +#### Scenario: Old protocol removal + +- **WHEN** the release candidate is inspected +- **THEN** the old protocol schemas, the JSONR interop adapter, page-side message routing, reaction toggle, history upsert, and HLC-only history cursor SHALL be absent + +### Requirement: Application/page Domain owns local records and projections + +Each domain's existing origin database SHALL remain the only message-history and local tab-synchronization mechanism. The application/page Domain/model layer SHALL own `MessageRecord`, `ChatMessageRecord`, `SystemNoticeRecord`, projected UI models, internal record helpers/codecs, and projection adapters. It SHALL directly consume protocol `ChatMessage` values and SHALL NOT define a second public/local `TextMessage` or `ReactionMessage` DTO with the same wire names. `MessageProjection` SHALL remain outside `src/protocol` and SHALL own reaction LWW winner calculation, record-to-UI message projection, and notification/danmaku projection. No headless Runtime Domain SHALL maintain a history copy or expose a writable application read-model replica. Designated Runtime/application persistence modules MAY use the injected Database through the internal concrete MessageStore; Chat/UI and the public ChatRoom extern SHALL NOT name stores, indexes, ranges, transactions, `DatabaseItem`, or adapters. + +The application/page Domain SHALL define this exact clean-cut record contract: + +```ts +export const MESSAGE_RECORD_TYPE = { + CHAT_MESSAGE: 'chat-message', + SYSTEM_NOTICE: 'system-notice' +} as const + +export interface ChatMessageRecord { + readonly type: typeof MESSAGE_RECORD_TYPE.CHAT_MESSAGE + readonly id: string + readonly message: Message + readonly user: ChatUser + readonly receivedAt: number +} + +export type TextMessageRecord = ChatMessageRecord +export type ReactionMessageRecord = ChatMessageRecord + +export interface Notice { + readonly id: string + readonly hlc: HLC + readonly type: NoticeType + readonly body: string +} + +export interface SystemNoticeRecord { + readonly type: typeof MESSAGE_RECORD_TYPE.SYSTEM_NOTICE + readonly id: string + readonly notice: Notice + readonly user: ChatUser + readonly receivedAt: number +} + +export type MessageRecord = ChatMessageRecord | SystemNoticeRecord +``` + +A strict decoder SHALL choose the closed variant only through outer `record.type`; it SHALL NOT infer record kind from `DatabaseItem.key`, an id/key prefix or shape, or the presence of `message`/`notice`. Every decoded record SHALL satisfy `DatabaseItem.key === record.id`. A decoded Chat record SHALL also satisfy `record.id === record.message.id` and `record.user.id === record.message.userId`; a decoded SystemNotice SHALL satisfy `record.id === record.notice.id`. Chat and notice ids SHALL occupy one globally unique record-id space so atomic first-value-wins cannot collide across variants. `Notice.type` remains the independent `join | leave | info` reason; outer `record.type` remains only the `chat-message | system-notice` storage/Domain category. SystemNotice identity SHALL be deterministic and it SHALL never enter peer wire or history. `receivedAt` is finite local first-acceptance/creation time, not an HLC, peer timestamp, or delivery state. Record ordering helpers SHALL use `(record.message.hlc, record.id)` for Chat and `(record.notice.hlc, record.id)` for SystemNotice; reaction LWW SHALL continue to use `(message.hlc, message.id)`, and UI reaction aggregation remains projection-only. There SHALL be no standalone `SYSTEM_NOTICE`, old outer notice `hlc`/`body`/`noticeType`, `LocalRecord`, `DurableEventRecord`, outer `event` alias, property-presence guard, key-based discriminator, `RecordStatus`, pending/sent/received state, mark method, outbox metadata, compatibility alias, or dual-read path. + +The only host-replaceable persistence extern SHALL be `Database`. IndexedDB and Memory SHALL implement it for one private `MessageDatabaseSchema` and private logical database name/version/store/index configuration. A future backend is compatible only after running the same public contract suite. The single internal concrete MessageStore SHALL be Database-backed and expose only `insert(record): Promise`, `query(query?: MessageQuery)`, `clear`, and `watch`; it SHALL NOT expose `list` or a compatibility alias and SHALL NOT be a Remesh extern, public-barrel export, host injection point, or independently replaceable backend. Its internal helpers SHALL strictly decode MessageRecord, derive message/notice keys and HLCs, validate Database item identity, distinguish same-id canonical replay from different-content conflict, and keep the first value without overwrite. `InsertMessageResult` SHALL be the readonly inserted/existing Domain union and SHALL NOT reuse Database's result type. + +The canonical per-origin IndexedDB identity SHALL remain stable, including its existing v2 database name. Private `MESSAGE_STORE_VERSION = 2` colocated with the native upgrade callback SHALL name the existing schema authority. This abstraction and record cleanup SHALL NOT itself advance the version or add an upgrade. The version SHALL advance only with an implemented compatible store/index/key/value migration; ordered IndexedDB upgrade transactions SHALL preserve canonical records and bounded conflict diagnostics. App/wire versions and ordinary fixes SHALL NOT clear, rename, or delete the database. Old unstorage data remains unread, unconverted, and uncleared. + +The headless Runtime SHALL own only network/history orchestration around the application-owned store: `HistoryDomain` owns requester/provider State, candidate-window and byte/message budgets, supplier selection/failover, page cancellation, and physical settlement; `WireDomain` owns protocol scheduling/queues; the page-host boundary owns internal RPC. Shared models consumed by both pages and Runtime SHALL remain defined by an application Domain/model module rather than by any Runtime owner or the public protocol. + +#### Scenario: Application persistence boundary + +- **WHEN** a page persists, projects, or synchronizes local records +- **THEN** the Database-backed application persistence boundary SHALL own that work, the headless Runtime Domains SHALL own no history read model, and Chat/UI SHALL receive only decoded Domain records/projections rather than storage primitives + +### Requirement: Application ports are exact, minimal, and replaceable + +`RuntimeServer` and `PagePort` SHALL remain internal page-host boundaries and MAY retain private Runtime contracts. Their adapter SHALL terminate trusted `sourcePeerId`, local/remote provenance, host gating, snapshots, replay, transient operation state, history supply, callback cleanup, timers, and storage detail before application Domains receive values. Runtime-to-page session delivery SHALL converge through one internal session-event path capable of carrying local self convergence, remote live changes, live leaves, and replacement snapshots with private provenance; separate `onLocalSession`/`onSession`/`onSessionLeave` public or adapter-facing paths SHALL be removed. This internal path SHALL NOT be exported from an extern. + +The application `ChatRoom` extern SHALL directly type-import the exact public protocol `ChatUser`, `ChatSession`, `ChatSite`, `ChatMessage`, and `MentionedUser` structures. Its entire public contract SHALL be exactly: + +```ts +interface JoinRoomCommand { + user: ChatUser + site: ChatSite +} +interface SendTextCommand { + type: 'text' + body: string + mentions: MentionedUser[] +} +interface SendReactionCommand { + type: 'reaction' + targetId: string + reaction: 'like' | 'hate' + active: boolean +} +type SendMessageCommand = SendTextCommand | SendReactionCommand +type Unsubscribe = () => void +interface ChatRoom { + joinRoom(command: JoinRoomCommand): Promise + leaveRoom(): Promise + sendMessage(command: SendMessageCommand): Promise + onMessage(listener: (message: ChatMessage) => void): Unsubscribe + onJoinRoom(listener: (session: ChatSession) => void): Unsubscribe + onLeaveRoom(listener: (session: ChatSession) => void): Unsubscribe + onSessions(listener: (sessions: readonly ChatSession[]) => void): Unsubscribe + onError(listener: (error: Error) => void): Unsubscribe +} +``` + +No alias, compatibility field, overload, extra method, generic metadata bag, Runtime type, or peer/source/joinedAt/timer/IDB/host/page/lease/retry field SHALL extend this contract without explicit Owner intervention. `joinRoom` SHALL use the caller-supplied user/site and `impls` SHALL create `sessionId`. `leaveRoom` SHALL operate on the currently joined room instance. `sendMessage` SHALL accept only the frozen business commands; `impls` SHALL create `id`, `hlc`, and `userId`, complete transport acceptance, and call local `MessageStore.insert`. Only after that insert operation successfully settles SHALL it resolve with the exact `ChatMessage` allocated and transported by that call. It SHALL NOT return or expose an insert result, same-id existing winner, `MessageRecord`, delivery status, or Runtime provenance. + +`onSessions` SHALL be the only application session-state truth. For a normal accepted live change, the implementation SHALL publish the updated immutable session snapshot before the corresponding `onJoinRoom` or `onLeaveRoom` fact. Initialization, first join, hydration, refresh, reconnect, host replacement, and replay SHALL publish snapshots only, with no synthetic live delta. `onMessage` SHALL publish only a first durably accepted remote live `ChatMessage`; it SHALL exclude `SessionMessage`, history request/response/control traffic, history messages/replay, initial IDB reads, local sends, duplicate/conflicting inserts, and store-watch replay. Chat and World session instances SHALL remain distinct; same-origin pages share one Runtime logical Chat view, while separate browsers/devices maintain local views that converge. + +Only explicit composition roots SHALL select and inject concrete `impls`. Application Domains and externs SHALL NOT import concrete `impls` or Runtime contracts. Application readiness SHALL retain immediate replay of `connecting | ready | unavailable`. `WorldRoomExtern` SHALL remain separately projected and source-free as specified by `world-room-presence`. + +`Unsubscribe = () => void` SHALL be a neutral Domain type. The only replaceable persistence extern SHALL have this exact complete surface: + +```ts +type DatabaseKey = string | number +interface StoreSchema { + key: DatabaseKey + value: unknown + indexes: Record +} +type DatabaseSchema = { [Store in keyof S]: StoreSchema } +type StoreName = keyof S & string +type Scope = readonly [Name, ...Name[]] +type LowerBound = { lower?: never; lowerOpen?: never } | { lower: T; lowerOpen?: boolean } +type UpperBound = { upper?: never; upperOpen?: never } | { upper: T; upperOpen?: boolean } +type DatabaseRange = LowerBound & UpperBound +type PrimaryQuery = { + index?: never + range?: DatabaseRange +} +type IndexQuery = { + [Index in keyof S['indexes'] & string]: { + index: Index + range?: DatabaseRange + } +}[keyof S['indexes'] & string] +type QueryOptions = PrimaryQuery | IndexQuery +type ScanOptions = QueryOptions & { + direction?: 'asc' | 'desc' + limit?: number +} +interface DatabaseItem { + readonly key: Key + readonly value: Value +} +type InsertResult = { readonly inserted: true } | { readonly inserted: false; readonly existing: Value } +interface ReadTransaction, Allowed extends StoreName> { + get(store: Store, key: Schema[Store]['key']): Promise + scan( + store: Store, + options?: ScanOptions + ): Promise[]> + count(store: Store, options?: QueryOptions): Promise +} +interface WriteTransaction< + Schema extends DatabaseSchema, + Allowed extends StoreName +> extends ReadTransaction { + insert( + store: Store, + key: Schema[Store]['key'], + value: Schema[Store]['value'] + ): Promise> + put(store: Store, key: Schema[Store]['key'], value: Schema[Store]['value']): Promise + delete(store: Store, key: Schema[Store]['key']): Promise + clear(store: Store): Promise +} +interface Database> { + read>, Result>( + stores: Stores, + operation: (transaction: ReadTransaction) => Promise, + signal?: AbortSignal + ): Promise + write>, Result>( + stores: Stores, + operation: (transaction: WriteTransaction) => Promise, + signal?: AbortSignal + ): Promise + watch>>(stores: Stores, listener: () => void): Unsubscribe + close(): Promise +} +``` + +`DatabaseKey` deliberately supports only strings and finite numbers; arbitrary IndexedDB Date/array/binary keys are outside the contract. A schema SHALL statically associate each store with its primary-key, value, index names, and index-key types, and a transaction SHALL reject any store not enlisted in its non-empty, duplicate-free scope. Primary scans order by primary key. Index scans order by index key and then primary key as a deterministic tie-break; `desc` reverses both. Bounds are inclusive unless their corresponding open flag is true. An open flag without its bound, a reversed bound, an unknown store/index, a non-finite key, or a limit that is not a finite non-negative safe integer SHALL reject deterministically; an equal bound with either side open yields an empty range. + +A read SHALL observe one consistent transaction snapshot. A write SHALL be atomic across its enlisted stores and SHALL never automatically retry its callback. The callback MAY await only promises created by that same transaction and SHALL NOT await network, timers, or arbitrary external promises. Callback throw/rejection or signal abort before commit SHALL abort the physical transaction, reject, persist nothing, and emit no watch notification. A successful commit linearized before a later abort SHALL remain successful. An atomic insert collision SHALL not abort the transaction; it SHALL return the exact same-key existing winner from that operation. `inserted:false` itself is not a mutation. Database failures, decode failures, and aborts SHALL reject rather than masquerade as collisions. + +`get` and `scan` SHALL return by-value-isolated snapshots: reassigning readonly item fields or mutating a returned nested object SHALL NOT mutate persistence; only an explicit `put` can write a replacement. Memory SHALL clone/isolate values to match IndexedDB. Canonical Message database values are limited to null, booleans, strings, finite numbers, dense arrays, and plain string-keyed objects; undefined, non-finite numbers, bigint, functions, symbols, Date, Map, Set, binary values, sparse arrays, cycles, and implementation-specific objects SHALL reject. TypeScript schema types do not replace strict runtime decoding. + +`watch` SHALL register synchronously for a non-empty store scope and act only as invalidation. Every relevant successful write commit after registration SHALL produce at most one notification per Database instance/context; rollback and failed writes SHALL produce none. Same-logical-database notifications SHALL cross supported same-origin contexts, listener exceptions SHALL be isolated, and unsubscribe SHALL be idempotent and prevent later callbacks. Conservative notification for an explicit delete/clear/put that makes no observable value change MAY occur, but no state-changing commit may be permanently missed. `close` SHALL be idempotent, stop new read/write/watch calls and this instance's callbacks immediately, allow already-started transactions to drain to their normal settlement, then release database and cross-context resources. + +The one internal concrete facade SHALL have this exact shape: + +```ts +type InsertMessageResult = { readonly inserted: true } | { readonly inserted: false; readonly existing: MessageRecord } +type MessageQuery = Readonly<{ + type?: MessageRecord['type'] + signal?: AbortSignal +}> +interface MessageStore { + insert(record: MessageRecord): Promise + query(query?: MessageQuery): Promise + clear(): Promise + watch(listener: () => void): Unsubscribe +} +``` + +MessageStore SHALL have one Database-backed implementation and no Remesh extern, public-barrel export, host injection, Memory-specific implementation, or fake replacement. It SHALL map Database's insert result into its own Domain result; strictly decode each physical item independently by outer `record.type`; retain every invalid raw row without publication; record bounded, per-key deduplicated diagnostics in the existing private conflicts store without notifying canonical-record watchers; enforce `DatabaseItem.key === record.id`, Chat `record.id === record.message.id`, notice `record.id === record.notice.id`, and Chat user/message identity; preserve the first canonical value; retain bounded conflict diagnostics for same-id different-content input; and classify same-content replay without overwrite. Key/id prefixes, property presence, the removed standalone `SYSTEM_NOTICE`, and compatibility aliases SHALL NOT participate in decode or dispatch. `query()` SHALL return every valid canonical record in physical primary-scan order even when other physical rows are invalid. `query({type})` SHALL return only valid records whose exact outer discriminator equals that type, after independently attempting to decode every physically read item before applying the type filter. `query({signal})` SHALL gate the physical read transaction and all subsequent decode/filter work. The optional query object SHALL contain only `type` and `signal`; an unknown field or a type outside the exact outer discriminator union SHALL reject deterministically. It SHALL expose no id, range, order, limit, cursor, or syncId criterion. The type filter SHALL add no physical index, schema migration, or version bump; private `MESSAGE_STORE_VERSION = 2` remains the schema authority. `clear` SHALL run only for an explicit user/application clear command and SHALL atomically clear canonical records plus conflicts; startup, app-version checks, and compatibility code SHALL NOT call it. `watch` SHALL observe canonical-record invalidation; a conflict-only diagnostic write need not invalidate visible query results. Neither Database nor MessageStore SHALL own history wire DTOs, `syncId`, cursor, cutoff, limit, response `messages`, or `done`; history cutoff/pagination/projection remains in its Runtime/application owner. No `list` alias or test-only conflict-count API is exposed. + +Default IndexedDB and Memory implementations SHALL use the same private `MessageDatabaseSchema` plus private logical name/version/store/index definition and run one unchanged backend contract suite; this requirement SHALL NOT add a public generic adapter factory. Type-negative tests SHALL reject wrong stores, keys, values, indexes, ranges, scopes, MessageQuery fields, and MessageQuery discriminator values. Separate parity tests SHALL cover Database transactions/abort/insert/order/watch/close/value isolation, MessageStore replay/conflict/decode/query-default/query-type/query-abort/clear/watch, and static imports proving Chat/UI/public ChatRoom do not import Database items, transactions, schemas, or concrete adapters. Residue scans SHALL reject the removed MessageStore `list` member, call sites, aliases, and dual paths without treating ordinary UI list nouns as compatibility APIs. If implementation proves any frozen field, method, or semantic insufficient, work on that boundary SHALL stop and the implementer SHALL send the exact gap, earliest blocked call chain, and minimum decision only to `@PM`. PM SHALL independently verify it and, when Owner input is required, ask `@molvqingtai` the one minimum decision. No implementation may add an alias, field, method, overload, fallback, or relaxed behavior autonomously. + +#### Scenario: Atomic Database commit and cancellation + +- **WHEN** a scoped read/write callback completes, throws, rejects, or receives an AbortSignal before or after physical commit +- **THEN** IndexedDB and Memory SHALL agree on snapshot visibility, all-or-nothing settlement, no callback retry, pre-commit rollback/no-watch, post-commit success, and rejection of scope-external access + +#### Scenario: Atomic existing-winner insert + +- **WHEN** concurrent writers insert the same primary key with the same or different canonical value +- **THEN** exactly one first value SHALL persist, every loser SHALL receive an isolated exact existing snapshot without aborting its transaction, and the internal MessageStore SHALL classify replay or bounded conflict without overwrite + +#### Scenario: Deterministic primary and index scan + +- **WHEN** a primary or secondary-index scan applies bounds, direction, duplicate index keys, and a limit +- **THEN** both backends SHALL return readonly DatabaseItems in the same index-then-primary order, preserve the primary key in each item, and reject every invalid store/index/range/key/limit combination identically + +#### Scenario: Cross-context committed-write invalidation + +- **WHEN** an enlisted store is mutated, rolled back, closed, or watched from another supported same-origin context +- **THEN** each relevant successful commit SHALL cause at most one invalidation per context, rollback SHALL cause none, listener failure SHALL not affect commit, unsubscribe/close SHALL stop callbacks, and no state-changing commit SHALL be permanently missed + +#### Scenario: Strict MessageRecord boundary + +- **WHEN** DatabaseItems are inserted, queried with default or exact-type criteria, replayed, conflicted, cleared, or observed through MessageStore +- **THEN** MessageStore SHALL accept only the exact outer-type MessageRecord union; validate `DatabaseItem.key === record.id`, Chat `record.id === record.message.id`, notice `record.id === record.notice.id`, shared cross-variant id uniqueness, and Chat user/message identity; reject key/property-presence discrimination and every old shape; keep SystemNotice local-only; expose only its four methods; and never expose Database primitives or history protocol values + +#### Scenario: Default-all typed MessageStore query + +- **WHEN** a caller invokes `query()` with no object, with one exact outer `type`, with an AbortSignal, or with an invalid field/discriminator +- **THEN** the facade SHALL respectively return all valid strictly decoded canonical records while isolating invalid rows, return only that type after independently decoding the complete scan, honor cancellation through read/decode/diagnostic/filter settlement, or reject deterministically; it SHALL preserve primary-scan order, expose no `list` alias or history criteria, and introduce no physical index, migration, or version change + +#### Scenario: Frozen definition proves insufficient + +- **WHEN** implementation or parity testing cannot satisfy a required behavior with the exact frozen Database, MessageRecord, MessageStore, or ChatRoom definitions +- **THEN** the implementer SHALL stop that work and report only to `@PM`; PM SHALL verify the exact gap and earliest blocked call chain before asking `@molvqingtai` for any contract extension or fallback decision + +Production Runtime/application adapters SHALL use `globalThis.setTimeout` and `globalThis.clearTimeout`; naked timer functions SHALL NOT be constructor dependencies or extern capabilities. Tests SHALL use Vitest fake timers. Timer callbacks SHALL still validate attempt/generation identity, and cleanup SHALL cancel owned handles without letting a stale callback mutate current state. + +#### Scenario: Exact ChatRoom surface + +- **WHEN** the ChatRoom extern, its imports, or a fake replacement is inspected +- **THEN** only the exact frozen commands, protocol values, eight methods, and `Unsubscribe` SHALL exist; `join`, `sendText`, `sendReaction`, `onRecord`, `onMembership`, `reconnect`, Runtime snapshots, source metadata, aliases, and compatibility members SHALL be absent + +#### Scenario: One internal session path and ordered application events + +- **WHEN** local convergence, a remote live join/leave, initialization, refresh, reconnect, host replacement, or replay changes session state +- **THEN** one private Runtime session-event path SHALL feed the implementation; a normal live change SHALL deliver the new `onSessions` snapshot before its one join/leave fact, while initialization/recovery/replay SHALL deliver snapshots only and no Runtime provenance SHALL escape + +#### Scenario: Natural live message callback + +- **WHEN** a remote live Chat message, history response, local send, duplicate, conflict, initial store read, or store replay is processed +- **THEN** `onMessage` SHALL fire exactly once only for the first durably accepted remote live `ChatMessage` and SHALL remain silent for every excluded path + +#### Scenario: Exact command allocation and send-first persistence + +- **WHEN** `sendMessage` accepts `SendTextCommand` or `SendReactionCommand` +- **THEN** the caller SHALL provide none of `id`, `hlc`, `userId`, or `sessionId`; `impls` SHALL allocate them, complete transport acceptance, then call `MessageStore.insert`, and only after that operation successfully settles resolve with the exact allocated-and-transported `ChatMessage`, even when same-id handling retains an existing canonical winner; it SHALL return neither that winner nor a status/result DTO and SHALL add no hidden callback or retry + +#### Scenario: Causal local send projection + +- **WHEN** local text sends race store-watch refresh, another tab sends the same body/mentions, or a same-id collision retains another canonical winner +- **THEN** the application SHALL derive exactly one local success projection from the `ChatMessage` returned by that call, never visible-record diff, body/mention matching, `onMessage`, or a hidden side channel; transport or MessageStore rejection SHALL return no message, preserve the draft, and emit no local success projection + +#### Scenario: Database and MessageStore exclude history protocol + +- **WHEN** the Database extern, internal MessageStore, and their implementation imports are inventoried +- **THEN** only Database SHALL be replaceable; MessageStore SHALL remain the one concrete four-method facade; neither SHALL expose history request/response/cursor/cutoff/limit/done projection or a test-only count API, while the outside owner retains cancellable physical history selection and projection + +#### Scenario: Duplicate delivery and conflict convergence + +- **WHEN** multiple pages/deliveries attempt one message id or a history value conflicts +- **THEN** one atomic insert SHALL win, the first canonical message SHALL remain, same-content replay SHALL be idempotent, different-content conflict SHALL not overwrite it, and side effects SHALL fire at most once + +#### Scenario: Global timer ownership + +- **WHEN** retry, timeout, grace, or cleanup behavior is constructed and tested +- **THEN** production SHALL call `globalThis` timers directly, tests SHALL control them with fake timers, no timer dependency SHALL be injected, and stale/cancelled callbacks SHALL fail their current-attempt identity check + +#### Scenario: No concrete implementation bypass + +- **WHEN** application imports are scanned +- **THEN** only composition roots SHALL wire concrete implementations; Domains SHALL depend on extern contracts and public protocol types, never `impls` or Runtime contracts + +#### Scenario: Runtime unavailable and accepted send-first loss window + +- **WHEN** a send begins before readiness, transport rejects, or transport succeeds but local insertion later fails or never starts because the browser exits +- **THEN** unavailable/rejected input SHALL preserve the draft, write no normal local record, return no `ChatMessage`, and emit no local success projection, while the post-handoff local-loss window SHALL remain accepted with no status, outbox, same-id crash retry, or hidden fallback + +#### Scenario: No Runtime history copy + +- **WHEN** Runtime and persistence ownership are inspected +- **THEN** the headless Runtime Domains SHALL keep no history replica, Database SHALL remain the only replaceable persistence extern, and the one internal MessageStore SHALL own no peer-history protocol orchestration + +### Requirement: Runtime Chat session lifecycle + +The headless Runtime SHALL bind each Chat source to a session identity and incarnation. A join SHALL send `session {sessionId, user}` before live text, reaction, or history traffic. A bound `sessionId` SHALL not change its `user.id`; live event `userId` SHALL match the transport-bound session user. A new incarnation SHALL retire the old binding and old history sync, and SHALL trigger exactly one fresh history request for the replacement without running it concurrently with unsettled old source work. + +#### Scenario: Session binding and replacement + +- **WHEN** a source joins Chat, sends a second session with a changed user, or reconnects with a new incarnation +- **THEN** the Runtime SHALL require the session message first, reject a user change for the same `sessionId`, reject live events whose `userId` does not match the bound user, retire the old binding/sync for a new incarnation, and issue exactly one fresh history request for the replacement + +#### Scenario: Future HLC does not advance Runtime clock + +- **WHEN** the Runtime receives a wire event rejected because its HLC is more than five minutes ahead of the explicit receiver `now` +- **THEN** it SHALL reject the event, leave the central HLC clock unchanged, and continue processing later valid events + +### Requirement: Headless Runtime owns history orchestration + +`HistoryDomain` SHALL own history synchronization policy around the application/page Domain's origin store. At each sync start, it SHALL freeze the receiver/requester's own `requester cutoff = requester wall clock - 180 days` (`HISTORY_WINDOW_DAYS = 180`). A remote response message exactly at that requester cutoff SHALL be eligible; only an earlier remote response message SHALL be rejected. At its corresponding provider supply/session admission, the selected provider SHALL separately freeze its own `provider cutoff = provider wall clock - 180 days`. A local candidate exactly at that provider cutoff SHALL be eligible, and only earlier local candidates SHALL be excluded without deletion; its local query, subsequent cursor, and local page failover SHALL retain that provider cutoff without re-reading time. A dormant successor SHALL freeze its own provider cutoff at its own admission and SHALL retain it after promotion. The cutoffs SHALL NOT be transmitted or required to match. The requester SHALL independently validate every response against its own cutoff and has final acceptance authority, so a remote provider SHALL NOT expand the requester window, although clock skew MAY omit boundary candidates that the requester would otherwise accept. The requester cutoff SHALL remain unchanged through that sync's pagination, retry, and provider failover. `HistoryDomain` SHALL enforce at most one outstanding request per source with a 10-second operational timeout, and stop a session at 8MiB or 10,000 messages while preserving the most recent accepted responses and never falsely claiming provider completion. `HistoryDomain` SHALL select one application/page supplier per request, keep requester/provider State scoped by `(sourcePeerId, domain, syncId, unique sync token)`, and SHALL not store a history copy or read-model replica. The public protocol remains responsible only for validating the typed history request/response shape, cursor, response-size limit, and user/message references. + +The Runtime page contract SHALL use an explicit `{supplyId}` request/cancel event plus `resolveHistorySupply`/`rejectHistorySupply` RPC. Each page supply attempt SHALL have a 5-second boundary. A page query SHALL receive an `AbortSignal` wired to its readonly IndexedDB transaction; the signal SHALL abort that transaction and gate all subsequent projection/filter/sort work, and the page SHALL confirm physical settlement only after the entire query and gated work truly exits. Failover, old-job release, and successor promotion SHALL wait for that confirmation. Supplier work SHALL be serial per source, isolated across domains and sources, and admitted through one pipeline covering supplier selection, encode, send, and final release; the pipeline SHALL have at most four active jobs, 32 admitted requests, and 8KiB of decoded request metadata. A replacement session's one-shot request arriving before the old source job settles SHALL occupy one dormant source-local successor within the same global admission; the successor itself SHALL count toward the 32-request and 8KiB decoded-metadata limits, SHALL NOT run concurrently, and SHALL automatically promote after physical settlement without another peer request. Timeout, leave, and release MAY remove an unstarted successor; a started job SHALL remain counted until settlement. A completed response releases its source slot immediately after send settlement, and an old timer/token SHALL NOT delay or close a newer domain/request. Cleanup SHALL retain active admission until physical settlement and remove dormant successors without starting them. + +#### Scenario: Frozen local history policy + +- **WHEN** a history sync begins, and when requester pagination/retry/provider failover, provider local query/subsequent cursor/local page failover, or dormant-successor promotion continues +- **THEN** the Runtime SHALL freeze the receiver/requester's own `requester cutoff = requester wall clock - 180 days`, accept remote response messages exactly at that requester cutoff and reject only earlier remote response messages after independently validating them against that requester cutoff. At corresponding provider supply/session admission, the provider SHALL separately freeze its own `provider cutoff = provider wall clock - 180 days`, accept local candidates exactly at that provider cutoff, exclude only earlier local candidates without deleting them, and retain it through its local query/subsequent cursor/local page failover; a dormant successor SHALL freeze its own provider cutoff at its own admission and retain it after promotion. The cutoffs SHALL NOT be transmitted or required to match; the requester has final acceptance authority, so remote data cannot expand its window, though clock skew MAY omit otherwise acceptable boundary candidates. The requester SHALL retain its cutoff without re-reading time through pagination/retry/provider failover, stop at provider exhaustion/cutoff/8MiB/10,000 events, and SHALL distinguish local budget exhaustion from provider completion + +#### Scenario: Scoped timeout ownership + +- **WHEN** an old requester/provider timeout fires after a replacement domain, request, or sync has started +- **THEN** the Runtime SHALL require the `(sourcePeerId, domain, syncId, unique sync token)` match before changing state, so the replacement retains its own timeout interval + +#### Scenario: Supplier isolation across domains and sources + +- **WHEN** a peer leaves domain A while domain B is waiting on a local supplier, or source A's supplier is hung while source B requests history +- **THEN** only the invalidated domain/source work SHALL be removed; eligible domain B and source B work SHALL continue within the bounded cross-source concurrency pool + +#### Scenario: Physical cancellation settlement + +- **WHEN** a selected page supply attempt reaches its 5-second boundary and the Runtime sends its `supplyId` cancellation +- **THEN** the page SHALL use the `AbortSignal` to abort its readonly IndexedDB transaction and gate subsequent projection/filter/sort work, and SHALL confirm only after the entire physical query and gated work truly exit; failover, old-job release, and successor promotion SHALL wait for that confirmation, and ignored cancellation SHALL keep the old job admitted until settlement + +#### Scenario: Bounded provider admission + +- **WHEN** supplier work is queued or active and cleanup, rejoin, or replacement occurs +- **THEN** started jobs SHALL remain counted until final settlement, dormant successors SHALL be included in the same global counts and removed without starting, and admission SHALL never exceed four active jobs, 32 requests, or 8KiB of decoded request metadata; excess requests SHALL be dropped source-locally without room reconnect + +#### Scenario: Replacement request continues after prior settlement + +- **WHEN** a replacement session sends its one history request while the prior source job is unsettled +- **THEN** the Runtime SHALL admit it as the one dormant source-local successor within the global 32-request/8KiB decoded-metadata admission, SHALL count it against those limits, SHALL NOT run it concurrently, and SHALL automatically promote it to supplier selection after the old physical job settles without requiring another peer request + +#### Scenario: One end-to-end concurrency boundary + +- **WHEN** supplier selection, encoding, or response sending is still active for an admitted history job +- **THEN** all stages SHALL retain the same job admission, and no more than four jobs SHALL be active across the supplier-to-encode-to-send pipeline + +#### Scenario: Completed provider releases its source slot + +- **WHEN** source A completes a final response for domain A and immediately requests domain B +- **THEN** domain B SHALL enter supplier work after the domain A send settles, without waiting for the old 10-second timer; any old timer/token SHALL fail its source/domain/request identity check + +#### Scenario: History application has no UI side effects + +- **WHEN** a valid history response reaches an application/page Domain +- **THEN** records SHALL be inserted-if-absent through the origin store without notifications, unread increments, or system notices caused solely by history application + +### Requirement: Idempotent inbound delivery without locks + +The Runtime SHALL publish each inbound peer message to all pages of the domain as an event carrying the stable message ID. Each page SHALL persist the message through one atomic insert-if-absent transaction against the shared domain store. Only the page whose transaction first inserts the message SHALL trigger side effects such as notification or unread increments. The system SHALL NOT use locks for this coordination. + +#### Scenario: Duplicate delivery across tabs + +- **WHEN** multiple pages of one domain receive the same inbound message event +- **THEN** exactly one atomic insert SHALL succeed, other pages SHALL observe the existing record without duplicating it, and side effects SHALL fire at most once + +#### Scenario: Page crash during delivery + +- **WHEN** a page crashes after receiving an event but before acknowledging +- **THEN** the system SHALL rely on the event buffer and idempotent persistence, not on lock release, to complete delivery safely + +### Requirement: Event sequence and un-ACK buffer + +`DeliveryDomain` SHALL maintain a short-term per-domain event sequence and volatile inbound un-ACK delivery buffer bounded to 512 records and 8MiB. An event SHALL be cleared once at least one page acknowledges durable persistence. A history-response batch SHALL be admitted atomically or rejected as a whole when it would exceed either bound; rejection SHALL not partially receive records, advance the cursor, or request the next response. A page that reconnects SHALL be re-sent unacknowledged inbound events by sequence. Events still unacknowledged when the domain's grace period ends SHALL be discarded. Loss of the buffer when the browser kills the Runtime is an accepted boundary. This inbound buffer is not an outbound outbox and SHALL NOT be used to recover or retry a local send. + +#### Scenario: ACK clears buffer + +- **WHEN** at least one page of the domain acknowledges durable storage of an event +- **THEN** the Runtime SHALL remove that event from the un-ACK buffer + +#### Scenario: Reconnect resend + +- **WHEN** a page reconnects within the grace period +- **THEN** the Runtime SHALL re-deliver buffered events by sequence so the page can persist them idempotently + +#### Scenario: Grace-expiry discard + +- **WHEN** the domain's 5-second grace ends with unacknowledged events +- **THEN** the Runtime SHALL discard those events and accept the documented loss boundary + +#### Scenario: Atomic history batch admission + +- **WHEN** a history-response batch would exceed 512 records or 8MiB in the volatile un-ACK buffer +- **THEN** the Runtime SHALL reject the whole batch, preserve existing records, leave the cursor unchanged, and SHALL NOT request the next response + +### Requirement: Runtime WirePipeline and internal RPC contracts stay inside Runtime boundaries + +`WireDomain` SHALL own per-room send serialization, per-source decode queues bounded to at most 8 frames and 256KiB of wire data, and source-local drop/apply/flush behavior. `HistoryDomain` SHALL own history concurrency admission and resource scheduling. The page-host boundary SHALL own the internal comctx RPC contracts/namespace. Malformed frames and queue overflow SHALL be dropped only for the affected source, logged with rate limiting, and SHALL NOT reconnect the room. Those contracts SHALL not be exported from `src/protocol/index.ts`; queue/drop types SHALL live with `WireDomain`, history admission types with `HistoryDomain`, and RPC types with the page-host boundary rather than the codec or public protocol module. `compareHLC` and `compareEventPosition` implementations SHALL live in the application/page Domain/model layer, or in a shared Domain/model module when both pages and Runtime consume them, while the public protocol defines only the canonical `(hlc, id)` ordering rule. HLC validators exposed by protocol SHALL accept an explicit `now` argument and SHALL NOT call `Date.now()` internally. + +#### Scenario: Internal Runtime ownership + +- **WHEN** queue/drop/pipeline scheduling, ordering implementation, or page-host RPC contracts are inspected +- **THEN** queue/drop/pipeline scheduling and page-host RPC contracts SHALL be defined under headless Runtime owners, while ordering implementations SHALL be defined under application/page Domain/model owners or a shared Domain/model module; none SHALL be exported from `@/protocol` or imported by protocol code + +#### Scenario: Source-local decode overflow + +- **WHEN** one source exceeds 8 queued frames or 256KiB of queued wire data, or sends a malformed frame +- **THEN** the Runtime SHALL drop only that source-local frame/work, rate-limit the diagnostic, preserve other sources, and SHALL NOT reconnect the room + +#### Scenario: Final send settlement releases source slot + +- **WHEN** a provider's final history response send settles and an old timeout/token later fires +- **THEN** the Runtime SHALL release the source slot immediately after send settlement, and the old timer/token SHALL fail its identity check without delaying or closing a newer domain/request + +### Requirement: Remesh DDD and CQRS govern every Runtime Domain + +Every Runtime Domain SHALL document and implement its State, Query, Command, Event, Effect, and Extern surface. A Command SHALL express intent and SHALL NOT be treated as proof that external work already occurred. A Query SHALL be a pure read or derivation from State and SHALL perform no I/O, issue no Command/Event, and mutate no State. An Event SHALL represent an immutable fact that already occurred after a transition or external outcome and SHALL NOT be used as a disguised Command. An Effect SHALL contain asynchronous or external I/O and SHALL feed its outcome back through the owning Domain's Command/Event path rather than mutate another Domain directly. An Extern SHALL inject an external capability and SHALL NOT become a parallel State, cache, or recovery owner. + +Application/UI code SHALL interact through Queries, Commands, and Events only. It MAY retain join input, draft state, and display read models, but it SHALL NOT own or write back a Runtime network fact. A shared network fact SHALL be read from the same Domain instance obtained through `domain.getDomain(...)`, never recreated by a module or copied into another writable State. + +#### Scenario: Query remains pure + +- **WHEN** any Runtime or application Query is evaluated +- **THEN** it SHALL derive only from current Domain State and SHALL perform no transport, persistence, comctx, timer, provider, Command, Event, or State-write operation + +#### Scenario: External result re-enters its owner + +- **WHEN** an Effect completes provider, clock, identity, page-port, persistence, or comctx I/O +- **THEN** the result SHALL re-enter through the owning Domain's Command/Event transition before becoming observable State, and the Effect SHALL NOT mutate another Domain's State directly + +#### Scenario: UI has no Runtime write authority + +- **WHEN** a page renders sessions, presence, readiness, or message projections +- **THEN** it SHALL derive them from application Queries fed by Runtime/application Events and SHALL NOT use the display model as a recoverable or writable Runtime source + +### Requirement: Runtime facts have exactly one writable Domain owner + +The shared Runtime SHALL split writable authority exactly by responsibility: + +- `LifecycleDomain` SHALL uniquely own page leases, per-domain reference counts, the unified five-second grace phase/deadline, and domain-release identity. +- `ConnectionDomain` SHALL uniquely own join, leave, reconnect, recovery attempts, physical-acceptance phase, and current host/domain generation. +- `SessionDomain` SHALL uniquely own committed local/remote Chat sessions, the full committed session snapshot, session incarnation, and central id/HLC allocation State. +- `WorldDomain` SHALL uniquely own the active-domain registry, local World session/snapshot, remote per-source presence snapshots, and derived World presence. +- `HistoryDomain` SHALL uniquely own requester/provider sessions, frozen cutoffs, cursors, budgets, supply ids, batches, and timeout identities. +- `DeliveryDomain` SHALL uniquely own per-domain inbound sequence, volatile un-ACK buffer, byte/event admission, history-batch membership, replay, and ACK completion. +- `WireDomain` SHALL uniquely own trusted room/source membership, provider-ready peer facts, per-room send serialization, per-source decode queues/drop bounds, immutable protocol translation, and provider callback translation. + +The application/page Domain SHALL remain the unique owner of retained input, origin records, and UI projections. The comctx Server SHALL only construct the graph and adapt request/reply/subscription registration. Mutable Server maps, a catch-all `NetworkDomain`, generic lock controllers, and direct cross-Domain State imports/writes SHALL NOT own any fact listed above. + +The Domain dependency graph SHALL be acyclic. Connection MAY consume Lifecycle, Wire, Session, World, and History; Session MAY consume Wire and Delivery; World MAY consume Wire; History MAY consume Wire, Delivery, and Session; Delivery MAY consume Lifecycle; Wire SHALL consume only the immutable public protocol and Runtime-private provider Extern. The resulting chain `Connection -> History -> Session -> Delivery -> Lifecycle`, together with the allowed edges toward Wire, SHALL remain acyclic. Session SHALL use Delivery only through one admit Command after Session-owned live source/user validation. History SHALL use Session only through a Query that verifies the current trusted `(room/domain, source)` binding before History-owned requester/provider transitions. A Domain SHALL consume another Domain only through its Queries, Commands, and Events. + +#### Scenario: Owner matrix has no duplicate writer + +- **WHEN** each lease, grace, connection generation, committed session/HLC, World presence, history session/cursor/batch, delivery sequence/buffer, and trusted wire/provider fact is traced +- **THEN** exactly one listed Domain SHALL define its writable State and transitions, while every other consumer uses that owner's CQRS surface + +#### Scenario: Runtime graph remains acyclic + +- **WHEN** Runtime Domain imports and `domain.getDomain(...)` dependencies are inspected +- **THEN** they SHALL follow the documented direction, contain no cycle, and expose no direct import or mutation of another Domain's State definition + +#### Scenario: Server owns no network truth + +- **WHEN** the comctx Server and host composition are inspected +- **THEN** they SHALL contain only graph construction, Extern injection, and request/reply/subscription adaptation, with no authoritative session, generation, presence, history, delivery, or trusted-room map + +### Requirement: ChatRoom Runtime implementation is a state-free application adapter + +The public `ChatRoomExtern` SHALL remain exactly the eight frozen methods. `src/domain/impls/runtime/ChatRoom.ts` SHALL be only a Runtime client proxy, Event bridge, persistence-before-ACK boundary, and history-supply boundary. It SHALL contain no retained join command, session map, local-session or published-session State, Runtime generation, join/recovery queue, snapshot cache used for replay, or writable network fact. It MAY retain only infrastructure needed to settle one in-flight call or dispose one active subscription; those handles SHALL be bounded to that operation/subscription and SHALL NOT be read as State or used for recovery. + +Inbound remote live messages SHALL still settle canonical MessageStore insertion before Runtime ACK. History supply SHALL still query the application-owned store through the private page-host contract. Outbound `sendMessage` SHALL retain `allocate -> transport acceptance -> local insert settlement -> exact causal ChatMessage result`, with `onMessage` remaining remote-live-only. Internal page-to-Runtime comctx Commands/Events MAY change in one clean cut to express the Domain graph, but the public eight methods, persistence/ACK order, product behavior, and reliability option B SHALL NOT change. + +#### Scenario: Adapter has no hidden recovery State + +- **WHEN** the Runtime ChatRoom implementation is inspected after construction and across host recovery +- **THEN** it SHALL have no command/session/local-session/published-session/generation/recovery map or snapshot authority, and host recovery SHALL converge from current application intent plus Runtime Domain State + +#### Scenario: Adapter preserves persistence and ACK order + +- **WHEN** a first remote live ChatMessage crosses the adapter +- **THEN** canonical insertion or confirmed canonical existence SHALL settle before ACK, while history and local sends SHALL remain excluded from `onMessage` + +#### Scenario: Internal control contract changes cleanly + +- **WHEN** the page-host comctx contract is replaced to express CQRS Commands and Events +- **THEN** no old request/event alias, fallback, dual listener, or dual read/write route SHALL remain, and the eight-method application port plus product behavior SHALL be unchanged + +### Requirement: Provider capability is private behind WireDomain + +The Runtime SHALL define one private `RoomTransportExtern` injected only into `WireDomain`. It SHALL express provider-neutral capabilities for stable local peer identity, room join/leave, targeted send, transport-confirmed inbound source, peer ready/leave, room close/error, and deterministic dispose. `RoomTransport` MAY remain only as the concrete implementation shape behind that Extern; it SHALL NOT be a public application port, protocol export, or capability imported by UI, application Domains, or non-Wire Runtime Domains. + +Artico SHALL appear only in the provider implementation and explicit composition root. `WireDomain` SHALL be the sole anti-corruption boundary: it validates trusted room/source identity, codec/schema/size limits, ordering and queue bounds, then emits typed Runtime Events; outbound typed Domain intent is encoded and sent only through its Effect and `RoomTransportExtern`. The former imperative `WireExtern` route and every direct concrete/provider call from another Domain SHALL be removed. + +#### Scenario: Provider can be replaced without application change + +- **WHEN** a second provider implementation satisfies the private provider contract +- **THEN** ChatRoomExtern, application Domains, Runtime owner semantics, peer protocol, persistence, and UI SHALL require no change; only provider implementation/composition SHALL differ + +#### Scenario: Artico does not leak + +- **WHEN** imports, public exports, Domain Externs, protocol types, and comctx contracts are scanned +- **THEN** Artico symbols SHALL exist only in its provider implementation and explicit composition, and only `WireDomain` SHALL obtain `RoomTransportExtern` + +#### Scenario: Provider contract parity + +- **WHEN** the provider contract suite runs against the Artico implementation and deterministic fake +- **THEN** both SHALL satisfy stable peer identity, join/leave, trusted inbound source, targeted send, target-local isolation, room-level failure, close, error, and dispose semantics without adding delivery acknowledgement + +### Requirement: Immutable peer values terminate in explicit Domain mappings + +`WireDomain` SHALL terminate every protocol DTO at one typed accepted-message Event and SHALL NOT expose raw provider callbacks, decoded unknown values, or a shared mutable wire model. `SessionMessage` SHALL enter Session binding/generation commit Commands, and `SessionEndMessage` SHALL enter Session's source-bound idempotent generation-end Command. `TextMessage` and `ReactionMessage` SHALL enter Session source/user validation and then Delivery admission. `HistoryRequestMessage` and `HistoryResponseMessage` SHALL enter History Commands; History SHALL verify the current trusted source/session binding through a Session Query before its requester/provider transition, with accepted response batches entering Delivery atomically. `WorldRoomMessage` SHALL enter World source-snapshot replacement. Provider peer-ready/leave and room-close/error facts SHALL enter Connection transitions, which SHALL request Session/World/History cleanup through their Commands rather than mutate them. + +Outbound `SessionMessage` SHALL originate from Session after an accepted Connection generation. Outbound `SessionEndMessage` SHALL originate from final Session release only after private lease retirement and SHALL be sent before physical Chat-room leave. Outbound Text/Reaction SHALL use Session-owned id/HLC allocation and a Wire send Command. History request/response SHALL originate from History State and page-supply outcomes. `WorldRoomMessage` SHALL originate from World's current full snapshot only after Connection acceptance. All outbound values SHALL use the strict current schemas and unchanged codec algorithm; only the Owner-authorized SESSION `presenceId` and SESSION_END shapes differ from the prior baseline. + +#### Scenario: Chat message crosses one trust and delivery path + +- **WHEN** Wire accepts a TextMessage or ReactionMessage from a transport-confirmed source +- **THEN** Session SHALL validate the committed source/user binding before Delivery receives one admit Command, and no other Domain or adapter SHALL store a parallel writable copy + +#### Scenario: History response crosses one owner path + +- **WHEN** Wire accepts a HistoryResponseMessage +- **THEN** History SHALL first verify the current trusted source/session binding through a Session Query, then validate its requester/provider/cursor/budget State and issue at most one atomic Delivery batch admission, without Wire, Server, or ChatRoom adapter owning the history session + +#### Scenario: World snapshot crosses one owner path + +- **WHEN** Wire accepts a WorldRoomMessage or Connection accepts a generation that must publish local presence +- **THEN** World SHALL be the only source-snapshot/presence owner and SHALL replace or publish one full current snapshot through the unchanged protocol + +### Requirement: Remesh modules represent only semantic reuse + +A surviving `*Module` capability SHALL be a real `Remesh.module` instance that is reusable/configurable and whose State, Queries, Commands, Events, transitions, cancellation, and failure semantics are isomorphic in at least two host Domains. Each instantiated module SHALL remain private to its host Domain. A module SHALL NOT be used to share one Runtime State instance across Domains; cross-Domain authority SHALL come only from the unique owner Domain obtained through `domain.getDomain(...)`. + +Existing Status-like capabilities SHALL either become real `Remesh.module` definitions or lose the misleading `Module` name. Attempt/generation logic SHALL become a module only after at least two flows prove the same transition and failure semantics. `DeliveryDomain` SHALL remain a direct unique owner while there is no second isomorphic consumer. Codec/parser, ordering, range, size, and provider callback logic SHALL remain pure functions or Extern implementation logic rather than forced modules. + +#### Scenario: Shared State is not duplicated by modules + +- **WHEN** two Domains need the same Runtime fact +- **THEN** both SHALL consume the unique owner Domain, and SHALL NOT instantiate matching modules as parallel writable copies + +#### Scenario: Module naming is truthful + +- **WHEN** a production `*Module` symbol is inspected +- **THEN** it SHALL call `Remesh.module` and satisfy the semantic-reuse rule, or the helper SHALL have a non-Module name + +### Requirement: Architecture migration is a structural clean cut + +Baseline `9c90bb0...` SHALL remain the functional/fail-before reference and SHALL NOT be accepted as the final Runtime architecture. The requirements commit SHALL remain authority-only and SHALL NOT become a source ancestor. Candidate-sensitive structural tests SHALL first fail on baseline for the stateful ChatRoom adapter, catch-all Network owner, missing private RoomTransport Extern/Wire Domain, CQRS boundary violations, misleading Module names, dependency cycles, and architecture artifact mismatch. + +Implementation SHALL move one writable owner at a time and immediately delete that fact's old owner. No dual read, dual write, alias, fallback, compatibility adapter, old `WireExtern` route, or transitional release SHALL remain. The final candidate SHALL have all source, tests, comments, tasks, residue cleanup, architecture JSON, and generated HTML complete before validation begins. + +#### Scenario: Baseline proves structural sensitivity + +- **WHEN** the architecture fail-before suite runs on exact `9c90bb0...` +- **THEN** each documented old-owner/provider/module/artifact violation SHALL fail for its intended reason while protected peer-wire behavior remains a passing control + +#### Scenario: Final candidate has no old authority path + +- **WHEN** source, imports, State definitions, comctx contracts, tests, comments, and artifacts are scanned +- **THEN** no catch-all Network owner, stateful ChatRoom recovery cache, old WireExtern path, provider leak, false Module name, dual owner, or compatibility route SHALL remain + +#### Scenario: Architecture artifacts are synchronized + +- **WHEN** the canonical architecture JSON is rendered +- **THEN** the generated HTML SHALL reproduce deterministically and show the exact application port, state-free client, seven Runtime owners, immutable protocol, private RoomTransport Extern, provider implementation, and one-way dependency flow + +### Requirement: Session classifies logical presence across physical lifecycles + +Session SHALL uniquely own local active-generation state, unsettled in-flight final-end identity, rejected retryable pending-final-end identity, observer-accepted settled-cleanup identity, and a bounded observer ledger. A private two-method `PresenceStoreExtern` SHALL persist those facts through `browser.storage.session` across supported Runtime host replacement; it SHALL NOT expand MessageStore, the origin database schema, `RuntimeServer`, `ChatRoomExtern`, or any UI/public model. Active lease, in-flight final end, retryable pending final end, and settled cleanup SHALL be four mutually exclusive strict records. Session SHALL allocate a generation only for initial join or true return after complete final end. Refresh, reconnect, recovery, replay, duplicate SESSION, additional physical session, page reattach, supported host replacement, and replacement recovery of any final-end marker SHALL reuse the retained generation and emit snapshot convergence without a logical join/leave. + +Chrome MV3 SHALL construct the concrete session-backed PresenceStore in the background Service Worker and expose only its existing `load`/`save` methods to the Offscreen Runtime through a dedicated comctx adapter over a point-to-point Runtime Port. Port name and comctx namespace SHALL be routing values rather than authority. Before delivering a message, Background SHALL require the transport sender's runtime id, exact Offscreen document URL, and absence of a tab; content, options, and every other extension source SHALL be disconnected without reading or writing durable state. Every provider response SHALL resolve through the exact request-to-Port binding recorded when its request arrived. If that binding has detached or been replaced, the response SHALL be dropped and SHALL NOT fall back to the current active Port. Offscreen SHALL admit a response only while that request remains pending on the same binding; uncorrelated, replayed, old-binding, wrong-namespace, wrong-direction, and broadcast responses SHALL reach no comctx callback. From request-ID response registration, each one-shot call SHALL reserve exactly one ordered transport generation. Generic response subscription SHALL NOT open a Port. The local heartbeat response subscription SHALL unregister before the actual `apply`, and that `apply` SHALL consume the oldest remaining request reservation. If the reserved generation terminates before pending insertion, the call SHALL reject before connecting or posting to a replacement and the adapter SHALL remove that operation's one-shot response entry. Port disconnect, synchronous connect/send failure, and adapter disposal SHALL reject every request and pre-send reservation owned by the terminal generation exactly once and release every adapter-owned per-operation response entry, without hanging or automatically replaying `load` or `save`; stale and late traffic SHALL traverse no terminal operation callback, and only a later new application call with a new request ID may create a replacement Port and correlation. Provider-owned long-lived callback handles SHALL retain their existing refresh/re-registration lifetime and SHALL NOT be removed by this one-shot cleanup. The dedicated adapter SHALL use Port send/disconnect as its liveness authority, satisfy comctx heartbeat preflight locally, and transmit only actual one-shot PresenceStore operations. Offscreen SHALL register no broadcast Runtime-message listener for PresenceStore, so another context cannot forge a provider response or observe one through that adapter. The Offscreen document SHALL receive the dependency through host assembly and SHALL NOT dereference an unavailable `browser.storage.session`, create memory storage, or route presence records through tabs/pages. Firefox MV2 SHALL pass the same concrete session-backed store directly from its persistent Background Page into the same shared host. Storage rejection and authenticated-Port termination SHALL reach Session's existing request-local failure fences without acknowledging, discarding, or weakening the durable transition; a later call after Service Worker recreation SHALL reconnect and use the same session-backed record. + +A peer discovered during preparation SHALL establish baseline membership without a historical observer join. A newly accepted post-baseline generation SHALL produce one observer-local join only when that user transitions from zero active logical generations to one. Physical `PeerLeft` SHALL not produce a logical leave. A valid SESSION_END SHALL produce one observer-local leave only when the user transitions from one active generation to zero. On graceful final local release, Session SHALL replace the active lease with an in-flight final-end identity, send SESSION_END, durably remove that identity after settlement, and only then allow Connection to leave the Chat room. The departing local client need not persist its own leave. + +The local self-join notice SHALL be generation-scoped, persist immediately after successful new-generation join without waiting for history, and consume only Runtime private join provenance. Reconnect/recovery/host replacement SHALL not create a candidate; later true return SHALL use a later stable generation event time and produce a distinct notice. All SystemNotice records SHALL remain observer-local and SHALL never enter ChatMessage history. + +#### Scenario: Chrome Offscreen mounts with background-owned durability + +- **GIVEN** a Chrome MV3 Offscreen document does not expose `browser.storage.session` while the background Service Worker does +- **WHEN** the shared Runtime host mounts and Session loads or saves a presence record +- **THEN** the Offscreen host SHALL remain available, the request SHALL use the private background PresenceStore adapter, and the exact session-backed record or persistence rejection SHALL return without a volatile fallback or page relay + +#### Scenario: Unauthorized extension contexts cannot access PresenceStore + +- **GIVEN** content and options contexts know the deterministic Port name and comctx namespace +- **WHEN** either context opens the named Port and sends a valid `load` or `save` injector envelope +- **THEN** Background SHALL reject the transport source before comctx dispatch, return no lifecycle record, perform no storage write, and leave the durable bytes unchanged + +#### Scenario: Forged provider response cannot reach Offscreen + +- **WHEN** a content, options, or other extension context broadcasts a provider-shaped response with the exact namespace and request id +- **THEN** the Offscreen PresenceStore injector SHALL receive nothing because it listens only to its background-owned point-to-point Port + +#### Scenario: Service Worker recreation preserves the private route + +- **WHEN** the accepted Port disconnects with a request in flight and the Service Worker provider is later recreated +- **THEN** every request owned by that Port SHALL reject through the existing request-local fence without replay, and only the next new `load`/`save` SHALL reconnect to the authenticated provider and use the retained session-storage record + +#### Scenario: Old provider completion cannot cross into a replacement Port + +- **GIVEN** an authenticated request arrived through one Port and that binding detached before its provider operation completed +- **WHEN** a replacement authenticated Port becomes active and the old operation later produces its response +- **THEN** Background SHALL drop the old response because its original request binding no longer exists; the replacement Port SHALL receive nothing and Session SHALL not advance from that completion + +#### Scenario: Terminal send failure settles every binding-owned request + +- **GIVEN** one or more Offscreen PresenceStore requests are pending on the same Port +- **WHEN** a later send on that Port throws a disconnected-Port error before the asynchronous disconnect event is observed +- **THEN** every request owned by that terminal binding SHALL reject without timeout, the operation that was already accepted SHALL not be replayed, and only a later new request may create a new Port and correlation + +#### Scenario: Pre-send request cannot migrate generations + +- **GIVEN** comctx generated request IDs and registered one or more one-shot response callbacks against a live generation, but their `apply` messages have not entered the pending registry +- **WHEN** that generation disconnects before those messages can be posted +- **THEN** every prepared call SHALL reject through its original generation, no replacement Port SHALL receive any original `load`/`save` bytes, and only a later application call with a new request ID may connect and settle + +#### Scenario: Terminal operations release adapter-owned response entries + +- **GIVEN** one-shot `load` or `save` operations registered response callbacks before or after pending insertion on one generation +- **WHEN** connect, post, disconnect, termination, or disposal makes that generation terminal +- **THEN** every affected operation SHALL reject, every adapter-owned response entry and reservation SHALL return to baseline, stale or late responses SHALL settle nothing, no original bytes SHALL migrate to a replacement, and a later fresh request SHALL use a new correlation + +#### Scenario: Dispose settles a prepared first call + +- **GIVEN** a one-shot call registered its response callback while no Port was open and its `apply` has not started +- **WHEN** the Offscreen adapter is disposed +- **THEN** the call SHALL reject locally, its adapter-owned response entry SHALL be released, no Port SHALL open, and no durable PresenceStore operation SHALL be posted + +#### Scenario: Firefox uses the equivalent direct store + +- **WHEN** Firefox MV2 mounts the shared Runtime in its persistent Background Page +- **THEN** host assembly SHALL pass the concrete session-backed PresenceStore directly and preserve the same strict records, rejection semantics, and supported replacement behavior + +#### Scenario: Six-timepoint A/B/C/D lifecycle + +- **GIVEN** independent actual Runtime Server/Session/Wire stacks use deterministic in-repo transport, A is an existing observer, B is a new local user, C is an additional physical session for B's generation, and D is B's replacement Runtime host +- **WHEN** the control executes preparation baseline, B first join, duplicate/C publication, transient B loss/D recovery, D final release, and B later return +- **THEN** B and A SHALL each persist one join for the first logical transition; duplicate/C/loss/recovery SHALL add no notice; A SHALL persist one leave on final end; and later return SHALL persist one fresh self join plus one fresh observer join + +#### Scenario: Physical loss remains provisional + +- **WHEN** a bound peer leaves transport without a valid final generation end and later republishes the same generation from reconnect, recovery, host replacement, or rejected-final-end replacement recovery +- **THEN** Session SHALL publish snapshots only and preserve the logical observer state without a leave/join pair + +#### Scenario: Duplicate and late lifecycle facts + +- **WHEN** SESSION or SESSION_END is duplicated, or an ended generation's SESSION arrives late +- **THEN** Session SHALL apply the accepted generation/end at most once, reject resurrection of the ended generation, and persist no duplicate notice + +### Requirement: Invalid records are isolated at send, receive, and retained-load boundaries + +Every uncontrolled record boundary SHALL use the existing strict Valibot schemas and `safeParse`. Invalid inbound Runtime records SHALL be rejected before persistence, barrage, or primary-list publication; the adapter SHALL diagnose and ACK only that invalid sequence so later valid delivery continues. MessageStore queries SHALL decode retained physical rows independently: valid rows remain queryable, invalid raw rows remain physically untouched, and bounded deduplicated private diagnostics use the existing conflicts store without invalidating canonical-record watchers. A diagnostic-write failure SHALL not hide valid results. No Zod schema, database version change, MessageStore method, or public failure channel SHALL be added. + +#### Scenario: Valid inbound event beside invalid retained data + +- **GIVEN** one unsupported retained row remains in the physical records store +- **WHEN** a valid remote live event is accepted +- **THEN** the valid event SHALL persist, emit once to realtime consumers, and appear in the primary list; the invalid row SHALL remain physical with one bounded diagnostic and SHALL not fail the canonical query + +#### Scenario: Invalid inbound does not poison the lane + +- **WHEN** one Runtime inbound record fails strict MessageRecord validation and a later valid event follows +- **THEN** the invalid event SHALL produce request-local diagnostics and no persistence/barrage/list publication, its sequence SHALL be discarded through ACK, and the later valid event SHALL settle normally + +### Requirement: Adjacent SystemNotice grouping is UI-only and history-responsive + +After the complete latest application projection is canonically sorted by event `(hlc,id)`, the UI SHALL group each maximal adjacent run of SystemNotice messages. A singleton SHALL render unchanged. A run of two or more SHALL initially render the latest notice and an icon expand/collapse control without a numeric count; expansion and collapse SHALL reveal or hide the earlier notices in canonical order through a height/opacity transition, while reduced-motion preference SHALL remove the transition without changing content. Any non-notice message SHALL split groups. The transform SHALL not alter, delete, merge, or persist canonical records, and lifecycle terminology SHALL not appear in the UI. + +Each grouped row SHALL derive one stable UI identity from its first canonical notice's persistent ID. Extending the same run SHALL preserve that identity. Before React and Virtuoso receive a row, text SHALL project as `message:`, singleton notice as `single-notice:`, and grouped notice as `notice-group:`. These row-kind namespaces SHALL remain structurally disjoint for every wire-valid opaque ID, including IDs that begin with another row kind's namespace. Every row SHALL pass that same projected identity to Virtuoso as the item key. Raw ID alone, array position, first/last presentation flags, and expand/collapse state SHALL NOT participate in row identity. + +Streaming history MAY insert Chat messages before, after, or between existing SystemNotice messages according to canonical event time. The UI SHALL recompute grouping from the new sorted projection so late history can create, split, or reposition a group without changing observer-local notice ownership or synchronizing notices through peer history. + +#### Scenario: Non-notice splits a run + +- **WHEN** two adjacent notices are followed by a Chat message and then two more adjacent notices +- **THEN** the UI SHALL render two independent collapsible groups separated by the unchanged Chat message + +#### Scenario: Late history reprojects grouping + +- **WHEN** streaming history inserts a canonically ordered Chat message between notices that were previously adjacent +- **THEN** the next UI projection SHALL split the prior group while every canonical notice and Chat record remains unchanged + +#### Scenario: Virtualized grouped-row identity remains stable + +- **WHEN** a two-notice group renders, its expand/collapse state changes, another notice extends the same run, or late history splits that run +- **THEN** Virtuoso SHALL receive a defined persistent key for every rendered row; expansion and extension SHALL not remount the original group, and split rows SHALL have distinct non-index identities + +#### Scenario: Count-free animated notice disclosure + +- **WHEN** a grouped notice row is expanded or collapsed +- **THEN** earlier notices SHALL enter or leave in canonical order through a smooth height/opacity transition, the latest notice SHALL remain the control anchor, reduced-motion preference SHALL remove the transition, and no numeric group count SHALL render + +#### Scenario: Opaque IDs cannot impersonate another row kind + +- **WHEN** text or notice IDs begin with `message:`, `single-notice:`, or `notice-group:` and late history creates or splits an adjacent-notice group +- **THEN** every React and Virtuoso identity SHALL retain its actual row-kind namespace, remain unique across the projection, preserve the original group through expansion or extension, and never transfer DOM, measurement, or scroll identity to another row kind + +### Requirement: Domain-scoped manual reconnect + +The actions menu SHALL include "Reconnect this site", which SHALL rebuild only the current domain's ChatRoom connection and re-publish that domain's presence. Because the frozen `ChatRoom` has no `reconnect` method, the application Domain command SHALL use the exact public `leaveRoom()` and retained `JoinRoomCommand` with `joinRoom(command)` rather than extending the extern. Availability SHALL be exactly `joined && !reconnecting` in Chrome and Firefox; `panelOpen` SHALL NOT participate. One enabled activation SHALL create one authoritative request identity and pending fact, immediately invoke the leave/join composition, preserve the main panel's current open/closed state, disable the Refresh control, and spin that control's own icon. The button SHALL expose no Ready text, success region, result badge, or second terminal state: it SHALL return to its ordinary eligible icon when the matching request terminal settles. Every callback SHALL validate the request identity so stale work cannot clear a newer request's spin or feedback. Toast subscription, mount, paint, dwell, failure, unmount, or absence SHALL NOT delay, cancel, reject, or redefine reconnect dispatch or the independently captured network operation outcome. A visibly painted request-correlated generic loading Toast SHALL contribute the accepted minimum 300ms dwell; feedback absence, unmount, or presentation failure SHALL settle boundedly and SHALL NOT strand the button or duplicate gate. This pending lifetime SHALL reject duplicates only and SHALL NOT become Runtime or network authority. + +The application SHALL expose one generic Toast feedback capability, not a reconnect-owned presenter. Reconnect, Runtime readiness, and unrelated application notifications SHALL publish through the existing generic Toast APIs and share the original direct `AppMain -> ` surface. `src/app/content/components/reconnect-toast.tsx`, `useReconnectToast`, reconnect-specific presenter/renderer naming, and any independent reconnect Toast lifecycle truth SHALL be removed. Any presentation adapter SHALL consume only generic Toast descriptors, stable IDs, and presentation acknowledgements; it SHALL NOT directly import or own ChatRoom, Readiness, Runtime, network, or panel behavior. Business Domain effects MAY map source events to generic Toast entries, but the reconnect request SHALL remain the only owner of operation outcome, duplicate rejection, button pending state, and stale-request fencing. Generic Toast state SHALL NOT become a second reconnect pending or network truth. + +The Toaster SHALL remain a direct `AppMain` child using the existing AppMain Motion translate containing mechanism. There SHALL be no wrapper, reconnect-specific Toaster, always-mounted launcher layer, host-page global overlay, second renderer, or source-specific Toast restyling. The direct Toaster SHALL retain `richColors`, current `themeMode`, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and `dark:bg-slate-950 border dark:border-slate-600` Toast classes. No source SHALL add custom geometry, width, content-fit, placement, pointer, opacity-tracking, pseudo-element, or eligibility styles. While mounted, generic entries MAY present reconnect loading/result, Runtime connecting loading, Runtime unavailable error, and unrelated feedback through that same surface. Normal Runtime ready SHALL NOT publish a success descriptor. One reconnect request SHALL correlate only its own generic Toast ID for visible-paint acknowledgement, the accepted 300ms minimum loading dwell, terminal update, and cleanup. Absence, unmount, or presentation failure SHALL settle boundedly without delaying bootstrap, Runtime, reconnect, or the matching button. Opening during an active reconnect MAY present only its current pending entry; terminal reconnect SHALL NOT replay later. Cleanup SHALL address only the correlated ID, SHALL NOT use unscoped/global dismissal, and SHALL preserve unrelated Toasts. + +`App.tsx` SHALL NOT render a fixed readiness output, and `content/index.tsx` SHALL NOT render a pre-App loading, unavailable, Retry, status, or result fallback when client bootstrap fails. Runtime lifecycle SHALL retain its immediate-replay `connecting | ready | unavailable` state for recovery and send preconditions; only the independent presentation authority is removed. ReadinessDomain SHALL be the sole application readiness-transition authority. Its State-setting Command SHALL compare every mapped extern input to current State: equal input SHALL perform no State write and emit no `StateChangedEvent`, while a different input SHALL update State and emit exactly one Event. The current Query SHALL remain immediately available independently of transition Events. No duplicate filter or second readiness State SHALL be added to ClientLease, the Readiness implementation adapter, AppFeedback, or Toast presentation. + +The ClientLease watchdog SHALL retain its existing five-second cadence, page lease renewal before the Coordinator's 15-second TTL, host availability probe, generation/host-id/page-attachment comparison, and real replacement/loss recovery. Equal healthy host-phase callbacks MAY still reach the Readiness extern boundary, but after mapping they SHALL NOT become application transitions. This heartbeat SHALL remain liveness infrastructure rather than proof of a new readiness fact. + +While AppMain/Toaster exists, normal Runtime `connecting` MAY publish the stable loading entry and `unavailable` MAY publish the stable error entry. A genuine transition to `ready`, or an immediate mounted-surface ready Query, SHALL issue only an ID-scoped dismissal for `webchat-runtime-readiness` if present; it SHALL NOT publish `Ready to chat` or any other success descriptor. Periodic equal-state ready health samples SHALL produce neither a `StateChangedEvent` nor any AppFeedback/Toast command. Toast dismissal, automatic close, acknowledgement, and presentation settlement SHALL NOT publish another readiness descriptor or create a loop. These rules SHALL NOT change recovery or send preconditions or alter the independently request-correlated reconnect success result. If Toast cannot render because App/Toaster is absent, the application SHALL NOT create an alternate loading, unavailable, Retry, status, or result view. Presentation absence SHALL NOT redefine bootstrap, Runtime, or network truth. Reconnect SHALL NOT rebuild the shared WorldRoom; the Runtime SHALL auto-reconnect WorldRoom only on its own connection failure. The Options page SHALL NOT gain a global reconnect entry. + +#### Scenario: Manual domain reconnect + +- **WHEN** a user activates "Reconnect this site" on one domain +- **THEN** only that domain's ChatRoom connection and presence SHALL be rebuilt, and other domains and the WorldRoom SHALL be undisturbed + +#### Scenario: Button and generic Toast entry share one reconnect request + +- **GIVEN** `AppMain` and its original Toaster are mounted +- **WHEN** an enabled user activates reconnect and the current-domain leave/join composition succeeds or fails +- **THEN** one request identity SHALL immediately invoke the composition, disable and spin the Refresh button, and correlate one generic loading-to-ready/success-or-failure Toast entry; the bounded request terminal SHALL stop only the matching spin, and neither the generic Toast layer nor the button SHALL create a second reconnect state owner + +#### Scenario: Reconnect does not wait for Toast presentation + +- **GIVEN** the Toast library defers its subscriber update or fails to render loading +- **WHEN** an enabled user activates reconnect +- **THEN** the leave/join ports SHALL be invoked immediately, the Refresh icon SHALL represent the same pending request, no Toast state SHALL delay or alter that operation, and a bounded presentation-failure outcome SHALL allow the shared request terminal rather than strand the icon or duplicate gate + +#### Scenario: Fast terminal reconnect respects mounted feedback + +- **GIVEN** `AppMain` remains mounted and the leave/join ports settle before the request-owned loading Toast receives its first visible paint +- **WHEN** the operation outcome reaches the shared request +- **THEN** the operation outcome SHALL be captured without waiting for feedback, while the matching Refresh icon and loading Toast SHALL remain tied to that request until the visible Toast completes its 300ms minimum dwell and transitions to the matching terminal Toast result + +#### Scenario: Stale terminal work cannot clear a newer request + +- **GIVEN** one reconnect has settled and a later reconnect owns a newer request identity +- **WHEN** delayed Toast paint, dwell, or terminal cleanup from the older request completes +- **THEN** it SHALL NOT stop the newer Refresh spin, dismiss the newer loading Toast, emit a newer error, or alter the newer reconnect operation + +#### Scenario: Original AppMain Toaster structure and visuals are preserved + +- **WHEN** the main panel renders application feedback +- **THEN** `AppMain` SHALL contain the direct original Toaster with `richColors`, current theme, `offset="70px"`, `visibleToasts={1}`, `position="top-center"`, and the existing dark Toast classes, without an added wrapper, launcher layer, reconnect-specific Toaster component, or custom geometry/pointer styling + +#### Scenario: Generic Toast surface carries independent business sources + +- **GIVEN** unrelated application Toast feedback exists before or during reconnect or a Runtime readiness transition +- **WHEN** reconnect or Runtime status publishes feedback +- **THEN** all sources SHALL use the same generic Toaster and generic descriptor contract, reconnect/Readiness SHALL own no presenter or renderer, source-local cleanup SHALL preserve unrelated entries, and no unscoped dismissal SHALL occur + +#### Scenario: Readiness emits only actual transitions + +- **GIVEN** Readiness State already equals the mapped extern input +- **WHEN** the five-second heartbeat or any other source repeats that same `connecting`, `ready`, or `unavailable` value +- **THEN** Readiness SHALL perform no State write, emit no `StateChangedEvent`, trigger no ChatRoom recovery, and publish or dismiss no Toast + +#### Scenario: Real readiness transitions remain observable once + +- **GIVEN** Readiness currently exposes one state through its Query +- **WHEN** the mapped extern input changes to a different `connecting`, `ready`, or `unavailable` value +- **THEN** Readiness SHALL update its State and emit exactly one matching `StateChangedEvent`, while current-state Query/replay remains available without manufacturing a duplicate transition + +#### Scenario: Runtime watchdog retains lease and recovery behavior + +- **GIVEN** a page lease requires renewal and the shared Runtime host may be lost or replaced without a reliable terminal event +- **WHEN** the ClientLease watchdog runs +- **THEN** it SHALL retain the five-second lease/probe/snapshot checks and initiate recovery only for a real unavailable, generation, host-id, or page-attachment change; Readiness transition dedup SHALL NOT disable or delay that liveness behavior + +#### Scenario: Runtime ready dismisses loading without success replay + +- **GIVEN** the Runtime readiness Toast uses stable ID `webchat-runtime-readiness` +- **WHEN** readiness genuinely enters ready or a newly mounted Toast surface reads the current ready Query +- **THEN** application feedback SHALL only dismiss that ID if present, SHALL NOT publish `Ready to chat` or another success descriptor, and SHALL NOT dismiss unrelated Toasts + +#### Scenario: Runtime ready settlement cannot form a presentation loop + +- **GIVEN** the Runtime readiness loading entry was dismissed or a prior Toast automatically closed, acknowledged, or settled +- **WHEN** readiness remains ready without a later connecting or unavailable transition +- **THEN** no Toast lifecycle fact SHALL republish the ready success descriptor, while Runtime recovery, immediate-replay readiness truth, and manual reconnect feedback SHALL remain unchanged + +#### Scenario: No independent readiness or bootstrap status view + +- **WHEN** Runtime is connecting or unavailable, or client bootstrap fails before App/Toaster exists +- **THEN** `App.tsx` and `content/index.tsx` SHALL render no fixed loading, unavailable, Retry, status, or result view; mounted normal-runtime feedback MAY use generic Toast, while absent Toast presentation SHALL create no replacement UI and SHALL NOT become bootstrap or Runtime authority + +#### Scenario: Closed-panel reconnect has no Toast prerequisite + +- **GIVEN** the main panel is closed and `AppMain` plus Toaster are unmounted +- **WHEN** an enabled user activates reconnect +- **THEN** the panel SHALL remain closed, the leave/join composition SHALL start immediately, the matching Refresh icon SHALL remain disabled and spinning while pending, and Toast absence SHALL neither queue nor strand the operation + +#### Scenario: Ready and result feedback use generic Toast only + +- **GIVEN** `AppMain` and Toaster are mounted for the reconnect request +- **WHEN** that request captures success/readiness or failure +- **THEN** the request-correlated generic Toast entry SHALL present the matching terminal result, while the Refresh button SHALL expose no Ready text, success region, error region, result badge, or second result state + +#### Scenario: Active request may enter a newly mounted Toaster once + +- **GIVEN** reconnect began while the panel was closed and the same request remains pending +- **WHEN** the user opens the panel +- **THEN** the original generic Toaster MAY present that request-correlated loading-to-terminal entry once, SHALL NOT restart reconnect, and SHALL NOT replay any request that had already terminated + +#### Scenario: Reconnect unavailable state is not a silent action + +- **GIVEN** the current domain cannot start the reconnect composition +- **WHEN** the actions menu renders "Reconnect this site" +- **THEN** the action SHALL be visibly disabled, SHALL NOT dispatch activation, and SHALL NOT accept a click that silently produces neither feedback nor an operation + +#### Scenario: Panel state changes only Toast availability + +- **WHEN** the main plugin panel opens or closes during an active reconnect +- **THEN** the same operation and matching button spin SHALL continue without cancellation, replay, restart, duplication, or panel mutation; opening MAY mount the request-correlated generic Toast entry once, while closing MAY unmount it and SHALL settle feedback absence boundedly + +#### Scenario: Reconnect cleanup is request-local + +- **GIVEN** unrelated Toast feedback exists before or during reconnect +- **WHEN** reconnect succeeds, fails, or the main panel changes state +- **THEN** cleanup SHALL address only the generic Toast ID correlated to the matching reconnect request, SHALL NOT invoke an unscoped/global dismissal, SHALL preserve all unrelated Toast feedback, and SHALL NOT affect a newer request + +#### Scenario: WorldRoom self-recovery + +- **WHEN** the WorldRoom connection itself fails +- **THEN** the Runtime SHALL reconnect it automatically without requiring the domain reconnect action + +### Requirement: One-shot migration without dual architecture + +The change SHALL be delivered as one candidate that includes the hosts, exact eight-method ChatRoom port, state-free Runtime client, clean-cut internal comctx surface, uniquely owned Lifecycle/Connection/Session/World/History/Delivery/Wire Domain graph, private RoomTransport Extern/provider composition, message delivery, reconnect entry, current v2 peer protocol with only the authorized logical-presence exception, exact typed Database extern/default adapters, internal concrete MessageStore, canonical outer-type/outer-id `MessageRecord` with `ChatMessageRecord.message` and `SystemNoticeRecord.notice`, send-first persistence, and complete removal of page-owned WebRTC, the v1 protocol, stateful ChatRoom authority, catch-all Network ownership, and old WireExtern/provider route. Persistence and Runtime authority SHALL be complete clean-cut structural replacements rather than minimal repairs; no compatibility wrapper, alias, dual path, dead facade, hidden state channel, provider leak, or test-only accommodation may retain an obsolete owner/record/Store/outbox architecture. No intermediate release SHALL ship both architectures. Existing local message history SHALL NOT be imported, migrated, or retained by the canonical database. + +#### Scenario: Single-candidate completeness + +- **WHEN** the release candidate is inspected +- **THEN** it SHALL contain the full Remesh DDD + CQRS Runtime architecture and SHALL NOT contain any active page-owned WebRTC path, v1 protocol path, stateful ChatRoom recovery authority, catch-all Network owner, old WireExtern route, or dual writable fact + +#### Scenario: No data migration + +- **WHEN** the extension upgrades with old unstorage message data present +- **THEN** the old data SHALL be left unread and unconverted, and no migration code, marker, or reaction conversion SHALL exist + +### Requirement: Verification coverage protects runtime boundaries + +Contract tests for peer wire behavior SHALL import only from `@/protocol` or the documented public entry. Type-negative and unchanged-backend suites SHALL cover Database typing, transactions, cancellation, ordering, insertion, watch, close, and value isolation on IndexedDB and Memory. Application and page Domain tests SHALL cover strict `MessageRecord` decoding, item/record/payload identity, cross-variant uniqueness, Chat identity, MessageStore replay/conflict/query/abort/clear/watch behavior, send-first success/failure/loss behavior, causal local projection, and ordering. Headless Runtime tests SHALL cover each unique owner, provisional connection generations, sessions, World presence, history, delivery replay, Wire queues, trusted provider translation, and internal RPC contracts. + +Dependency, export, and residue checks SHALL prove that public protocol code has no reverse application or Runtime dependencies; Chat and UI do not import concrete Runtime, provider, Database, or adapter primitives; MessageStore remains internal; provider imports remain composition-only; and removed owners, aliases, fallback paths, status/outbox/retry state, and old Wire routes do not return. Reconnect controls SHALL continue covering immediate request-owned dispatch, pending button state, mounted and absent Toast behavior, stale fencing, request-local cleanup, original Toaster parameters, and the absence of a second readiness/result surface. + +#### Scenario: Product boundaries remain testable + +- **WHEN** the runtime, persistence, protocol, or reconnect implementation changes +- **THEN** the affected contract suites and structural checks SHALL detect owner leakage, public-boundary widening, obsolete architecture residue, or feedback-state duplication + +#### Scenario: Runtime verification uses production boundaries + +- **WHEN** browser Runtime behavior is accepted for release +- **THEN** verification SHALL use the built extension and real controls rather than treating `pnpm dev`, isolated Toaster mounting, synthetic clicks, or source call order alone as product evidence diff --git a/openspec/specs/world-room-presence/spec.md b/openspec/specs/world-room-presence/spec.md new file mode 100644 index 00000000..b9c39b98 --- /dev/null +++ b/openspec/specs/world-room-presence/spec.md @@ -0,0 +1,103 @@ +# world-room-presence Specification + +## Purpose + +TBD - created by archiving change refactor-to-shared-webrtc-runtime. Update Purpose after archive. + +## Requirements + +### Requirement: WorldRoom v2 is a browser singleton inside the Runtime + +The World wire types, schema, limits, and pure validation SHALL be defined by the public `src/protocol/index.ts` module. Runtime-only registry, projection, lifecycle, and transport orchestration SHALL remain outside that module; the dependency direction SHALL be Runtime → protocol. + +The system SHALL keep the WorldRoom as the cross-domain discovery index, joined exactly once per browser by the shared Runtime in a v2-namespaced room rather than once per page. `ConnectionDomain` SHALL uniquely own that physical WorldRoom membership/recovery generation, while `WorldDomain` uniquely owns its session, active-domain registry, and presence snapshots. While at least one supported domain page is online, Connection SHALL maintain one WorldRoom membership; after the last page disconnects, the membership SHALL follow the Lifecycle domain-released Event after the unified five-second grace before exit. + +#### Scenario: Single membership per browser + +- **WHEN** multiple pages across one or more domains are online in one browser +- **THEN** `ConnectionDomain` SHALL hold exactly one WorldRoom membership, not one per page or per domain + +#### Scenario: Grace-aligned exit + +- **WHEN** the last page of the browser disconnects +- **THEN** the WorldRoom membership SHALL exit only after the same unified 5-second grace used by domain lifecycles + +### Requirement: Presence is a full per-peer snapshot + +WorldRoom presence SHALL be published as exactly one `WorldRoomMessage` complete snapshot per source peer: `WorldRoomMessage extends ChatSession {sites: ChatSite[]}`, `ChatSession = {sessionId,user:ChatUser}`, and `ChatSite = {origin,title?,icon?,description?}`. It SHALL have no payload discriminator. The trusted World `roomId` SHALL select this strict parser. World and Chat SHALL use the same structures while maintaining distinct session instances and room protocols. `WorldDomain` SHALL maintain the browser's active-domain registry, publish a fresh complete snapshot whenever the registry changes, atomically replace a trusted source peer's whole record on each snapshot, and delete that record when the peer leaves. + +#### Scenario: Registry-driven publication + +- **WHEN** a domain joins or leaves the browser's active registry +- **THEN** `WorldDomain` SHALL publish one new complete `sites` snapshot reflecting the post-change registry + +#### Scenario: Atomic replace and delete + +- **WHEN** a receiver gets a snapshot from a source peer +- **THEN** it SHALL replace that peer's entire site set; when the peer leaves it SHALL delete the entire record, never producing ghosts + +#### Scenario: No partial-update artifacts + +- **WHEN** presence behavior is inspected +- **THEN** there SHALL be no payload `type`, composite keys, TTL expiry, World reconnect-republish cycles, withdraw message types, or list flicker by design + +### Requirement: Presence counts per domain per browser + +Presence SHALL be aggregated by domain: multiple pages of one browser on one domain SHALL count as exactly one online user for that domain. A user on domain A who has not joined domain B's ChatRoom SHALL still see domain B's online count in the sites list. + +#### Scenario: Multi-page dedup + +- **WHEN** one browser has several pages open on the same domain +- **THEN** its WorldRoom contribution for that domain SHALL count as exactly one online user + +#### Scenario: Cross-domain visibility + +- **WHEN** a user stays on domain A without joining domain B's ChatRoom +- **THEN** the user SHALL still see domain B's online count derived from WorldRoom presence + +#### Scenario: Grace-period counting stability + +- **WHEN** a domain enters its 5-second refresh grace period +- **THEN** it SHALL remain in the published `sites` snapshot during the window and SHALL be removed only after grace expiry, avoiding count flapping + +### Requirement: Presence site metadata is display-safe and privacy-bounded + +`ChatSite = {origin, title?, icon?, description?}`. `host` and `hostname` SHALL NOT be published because they are derivable from `origin`; raw `href` SHALL NOT be published because it can leak paths, queries, and tokens. Any future page-specific display SHALL use newly designed sanitized fields behind an explicit privacy decision. + +#### Scenario: No page-content disclosure + +- **WHEN** any presence snapshot is published +- **THEN** each site SHALL contain only `origin` and optional `title`, `icon`, and `description`, and SHALL NOT contain `host`, `hostname`, `href`, paths, or query strings + +### Requirement: WorldRoom self-recovery is independent + +`ConnectionDomain` SHALL reconnect the WorldRoom automatically when its physical connection fails, and `WorldDomain` SHALL republish the current full presence snapshot only after the new generation is accepted. The domain-scoped manual reconnect action SHALL NOT rebuild or interrupt the WorldRoom. + +#### Scenario: WorldRoom connection failure + +- **WHEN** the WorldRoom connection itself fails while pages remain online +- **THEN** `ConnectionDomain` SHALL re-establish it automatically and `WorldDomain` SHALL republish current presence without user action or duplicate ownership + +#### Scenario: Domain reconnect isolation + +- **WHEN** the user activates "Reconnect this site" on any domain +- **THEN** the WorldRoom membership and its presence SHALL continue unaffected + +### Requirement: World application port is projected and source-free + +`WorldRoomExtern` SHALL directly type-import the exact protocol `ChatSite` and `ChatUser` structures and SHALL expose exactly `getState(): Promise`, `onState((WorldState) => void): disposer`, and `onError((Error) => void): disposer`, where `WorldState = (ChatSite & {users: ChatUser[]})[]`. `WorldDomain` SHALL remain the unique owner of private source/site contributions and SHALL project the source-free `WorldState`; the application adapter SHALL subscribe before taking that projected upstream snapshot, so no update can fall into a getter/subscription gap, and SHALL NOT reconstruct or retain a second writable source multiset. The application port SHALL NOT expose peer/source identity, Runtime snapshot/presence types, synthetic join times, or routing fields. Each `(source, origin)` contribution in `WorldDomain` SHALL receive stable order at first appearance; updates SHALL not reorder it and new contributions SHALL append. Every active contribution SHALL retain one entry in that origin's `users`, including repeated entries for the same user id; origin groups and their users SHALL project in stable order, and every origin SHALL have exactly one group. Group `ChatSite` metadata SHALL always be the current ChatSite value of the first active contribution: a metadata-only update of that first contribution updates the metadata while it remains first, and only its leave or move to another origin promotes the next active contribution. World application consumers and non-runtime Domains SHALL have zero `@/runtime/**` imports. + +#### Scenario: Exact-origin projected multiset and metadata order + +- **WHEN** multiple source contributions describe one exact origin, including multiple contributions for the same user, and the first contribution updates metadata, leaves, or moves to another origin +- **THEN** the application World state SHALL expose one exact-origin group whose users and origin groups retain first-appearance stable order; the metadata SHALL use the first active contribution's current ChatSite value after a metadata-only update and SHALL promote the next active contribution only after that first contribution leaves or moves, without exposing the source identities used by the adapter + +#### Scenario: World subscription closes the snapshot gap + +- **WHEN** a World application consumer establishes both state observation and an initial state read while upstream presence changes +- **THEN** the adapter SHALL subscribe before its projected upstream snapshot and the consumer SHALL receive no missing state between `onState` and `getState`, without the adapter retaining private source contribution authority + +#### Scenario: World fake-port replacement + +- **WHEN** a fake World application port drives state updates and errors +- **THEN** a World application consumer SHALL operate using only the exact-origin projected state and exact public protocol value types, while `WorldDomain` alone retains Runtime snapshot/source handling diff --git a/openspec/specs/wxt-typecheck-readiness/spec.md b/openspec/specs/wxt-typecheck-readiness/spec.md new file mode 100644 index 00000000..411576a6 --- /dev/null +++ b/openspec/specs/wxt-typecheck-readiness/spec.md @@ -0,0 +1,54 @@ +# wxt-typecheck-readiness Specification + +## Purpose + +TBD - created by archiving change repair-wxt-typecheck-readiness. Update Purpose after archive. + +## Requirements + +### Requirement: Canonical TypeScript gate prepares WXT state + +The root `check` script SHALL be exactly `wxt prepare && tsc --noEmit`. The installed WXT prepare command SHALL settle successfully before strict TypeScript checking begins. CI and local automation SHALL invoke this canonical script rather than depend on an earlier install lifecycle as the only WXT-generation prerequisite. + +#### Scenario: Typecheck starts without generated WXT state + +- **WHEN** installed dependencies are present but `.wxt` is absent +- **THEN** `pnpm run check` SHALL generate `.wxt/tsconfig.json` and then run `tsc --noEmit` successfully against the unchanged source + +#### Scenario: Typecheck starts with existing generated WXT state + +- **WHEN** `.wxt` already contains current generated state +- **THEN** `pnpm run check` SHALL safely refresh that state and run the same strict TypeScript gate + +#### Scenario: WXT preparation fails + +- **WHEN** `wxt prepare` rejects or exits non-zero +- **THEN** the canonical check SHALL fail immediately and SHALL NOT report a successful or independently executed TypeScript gate + +#### Scenario: TypeScript detects a real error + +- **WHEN** WXT preparation succeeds but `tsc --noEmit` reports a type error +- **THEN** the canonical check SHALL fail with that error and SHALL NOT suppress, waive, or rewrite it + +### Requirement: Generated WXT state remains disposable + +`.wxt/**` SHALL remain ignored generated checkout state. The repair SHALL NOT commit `.wxt/tsconfig.json` or any other generated WXT file, and repeated canonical checks SHALL leave no tracked diff. + +#### Scenario: Review generated output ownership + +- **WHEN** `pnpm run check` creates or refreshes `.wxt` +- **THEN** `.wxt/**` SHALL remain ignored, absent from the candidate commit, and removable before the next successful check + +#### Scenario: Repeat the canonical check + +- **WHEN** the canonical check is run twice on one unchanged checkout +- **THEN** both runs SHALL pass and the second run SHALL leave the tracked worktree clean + +### Requirement: Install lifecycle is not the typecheck authority + +The existing `postinstall` WXT preparation MAY remain for installation ergonomics, but a successful or previously cached install SHALL NOT be treated as proof that the current checkout is ready for TypeScript. The canonical check SHALL establish readiness itself. + +#### Scenario: Dependencies are restored from cache + +- **WHEN** `node_modules` is restored or already present while the checkout has no `.wxt` +- **THEN** `pnpm run check` SHALL not require lifecycle replay and SHALL prepare the current checkout before TypeScript diff --git a/package.json b/package.json index 76c2ffb8..c55fb1e7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "web-chat", "displayName": "WebChat", - "version": "1.9.7", + "version": "2.0.0", "description": "Chat with anyone on any website.", "type": "module", "scripts": { @@ -13,9 +13,14 @@ "pack": "cross-env NODE_ENV=production run-p pack:*", "pack:chrome": "wxt zip -b chrome", "pack:firefox": "wxt zip -b firefox", - "lint": "eslint --fix --cache", - "clear": "rimraf .output", - "check": "tsc --noEmit", + "format": "oxfmt --write .", + "format:check": "oxfmt --check .", + "lint": "oxlint --fix .", + "lint:check": "oxlint .", + "clear": "rimraf .output node_modules", + "test": "vitest run", + "test:e2e:chrome": "cross-env NODE_ENV=production pnpm build:chrome && node e2e/chrome-runtime.ts", + "check": "wxt prepare && tsc --noEmit", "prepare": "husky", "postinstall": "wxt prepare" }, @@ -44,47 +49,46 @@ }, "homepage": "https://github.com/molvqingtai/WebChat", "dependencies": { - "@hookform/resolvers": "^5.2.2", - "@number-flow/react": "^0.5.10", - "@perfsee/jsonr": "^1.14.2", - "@radix-ui/react-avatar": "^1.1.10", - "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", + "@hookform/resolvers": "^5.4.0", + "@number-flow/react": "^0.6.1", + "@radix-ui/react-avatar": "^1.2.2", + "@radix-ui/react-checkbox": "^1.3.7", + "@radix-ui/react-dropdown-menu": "^2.1.20", + "@radix-ui/react-hover-card": "^1.1.19", "@radix-ui/react-icons": "^1.3.2", - "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-portal": "^1.1.9", - "@radix-ui/react-presence": "^1.1.5", - "@radix-ui/react-radio-group": "^1.3.8", - "@radix-ui/react-scroll-area": "^1.2.10", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.6", - "@resreq/event-hub": "^1.6.0", + "@radix-ui/react-label": "^2.1.11", + "@radix-ui/react-popover": "^1.1.19", + "@radix-ui/react-portal": "^1.1.13", + "@radix-ui/react-presence": "^1.1.7", + "@radix-ui/react-radio-group": "^1.4.3", + "@radix-ui/react-scroll-area": "^1.2.14", + "@radix-ui/react-slot": "^1.3.0", + "@radix-ui/react-switch": "^1.3.3", + "@resreq/event-hub": "^1.7.2", "@rtco/client": "^0.3.6", - "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/typography": "^0.5.20", "@webcomponents/custom-elements": "^1.6.0", - "@webext-core/proxy-service": "^1.2.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cobe": "^0.6.5", - "comctx": "^1.4.3", - "danmu": "^0.18.1", - "date-fns": "^4.1.0", - "dompurify": "^3.2.7", - "framer-motion": "^12.23.22", - "hash-it": "^6.0.0", - "idb-keyval": "^6.2.2", - "imgcap": "^1.0.2", - "lucide-react": "^0.544.0", - "motion": "^12.23.22", - "nanoid": "^5.1.6", - "react": "^19.2.0", - "react-dom": "^19.2.0", - "react-hook-form": "^7.63.0", + "cobe": "^2.0.1", + "comctx": "^1.7.5", + "core-js": "^3.49.0", + "danmu": "^0.20.0", + "date-fns": "^4.4.0", + "framer-motion": "^12.42.2", + "hash-it": "^7.0.3", + "idb": "^8.0.3", + "idb-keyval": "^6.3.0", + "imgcap": "^1.1.0", + "lucide-react": "^1.25.0", + "motion": "^12.42.2", + "nanoid": "^6.0.0", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-hook-form": "^7.81.0", "react-markdown": "^10.1.0", - "react-use": "^17.6.0", - "react-virtuoso": "^4.14.1", + "react-use": "^17.6.1", + "react-virtuoso": "^4.18.11", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", "remesh": "^4.2.2", @@ -92,57 +96,51 @@ "remesh-react": "^4.1.2", "rxjs": "^7.8.2", "sonner": "^2.0.7", - "tailwind-merge": "^3.3.1", - "type-fest": "^5.0.1", - "unstorage": "^1.17.1", - "valibot": "1.1.0", - "zod": "^4.1.11" + "tailwind-merge": "^3.6.0", + "type-fest": "^5.8.0", + "unstorage": "^1.17.5", + "valibot": "1.4.2" }, "devDependencies": { - "@commitlint/cli": "^20.1.0", - "@commitlint/config-conventional": "^20.0.0", - "@eslint-react/eslint-plugin": "^2.0.5", - "@eslint/js": "^9.36.0", + "@commitlint/cli": "^21.2.1", + "@commitlint/config-conventional": "^21.2.0", + "@rolldown/plugin-babel": "0.2.3", "@semantic-release/changelog": "^6.0.3", "@semantic-release/exec": "^7.1.0", "@semantic-release/git": "^10.0.1", - "@tailwindcss/postcss": "^4.1.14", - "@tailwindcss/vite": "^4.1.14", - "@types/eslint": "^9.6.1", - "@types/eslint-plugin-tailwindcss": "^3.17.0", - "@types/node": "^24.6.2", - "@types/react": "^19.2.0", - "@types/react-dom": "^19.2.0", - "@typescript-eslint/parser": "^8.45.0", - "@vitejs/plugin-react": "^5.0.4", - "autoprefixer": "^10.4.21", + "@tailwindcss/postcss": "^4.3.3", + "@tailwindcss/vite": "^4.3.3", + "@types/node": "^26.1.1", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "autoprefixer": "^10.5.4", "babel-plugin-react-compiler": "19.1.0-rc.3", "cross-env": "^10.1.0", - "eslint": "^9.36.0", - "eslint-config-prettier": "^10.1.8", - "eslint-plugin-prettier": "^5.5.4", - "eslint-plugin-tailwindcss": "^3.18.2", - "globals": "^16.4.0", + "fake-indexeddb": "^6.2.5", "husky": "^9.1.7", - "lint-staged": "^16.2.3", + "lint-staged": "^17.0.8", "npm-run-all": "^4.1.5", - "postcss": "^8.5.6", - "postcss-rem-to-responsive-pixel": "^6.1.0", - "prettier": "^3.6.2", - "rimraf": "^6.0.1", - "semantic-release": "^24.2.9", - "tailwindcss": "^4.1.14", + "oxfmt": "^0.57.0", + "oxlint": "^1.72.0", + "postcss": "^8.5.19", + "postcss-rem-to-responsive-pixel": "^7.0.4", + "rimraf": "^6.1.3", + "semantic-release": "^25.0.7", + "tailwindcss": "^4.3.3", "tw-animate-css": "^1.4.0", - "typescript": "^5.9.3", - "typescript-eslint": "^8.45.0", - "vite-plugin-svgr": "^4.5.0", - "webext-bridge": "^6.0.1", - "wxt": "^0.20.11" + "typescript": "^7.0.2", + "vite-plugin-svgr": "^5.2.0", + "vitest": "^4.1.10", + "wxt": "^0.20.27" }, "lint-staged": { - "*.{js,jsx,ts,tsx}": "eslint --fix --cache" + "*.{js,jsx,ts,tsx}": [ + "oxfmt --write", + "oxlint --fix" + ] }, "engines": { - "node": ">=20.0.0" + "node": "^22.18.0 || >=23.6.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bb18881..43f02aa2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,68 +9,62 @@ importers: .: dependencies: '@hookform/resolvers': - specifier: ^5.2.2 - version: 5.2.2(react-hook-form@7.63.0(react@19.2.0)) + specifier: ^5.4.0 + version: 5.4.0(react-hook-form@7.81.0(react@19.2.7)) '@number-flow/react': - specifier: ^0.5.10 - version: 0.5.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@perfsee/jsonr': - specifier: ^1.14.2 - version: 1.14.2 + specifier: ^0.6.1 + version: 0.6.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-avatar': - specifier: ^1.1.10 - version: 1.1.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.2.2 + version: 1.2.2(@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) '@radix-ui/react-checkbox': - specifier: ^1.3.3 - version: 1.3.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.3.7 + version: 1.3.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) '@radix-ui/react-dropdown-menu': - specifier: ^2.1.16 - version: 2.1.16(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^2.1.20 + version: 2.1.20(@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) '@radix-ui/react-hover-card': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.1.19 + version: 1.1.19(@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) '@radix-ui/react-icons': specifier: ^1.3.2 - version: 1.3.2(react@19.2.0) + version: 1.3.2(react@19.2.7) '@radix-ui/react-label': - specifier: ^2.1.7 - version: 2.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^2.1.11 + version: 2.1.11(@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) '@radix-ui/react-popover': - specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.1.19 + version: 1.1.19(@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) '@radix-ui/react-portal': - specifier: ^1.1.9 - version: 1.1.9(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.1.13 + version: 1.1.13(@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) '@radix-ui/react-presence': - specifier: ^1.1.5 - version: 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.1.7 + version: 1.1.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) '@radix-ui/react-radio-group': - specifier: ^1.3.8 - version: 1.3.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.4.3 + version: 1.4.3(@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) '@radix-ui/react-scroll-area': - specifier: ^1.2.10 - version: 1.2.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.2.14 + version: 1.2.14(@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) '@radix-ui/react-slot': - specifier: ^1.2.3 - version: 1.2.3(@types/react@19.2.0)(react@19.2.0) + specifier: ^1.3.0 + version: 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-switch': - specifier: ^1.2.6 - version: 1.2.6(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^1.3.3 + version: 1.3.3(@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) '@resreq/event-hub': - specifier: ^1.6.0 - version: 1.6.0 + specifier: ^1.7.2 + version: 1.7.2 '@rtco/client': specifier: ^0.3.6 version: 0.3.6 '@tailwindcss/typography': - specifier: ^0.5.19 - version: 0.5.19(tailwindcss@4.1.14) + specifier: ^0.5.20 + version: 0.5.20(tailwindcss@4.3.3) '@webcomponents/custom-elements': specifier: ^1.6.0 version: 1.6.0 - '@webext-core/proxy-service': - specifier: ^1.2.1 - version: 1.2.1(@webext-core/messaging@2.3.0)(webextension-polyfill@0.12.0) class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -78,59 +72,62 @@ importers: specifier: ^2.1.1 version: 2.1.1 cobe: - specifier: ^0.6.5 - version: 0.6.5 + specifier: ^2.0.1 + version: 2.0.1 comctx: - specifier: ^1.4.3 - version: 1.4.3 + specifier: ^1.7.5 + version: 1.7.5 + core-js: + specifier: ^3.49.0 + version: 3.49.0 danmu: - specifier: ^0.18.1 - version: 0.18.1 + specifier: ^0.20.0 + version: 0.20.0 date-fns: - specifier: ^4.1.0 - version: 4.1.0 - dompurify: - specifier: ^3.2.7 - version: 3.2.7 + specifier: ^4.4.0 + version: 4.4.0 framer-motion: - specifier: ^12.23.22 - version: 12.23.22(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^12.42.2 + version: 12.42.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) hash-it: - specifier: ^6.0.0 - version: 6.0.0 + specifier: ^7.0.3 + version: 7.0.3 + idb: + specifier: ^8.0.3 + version: 8.0.3 idb-keyval: - specifier: ^6.2.2 - version: 6.2.2 + specifier: ^6.3.0 + version: 6.3.0 imgcap: - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.1.0 + version: 1.1.0 lucide-react: - specifier: ^0.544.0 - version: 0.544.0(react@19.2.0) + specifier: ^1.25.0 + version: 1.25.0(react@19.2.7) motion: - specifier: ^12.23.22 - version: 12.23.22(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^12.42.2 + version: 12.42.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) nanoid: - specifier: ^5.1.6 - version: 5.1.6 + specifier: ^6.0.0 + version: 6.0.0 react: - specifier: ^19.2.0 - version: 19.2.0 + specifier: ^19.2.7 + version: 19.2.7 react-dom: - specifier: ^19.2.0 - version: 19.2.0(react@19.2.0) + specifier: ^19.2.7 + version: 19.2.7(react@19.2.7) react-hook-form: - specifier: ^7.63.0 - version: 7.63.0(react@19.2.0) + specifier: ^7.81.0 + version: 7.81.0(react@19.2.7) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.0)(react@19.2.0) + version: 10.1.0(@types/react@19.2.17)(react@19.2.7) react-use: - specifier: ^17.6.0 - version: 17.6.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^17.6.1 + version: 17.6.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react-virtuoso: - specifier: ^4.14.1 - version: 4.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: ^4.18.11 + version: 4.18.11(react-dom@19.2.7(react@19.2.7))(react@19.2.7) remark-breaks: specifier: ^4.0.0 version: 4.0.0 @@ -145,151 +142,136 @@ importers: version: 4.1.0(remesh@4.2.2(rxjs@7.8.2)) remesh-react: specifier: ^4.1.2 - version: 4.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(remesh@4.2.2(rxjs@7.8.2))(rxjs@7.8.2) + version: 4.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(remesh@4.2.2(rxjs@7.8.2))(rxjs@7.8.2) rxjs: specifier: ^7.8.2 version: 7.8.2 sonner: specifier: ^2.0.7 - version: 2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tailwind-merge: - specifier: ^3.3.1 - version: 3.3.1 + specifier: ^3.6.0 + version: 3.6.0 type-fest: - specifier: ^5.0.1 - version: 5.0.1 + specifier: ^5.8.0 + version: 5.8.0 unstorage: - specifier: ^1.17.1 - version: 1.17.1(idb-keyval@6.2.2) + specifier: ^1.17.5 + version: 1.17.5(idb-keyval@6.3.0) valibot: - specifier: 1.1.0 - version: 1.1.0(typescript@5.9.3) - zod: - specifier: ^4.1.11 - version: 4.1.11 + specifier: 1.4.2 + version: 1.4.2(typescript@7.0.2) devDependencies: '@commitlint/cli': - specifier: ^20.1.0 - version: 20.1.0(@types/node@24.6.2)(typescript@5.9.3) + specifier: ^21.2.1 + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) '@commitlint/config-conventional': - specifier: ^20.0.0 - version: 20.0.0 - '@eslint-react/eslint-plugin': - specifier: ^2.0.5 - version: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint/js': - specifier: ^9.36.0 - version: 9.36.0 + specifier: ^21.2.0 + version: 21.2.0 + '@rolldown/plugin-babel': + specifier: 0.2.3 + version: 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@semantic-release/changelog': specifier: ^6.0.3 - version: 6.0.3(semantic-release@24.2.9(typescript@5.9.3)) + version: 6.0.3(semantic-release@25.0.7(typescript@7.0.2)) '@semantic-release/exec': specifier: ^7.1.0 - version: 7.1.0(semantic-release@24.2.9(typescript@5.9.3)) + version: 7.1.0(semantic-release@25.0.7(typescript@7.0.2)) '@semantic-release/git': specifier: ^10.0.1 - version: 10.0.1(semantic-release@24.2.9(typescript@5.9.3)) + version: 10.0.1(semantic-release@25.0.7(typescript@7.0.2)) '@tailwindcss/postcss': - specifier: ^4.1.14 - version: 4.1.14 + specifier: ^4.3.3 + version: 4.3.3 '@tailwindcss/vite': - specifier: ^4.1.14 - version: 4.1.14(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) - '@types/eslint': - specifier: ^9.6.1 - version: 9.6.1 - '@types/eslint-plugin-tailwindcss': - specifier: ^3.17.0 - version: 3.17.0 + specifier: ^4.3.3 + version: 4.3.3(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) '@types/node': - specifier: ^24.6.2 - version: 24.6.2 + specifier: ^26.1.1 + version: 26.1.1 '@types/react': - specifier: ^19.2.0 - version: 19.2.0 + specifier: ^19.2.17 + version: 19.2.17 '@types/react-dom': - specifier: ^19.2.0 - version: 19.2.0(@types/react@19.2.0) - '@typescript-eslint/parser': - specifier: ^8.45.0 - version: 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': - specifier: ^5.0.4 - version: 5.0.4(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) + specifier: ^6.0.3 + version: 6.0.3(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)))(babel-plugin-react-compiler@19.1.0-rc.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) + specifier: ^10.5.4 + version: 10.5.4(postcss@8.5.19) babel-plugin-react-compiler: specifier: 19.1.0-rc.3 version: 19.1.0-rc.3 cross-env: specifier: ^10.1.0 version: 10.1.0 - eslint: - specifier: ^9.36.0 - version: 9.36.0(jiti@2.6.1) - eslint-config-prettier: - specifier: ^10.1.8 - version: 10.1.8(eslint@9.36.0(jiti@2.6.1)) - eslint-plugin-prettier: - specifier: ^5.5.4 - version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.36.0(jiti@2.6.1)))(eslint@9.36.0(jiti@2.6.1))(prettier@3.6.2) - eslint-plugin-tailwindcss: - specifier: ^3.18.2 - version: 3.18.2(tailwindcss@4.1.14) - globals: - specifier: ^16.4.0 - version: 16.4.0 + fake-indexeddb: + specifier: ^6.2.5 + version: 6.2.5 husky: specifier: ^9.1.7 version: 9.1.7 lint-staged: - specifier: ^16.2.3 - version: 16.2.3 + specifier: ^17.0.8 + version: 17.0.8 npm-run-all: specifier: ^4.1.5 version: 4.1.5 + oxfmt: + specifier: ^0.57.0 + version: 0.57.0 + oxlint: + specifier: ^1.72.0 + version: 1.74.0 postcss: - specifier: ^8.5.6 - version: 8.5.6 + specifier: ^8.5.19 + version: 8.5.19 postcss-rem-to-responsive-pixel: - specifier: ^6.1.0 - version: 6.1.0 - prettier: - specifier: ^3.6.2 - version: 3.6.2 + specifier: ^7.0.4 + version: 7.0.4(postcss@8.5.19) rimraf: - specifier: ^6.0.1 - version: 6.0.1 + specifier: ^6.1.3 + version: 6.1.3 semantic-release: - specifier: ^24.2.9 - version: 24.2.9(typescript@5.9.3) + specifier: ^25.0.7 + version: 25.0.7(typescript@7.0.2) tailwindcss: - specifier: ^4.1.14 - version: 4.1.14 + specifier: ^4.3.3 + version: 4.3.3 tw-animate-css: specifier: ^1.4.0 version: 1.4.0 typescript: - specifier: ^5.9.3 - version: 5.9.3 - typescript-eslint: - specifier: ^8.45.0 - version: 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) + specifier: ^7.0.2 + version: 7.0.2 vite-plugin-svgr: - specifier: ^4.5.0 - version: 4.5.0(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)) - webext-bridge: - specifier: ^6.0.1 - version: 6.0.1 + specifier: ^5.2.0 + version: 5.2.0(rollup@4.52.3)(typescript@7.0.2)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vitest: + specifier: ^4.1.10 + version: 4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) wxt: - specifier: ^0.20.11 - version: 0.20.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(rollup@4.52.3)(yaml@2.8.1) + specifier: ^0.20.27 + version: 0.20.27(@types/node@26.1.1)(eslint@10.7.0(jiti@2.7.0))(jiti@2.7.0)(rolldown@1.1.5)(rollup@4.52.3)(yaml@2.9.0) packages: - '@1natsu/wait-element@4.1.2': - resolution: {integrity: sha512-qWxSJD+Q5b8bKOvESFifvfZ92DuMsY+03SBNjTO34ipJLP6mZ9yK4bQz/vlh48aEQXoJfaZBqUwKL5BdI5iiWw==} + '@1natsu/wait-element@4.2.0': + resolution: {integrity: sha512-Om0Q+WE9mNrpY4AwMTvkFiYHv8VM7TML3PvOqXy+w6kAjLjKhGYHYX+305+a6J8RVpds9s7IF2Z5aOPYwULFNw==} + + '@actions/core@3.0.1': + resolution: {integrity: sha512-a6d/Nwahm9fliVGRhdhofo40HjHQasUPusmc7vBfyky+7Z+P2A1J68zyFVaNcEclc/Se+eO595oAr5nwEIoIUA==} + + '@actions/exec@3.0.0': + resolution: {integrity: sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==} + + '@actions/http-client@4.0.1': + resolution: {integrity: sha512-+Nvd1ImaOZBSoPbsUtEhv+1z99H12xzncCkz0a3RuehINE81FZSe2QTj3uvAPTcJX/SCzUQHQ0D1GrPMbrPitg==} + + '@actions/io@3.0.2': + resolution: {integrity: sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==} '@aklinker1/rollup-plugin-visualizer@5.12.0': resolution: {integrity: sha512-X24LvEGw6UFmy0lpGJDmXsMyBD58XmX1bbwsaMLhNoM+UMQfQ3b2RtC+nz4b/NoRK5r6QJSKJHBNVeUdwqybaQ==} @@ -305,169 +287,169 @@ packages: resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@babel/code-frame@7.27.1': - resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} engines: {node: '>=6.9.0'} - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-transform-react-jsx-self@7.27.1': - resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.27.1': - resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/runtime@7.28.2': resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + '@babel/runtime@7.29.7': + resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} engines: {node: '>=6.9.0'} '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} - '@commitlint/cli@20.1.0': - resolution: {integrity: sha512-pW5ujjrOovhq5RcYv5xCpb4GkZxkO2+GtOdBW2/qrr0Ll9tl3PX0aBBobGQl3mdZUbOBgwAexEQLeH6uxL0VYg==} - engines: {node: '>=v18'} + '@commitlint/cli@21.2.1': + resolution: {integrity: sha512-blsZGe29hJ72VGEFVl72IVYX+1vsfINpjA9yWQA6i7OKD/McGEOXg08sKIRKjFk4JvzhV/9n0l3i6NooPLTNfg==} + engines: {node: '>=22.12.0'} hasBin: true - '@commitlint/config-conventional@20.0.0': - resolution: {integrity: sha512-q7JroPIkDBtyOkVe9Bca0p7kAUYxZMxkrBArCfuD3yN4KjRAenP9PmYwnn7rsw8Q+hHq1QB2BRmBh0/Z19ZoJw==} - engines: {node: '>=v18'} + '@commitlint/config-conventional@21.2.0': + resolution: {integrity: sha512-Qf8WRDVcyVd14if6VTWenebxFbKnVnbzPUJjlzjkyJGeHK2xCGd63Dr1XZzj0plXKQb9P0BfOxoc1HVeCo2BWQ==} + engines: {node: '>=22.12.0'} - '@commitlint/config-validator@20.0.0': - resolution: {integrity: sha512-BeyLMaRIJDdroJuYM2EGhDMGwVBMZna9UiIqV9hxj+J551Ctc6yoGuGSmghOy/qPhBSuhA6oMtbEiTmxECafsg==} - engines: {node: '>=v18'} + '@commitlint/config-validator@21.2.0': + resolution: {integrity: sha512-t7AzNHAKeIdo/3NRGwzpufKHsKkPHmFs/56N2Fnsh0/r0rGtnQzTxk6vnFgjaGr4hdSQKNB50/KAhR9Yk4LJKA==} + engines: {node: '>=22.12.0'} - '@commitlint/ensure@20.0.0': - resolution: {integrity: sha512-WBV47Fffvabe68n+13HJNFBqiMH5U1Ryls4W3ieGwPC0C7kJqp3OVQQzG2GXqOALmzrgAB+7GXmyy8N9ct8/Fg==} - engines: {node: '>=v18'} + '@commitlint/ensure@21.2.0': + resolution: {integrity: sha512-76IF9vDNS13lAzEEik9eKwzt8f9hYhWiwVXZ2AnyLCz5/f511FsEQ3pw1X3/zSQpdRLQU7i5qDMVKyXi1GWjSg==} + engines: {node: '>=22.12.0'} - '@commitlint/execute-rule@20.0.0': - resolution: {integrity: sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==} - engines: {node: '>=v18'} + '@commitlint/execute-rule@21.0.1': + resolution: {integrity: sha512-RifH+FmImozKBE6mozhF4K3r2RRKP7SMi/Q/zLCmExtp5e05lhHOUYqGBlFBAGNHaZxU/WYw1XuugYK9jQzqnA==} + engines: {node: '>=22.12.0'} - '@commitlint/format@20.0.0': - resolution: {integrity: sha512-zrZQXUcSDmQ4eGGrd+gFESiX0Rw+WFJk7nW4VFOmxub4mAATNKBQ4vNw5FgMCVehLUKG2OT2LjOqD0Hk8HvcRg==} - engines: {node: '>=v18'} + '@commitlint/format@21.2.0': + resolution: {integrity: sha512-c4q64xaav2U83t7k7RyzJerBZurPer7FxUOY0RL5L/6CZijZ7K+s6HIBGIghj0ey1P2+seRX0J9XQYtDued6tg==} + engines: {node: '>=22.12.0'} - '@commitlint/is-ignored@20.0.0': - resolution: {integrity: sha512-ayPLicsqqGAphYIQwh9LdAYOVAQ9Oe5QCgTNTj+BfxZb9b/JW222V5taPoIBzYnAP0z9EfUtljgBk+0BN4T4Cw==} - engines: {node: '>=v18'} + '@commitlint/is-ignored@21.2.0': + resolution: {integrity: sha512-4/eB0vBN7L88O/oC4ajAEqi7j2ZfNgxl/+11RfAV9YosejZgDXhY2C9VcHnHJhOzPLoSy5P3Mg/46kqeyJfXKw==} + engines: {node: '>=22.12.0'} - '@commitlint/lint@20.0.0': - resolution: {integrity: sha512-kWrX8SfWk4+4nCexfLaQT3f3EcNjJwJBsSZ5rMBw6JCd6OzXufFHgel2Curos4LKIxwec9WSvs2YUD87rXlxNQ==} - engines: {node: '>=v18'} + '@commitlint/lint@21.2.0': + resolution: {integrity: sha512-ceO5dp9pLjEZ6y6qbq/uXWXDPykqqlTsyzoQ0NzecpisSJhK3kTy9qzQoPeJuWG/IMNdV1lO0RgmzqoAlSi1uw==} + engines: {node: '>=22.12.0'} - '@commitlint/load@20.1.0': - resolution: {integrity: sha512-qo9ER0XiAimATQR5QhvvzePfeDfApi/AFlC1G+YN+ZAY8/Ua6IRrDrxRvQAr+YXUKAxUsTDSp9KXeXLBPsNRWg==} - engines: {node: '>=v18'} + '@commitlint/load@21.2.0': + resolution: {integrity: sha512-RjlzWQqruRwIenJEfZtq7kG97co97nKoHpflE5YnF61tDLXxHPrdWImgzw6VL6MlFyaOcVlk74eBV8ZQmc3oIA==} + engines: {node: '>=22.12.0'} - '@commitlint/message@20.0.0': - resolution: {integrity: sha512-gLX4YmKnZqSwkmSB9OckQUrI5VyXEYiv3J5JKZRxIp8jOQsWjZgHSG/OgEfMQBK9ibdclEdAyIPYggwXoFGXjQ==} - engines: {node: '>=v18'} + '@commitlint/message@21.2.0': + resolution: {integrity: sha512-YxGoiXD/HXNXLJPrQwE5poXa+XH0CBEm+mdvbHQP0g6MV/dmJyUFCzPNzZbxL93GvZ70TmtTK0Z0/IBpAqHv8g==} + engines: {node: '>=22.12.0'} - '@commitlint/parse@20.0.0': - resolution: {integrity: sha512-j/PHCDX2bGM5xGcWObOvpOc54cXjn9g6xScXzAeOLwTsScaL4Y+qd0pFC6HBwTtrH92NvJQc+2Lx9HFkVi48cg==} - engines: {node: '>=v18'} + '@commitlint/parse@21.2.0': + resolution: {integrity: sha512-QHWxG4d0PLTF634/AdyZ0MQS+CLn5YOuJlCFhMMlSGKFxzYGUetkHBj18xgBD+6fVzUrA2lrCdi/vlS2f/oYXg==} + engines: {node: '>=22.12.0'} - '@commitlint/read@20.0.0': - resolution: {integrity: sha512-Ti7Y7aEgxsM1nkwA4ZIJczkTFRX/+USMjNrL9NXwWQHqNqrBX2iMi+zfuzZXqfZ327WXBjdkRaytJ+z5vNqTOA==} - engines: {node: '>=v18'} + '@commitlint/read@21.2.1': + resolution: {integrity: sha512-hUW7EJQnNTL0vPOmVMNK4CrnrNBN0nN+JJHReFkdHO5y4iyHeEmTBwuC15OCqUTjxWo7idnH1LftfpWVIaPWIA==} + engines: {node: '>=22.12.0'} - '@commitlint/resolve-extends@20.1.0': - resolution: {integrity: sha512-cxKXQrqHjZT3o+XPdqDCwOWVFQiae++uwd9dUBC7f2MdV58ons3uUvASdW7m55eat5sRiQ6xUHyMWMRm6atZWw==} - engines: {node: '>=v18'} + '@commitlint/resolve-extends@21.2.0': + resolution: {integrity: sha512-4O/1j51+79Wth9s/MGxt/5gs0XYLDgNlYpltQfhAvLE0itusLKs9zruxbiNg1oOkmkb9L9L4USYGjEj7n87NxA==} + engines: {node: '>=22.12.0'} - '@commitlint/rules@20.0.0': - resolution: {integrity: sha512-gvg2k10I/RfvHn5I5sxvVZKM1fl72Sqrv2YY/BnM7lMHcYqO0E2jnRWoYguvBfEcZ39t+rbATlciggVe77E4zA==} - engines: {node: '>=v18'} + '@commitlint/rules@21.2.0': + resolution: {integrity: sha512-C2yXMNpiB8ETZKfx5JD8+ExgF8vTU1VQMKPSUUYwqKpw9oJWQBrlXBpdU038mj2WPjof7o9UzFpmTyBeGMZwZg==} + engines: {node: '>=22.12.0'} - '@commitlint/to-lines@20.0.0': - resolution: {integrity: sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==} - engines: {node: '>=v18'} + '@commitlint/to-lines@21.0.1': + resolution: {integrity: sha512-bd1BFII7p1EQZre9Kaj+kKaMFP3cFCdt21K7DItVux9XP5WjLgJ0/Uy1pJJh9aPwVJ6SKg62PxqlZaHI8hQAXw==} + engines: {node: '>=22.12.0'} - '@commitlint/top-level@20.0.0': - resolution: {integrity: sha512-drXaPSP2EcopukrUXvUXmsQMu3Ey/FuJDc/5oiW4heoCfoE5BdLQyuc7veGeE3aoQaTVqZnh4D5WTWe2vefYKg==} - engines: {node: '>=v18'} + '@commitlint/top-level@21.2.0': + resolution: {integrity: sha512-Y5gmQ+KxzqCrBFJfLvFEPvvwD3LDiNZoTT2yeFBm96M8qhmqSzQc5DvX3rheAaAMjyIvMXOCLS/mWfdpONsjyQ==} + engines: {node: '>=22.12.0'} - '@commitlint/types@20.0.0': - resolution: {integrity: sha512-bVUNBqG6aznYcYjTjnc3+Cat/iBgbgpflxbIBTnsHTX0YVpnmINPEkSRWymT2Q8aSH3Y7aKnEbunilkYe8TybA==} - engines: {node: '>=v18'} + '@commitlint/types@21.2.0': + resolution: {integrity: sha512-7zVFCDB2reMvJH5dmbKnOQPjZEvjdJTH8jc0U/PIPU1r3/+vf5pD1HlfitV2MWsWXrvu7u39iY1lyLUPOaN0Gw==} + engines: {node: '>=22.12.0'} + + '@conventional-changelog/git-client@3.1.0': + resolution: {integrity: sha512-Tqa/gHco2WJWa740NRjOrfKVvzIqxkZpecb8bemaQ8sKM5PXb1UK4uTyTb/1wIqNuOVaDOFxyBdhTIQZn6gdjQ==} + engines: {node: '>=22'} + peerDependencies: + conventional-commits-filter: ^6.0.1 + conventional-commits-parser: ^7.0.1 + peerDependenciesMeta: + conventional-commits-filter: + optional: true + conventional-commits-parser: + optional: true + + '@conventional-changelog/template@1.2.1': + resolution: {integrity: sha512-TzlTVpKPjaqW6qOYjQcYUDuGsLCNsvFHVBXkYGTAnf5V37jCWrE5haKNXzz0WZUtVHjrpV76L1buANjwXMfT8w==} + engines: {node: '>=22'} '@devicefarmer/adbkit-logcat@2.1.3': resolution: {integrity: sha512-yeaGFjNBc/6+svbDeul1tNHtNChw6h8pSHAt5D+JsedUrMTN7tla7B15WLDyekxsuS2XlZHRxpuC6m92wiwCNw==} @@ -482,6 +464,15 @@ packages: engines: {node: '>= 0.10.4'} hasBin: true + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} + + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} + + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} + '@emotion/is-prop-valid@1.3.1': resolution: {integrity: sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==} @@ -491,257 +482,222 @@ packages: '@epic-web/invariant@1.0.0': resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} engines: {node: '>=18'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.12.1': - resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint-react/ast@2.0.5': - resolution: {integrity: sha512-HnSfltDeem0CuHqAVkforpMYmWYvVSK7hx9JaM2UX2thxq+ANtRhF+Bl0loxl+R35xja3cO7qNqvFAdAmQhyFw==} - engines: {node: '>=20.19.0'} - - '@eslint-react/core@2.0.5': - resolution: {integrity: sha512-ZtmagpyEFYJQeOsT8Ycj8r2Zn8K/L4xj1XiGF3uEZQ0QeZh0a1lkieNyB4baZ1MbjZlKfpSuybOX1ncQC2pqKA==} - engines: {node: '>=20.19.0'} - - '@eslint-react/eff@2.0.5': - resolution: {integrity: sha512-PARItfaOis2ATjdDmrr7+Wo+nAVdOiKRl9noAT35dsVpKO+VrE8g7JjvX8hcC8SQSjq4LCOTaQApJKo0JRdhuw==} - engines: {node: '>=20.19.0'} - - '@eslint-react/eslint-plugin@2.0.5': - resolution: {integrity: sha512-YIqqDatuG8Udf5Y+ShHPZt7J+PivG5fPeqMrhxpbDE7dn1u1bui3LQNQv861eOthGCs0SvrO2Ttj3pyNecs3jA==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - '@eslint-react/kit@2.0.5': - resolution: {integrity: sha512-ehUiCKP8CBfGrQZnbFEB7GrLBE6SVNi3yT3sRnNih0YsWMdnqZkn8NKhbYzHN5EfDhj1tmENFBPzwEKQfpivWA==} - engines: {node: '>=20.19.0'} - - '@eslint-react/shared@2.0.5': - resolution: {integrity: sha512-lfakEmJOUNxUXY/iOfggJlM0Hg5//2nEGAsbswevRfDYtfAVTAT1DBKPmI/a9raU+6y+Zcm0dDsZAd7JNcji/w==} - engines: {node: '>=20.19.0'} - - '@eslint-react/var@2.0.5': - resolution: {integrity: sha512-AZXCxgRLgPLZtYvZOkB5jAYyGhRA2h1X/qS8JHWG+4lSrQnvRzGmjA2TG/LKsEQusjX/xDR/T3r9OZZB2JLIlQ==} - engines: {node: '>=20.19.0'} - - '@eslint/config-array@0.21.0': - resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/config-helpers@0.3.1': - resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.6.0': + resolution: {integrity: sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.15.2': - resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.36.0': - resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.7.2': + resolution: {integrity: sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/object-schema@2.1.6': - resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/core@1.8.0': + resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@eslint/plugin-kit@0.3.5': - resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@floating-ui/dom@1.8.0': + resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - '@floating-ui/core@1.7.3': - resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==} - - '@floating-ui/dom@1.7.4': - resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==} - - '@floating-ui/react-dom@2.1.6': - resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==} + '@floating-ui/react-dom@2.1.9': + resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@floating-ui/utils@0.2.12': + resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - '@hookform/resolvers@5.2.2': - resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} + '@hookform/resolvers@5.4.0': + resolution: {integrity: sha512-EIsqr/t/qbinPIhGjMdtvutIN1Kk4uwbROE9/UQ93CAVGR7GkA7Y92+fX80OzXi/OB67jVFYwKGO1WzkxmkFZw==} peerDependencies: react-hook-form: ^7.55.0 - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -752,22 +708,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@isaacs/fs-minipass@4.0.1': - resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} - engines: {node: '>=18.0.0'} - '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -784,24 +724,14 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@lukeed/csprng@1.1.0': - resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} - engines: {node: '>=8'} - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 - '@number-flow/react@0.5.10': - resolution: {integrity: sha512-a8Wh5eNITn7Km4xbddAH7QH8eNmnduR6k34ER1hkHSGO4H2yU1DDnuAWLQM99vciGInFODemSc0tdxrXkJEpbA==} + '@number-flow/react@0.6.1': + resolution: {integrity: sha512-JsvB8FrmtRmn251jbJnFD7Tr6Sv/eQMLUAlbshGo4CfAPk0qgNYTio9HuFoETqtuflcJHQezeGsSTPbUn1oplw==} peerDependencies: react: ^18 || ^19 react-dom: ^18 || ^19 @@ -810,56 +740,296 @@ packages: resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} engines: {node: '>= 20'} - '@octokit/core@7.0.5': - resolution: {integrity: sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==} + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} engines: {node: '>= 20'} - '@octokit/endpoint@11.0.1': - resolution: {integrity: sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==} + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} engines: {node: '>= 20'} - '@octokit/graphql@9.0.2': - resolution: {integrity: sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==} + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} engines: {node: '>= 20'} - '@octokit/openapi-types@26.0.0': - resolution: {integrity: sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==} + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} - '@octokit/plugin-paginate-rest@13.2.0': - resolution: {integrity: sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==} + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=6' - '@octokit/plugin-retry@8.0.2': - resolution: {integrity: sha512-mVPCe77iaD8g1lIX46n9bHPUirFLzc3BfIzsZOpB7bcQh1ecS63YsAgcsyMGqvGa2ARQWKEFTrhMJX2MLJVHVw==} + '@octokit/plugin-retry@8.1.0': + resolution: {integrity: sha512-O1FZgXeiGb2sowEr/hYTr6YunGdSAFWnr2fyW39Ah85H8O33ELASQxcvOFF5LE6Tjekcyu2ms4qAzJVhSaJxTw==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': '>=7' - '@octokit/plugin-throttling@11.0.2': - resolution: {integrity: sha512-ntNIig4zZhQVOZF4fG9Wt8QCoz9ehb+xnlUwp74Ic2ANChCk8oKmRwV9zDDCtrvU1aERIOvtng8wsalEX7Jk5Q==} + '@octokit/plugin-throttling@11.0.3': + resolution: {integrity: sha512-34eE0RkFCKycLl2D2kq7W+LovheM/ex3AwZCYN8udpi6bxsyjZidb2McXs69hZhLmJlDqTSP8cH+jSRpiaijBg==} engines: {node: '>= 20'} peerDependencies: '@octokit/core': ^7.0.0 - '@octokit/request-error@7.0.1': - resolution: {integrity: sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==} + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} engines: {node: '>= 20'} - '@octokit/request@10.0.5': - resolution: {integrity: sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==} + '@octokit/request@10.0.11': + resolution: {integrity: sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==} engines: {node: '>= 20'} - '@octokit/types@15.0.0': - resolution: {integrity: sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==} + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + + '@oxc-project/types@0.139.0': + resolution: {integrity: sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==} + + '@oxfmt/binding-android-arm-eabi@0.57.0': + resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxfmt/binding-android-arm64@0.57.0': + resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxfmt/binding-darwin-arm64@0.57.0': + resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxfmt/binding-darwin-x64@0.57.0': + resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxfmt/binding-freebsd-x64@0.57.0': + resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxfmt/binding-linux-x64-musl@0.57.0': + resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxfmt/binding-openharmony-arm64@0.57.0': + resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@oxlint/binding-android-arm-eabi@1.74.0': + resolution: {integrity: sha512-+gHd12muVI9ZLBaWLPkHt3Fj7jihFjgQ1MGtBaRL8vWrWrI0P7dLUty/cHrHS0oqPYIRgQUJsPu2CExQuMcwNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxlint/binding-android-arm64@1.74.0': + resolution: {integrity: sha512-xjKdoMB+H+RCOByv/7l7nfIGW9mlOisqYdcyC75UqYuQecLpReAeEYUf2CNeDEI3KtmUgxpRw/+c63y4AeF/Bw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@oxlint/binding-darwin-arm64@1.74.0': + resolution: {integrity: sha512-iUK7wvc6sejMKsC+Pt67mntoF5weFcyEunhZfLJceU6gL419mexz5wBkSx/EnkFBExMLNtOi9fnDSc5xfK0IzQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@oxlint/binding-darwin-x64@1.74.0': + resolution: {integrity: sha512-ggKc/tn5SJ1u2yG2izC6VKODfYKV8MQ2AicJlNzOjuyrC29udvOef6/JzK2r32xqCnBDLFouR1VCkjzEI0/N9Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@oxlint/binding-freebsd-x64@1.74.0': + resolution: {integrity: sha512-u++dH/43jy9hTLbneaWlS0gla/Bp1JdwJ2zgevCl8nDFUh6qRCGMxcL0f0lb7By3A9p/LfFr+7cG4HU1hG856g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@oxlint/binding-linux-arm-gnueabihf@1.74.0': + resolution: {integrity: sha512-Sj1zmtFDVTPeIbIz4ZfcXAbFHqCmKCXdCUlAJzvTF7I20NTH1RDpoF2PhkqNODutJzVhJYmm3oz0GwgY+tvE2g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm-musleabihf@1.74.0': + resolution: {integrity: sha512-//PKyQb/tQXcHArx2f7z+oVI/eMS2Jpv+edNuAtOrgIhWdGcpHxogveAxzmF2rpH1AIHp4Hq04RF/rgJdiICnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxlint/binding-linux-arm64-gnu@1.74.0': + resolution: {integrity: sha512-/k1Me+aX2tjuH10K62mLS0y8cLkJBHX6Ce0xPK+eWeel4bSdEGZ8dv4+hYMzg0GrSmjwy4yAYsDPeEeKBft/2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-arm64-musl@1.74.0': + resolution: {integrity: sha512-3tFSjBxc5D8/zvjEuLvOqcA8ZXKD0+6NuaVO/edeamNc49MoAsbfaC9s1UiwODwgF6slGaF8yJA2TPkukd77tg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-ppc64-gnu@1.74.0': + resolution: {integrity: sha512-9QggtPkSPXOCTu8Szis7auOK/sC7KdQaN+/TujP7YVVhzCAOhgdRfgv8uEz0r2tk5xdgus5rLYUrCDoZNtiRUw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-gnu@1.74.0': + resolution: {integrity: sha512-VM5VPUJ4DJIWiK+AZn8FScUqMr6OFrCAYybMYjEEi7W13ParI64MByiXTkKMqZpBmvQ9zxl9Ebq2VUOiZRJYUg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-riscv64-musl@1.74.0': + resolution: {integrity: sha512-SaDY1gh9rOA592J54g+gu5hkOFFQBZsMmIYHs+NRHG+Uq0OxtuuCXMWQ3vu1830Eugv5uMXyjG+bv2Z9y4IXjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@oxlint/binding-linux-s390x-gnu@1.74.0': + resolution: {integrity: sha512-ZATQeHZCyr6MbDveg0obD5sxLHFOghtOdC5jwVwYlvFWqtFOxctgFEG6Ef/64hYvZrWyhyCckB10AelqLopeDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-gnu@1.74.0': + resolution: {integrity: sha512-+aIvJyrdeD7LwCQ2WYLMUWNmnbeDRSPb40aBYtPjD9+PTqUwgJnk+HK5yLfSMeqXrMrDhE9uTmtt2y50tvjhHw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@oxlint/binding-linux-x64-musl@1.74.0': + resolution: {integrity: sha512-XyktaR8lhK2qWiCK0Tk8oYD+/cgn+oHA6ddRnxSSXUKkkojkV78CmShZUxQF+yrBFs0SuW+JBOPG6hecyc/iZg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@oxlint/binding-openharmony-arm64@1.74.0': + resolution: {integrity: sha512-mzbjrPl4neaVUiJ1fUiEUxTGaSZBoiKtaoB6jmIpz9S+VOA2vDYmJpihQ82w6178V5jxziclTg8Cgj5yF6tTDg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@oxlint/binding-win32-arm64-msvc@1.74.0': + resolution: {integrity: sha512-vUAe9okpS2Oa5+lX67lqHMuNUvfkleRKwrUDJ/WJBsgmddvZ1mrsh2HVmuFDRzqFELhaJhFaCNOuR6a7L3rtIA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] - '@perfsee/jsonr@1.14.2': - resolution: {integrity: sha512-cF5kWQfYiYcOI8SSZ86vZLVyAUgM6r4Q2knxhaPLQSzzQI8b9OwGd8YfB34szuQUPkm2/rxSDs/UiKEGjB8P5Q==} + '@oxlint/binding-win32-ia32-msvc@1.74.0': + resolution: {integrity: sha512-yyXXJyYYSXL4I8K8jAWjJs+J3fa9gH2JmEbo4f5adm+1tNC9itseicBNuwK7BDHvqQ5J534s+yDULu89vYL2ZQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@oxlint/binding-win32-x64-msvc@1.74.0': + resolution: {integrity: sha512-VTC9IYTIMrVUk/i6Ms1ohzzDKZFkWn0KU2OBbPBzgmVZ2V30165T/zK4LztTr0Xgp9fZ1qQZ1rsZAu/rEmySlA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] '@pnpm/config.env-replace@1.1.0': resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} @@ -869,18 +1039,18 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.3.1': - resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + '@pnpm/npm-conf@3.0.3': + resolution: {integrity: sha512-//0sR/cow/s4ICQaYoAobOl4aU8cjU6x/V24V7XkKotb9+O+3zySIYp146vpaobYHnxa4pZX8NkV54Z5AwbDKA==} engines: {node: '>=12'} - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + '@radix-ui/number@1.1.2': + resolution: {integrity: sha512-ceTwaxc4I5IOi97DgCotl3pqiyRGvffcc0oOsE2dQYaJOFIDsDt4VWG6xEbg1QePv9QWausCEIppud/tJ1wNig==} - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + '@radix-ui/primitive@1.1.5': + resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + '@radix-ui/react-arrow@1.1.11': + resolution: {integrity: sha512-Kdil9BB1rIFC/khmf4hC35bn8701AJcizTU7G7cUbEbk5XqqbjDuHW60uUfKqO5WojjZcbAW51Q7P0hRmMLw8A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -892,8 +1062,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-avatar@1.1.10': - resolution: {integrity: sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog==} + '@radix-ui/react-avatar@1.2.2': + resolution: {integrity: sha512-sST0qh8GzOB7besQ3tMLWLyngnRuSk0gc/Hm+667KYKQFCt6Y6ZXv25WlqM7dIDK54ULCh5+CHmk4LIolzfz+A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -905,8 +1075,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-checkbox@1.3.3': - resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + '@radix-ui/react-checkbox@1.3.7': + resolution: {integrity: sha512-JroKHfQBfh+fDuzpPsBC+pESkhuq8ql4hljTguz8MWnS35cISr3d/Jhl9kYrB44FlDtxCArYdDvTx+BSsJ64rQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -918,8 +1088,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -931,8 +1101,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -940,8 +1110,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -949,8 +1119,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-direction@1.1.1': - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -958,8 +1128,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + '@radix-ui/react-dismissable-layer@1.1.15': + resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -971,8 +1141,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-dropdown-menu@2.1.16': - resolution: {integrity: sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==} + '@radix-ui/react-dropdown-menu@2.1.20': + resolution: {integrity: sha512-slfm+rRaZRuQBvHq60lXvSVUPhid0IPtjSZzIuUlWZMUs01iYZNlGS3mJgRD3ChLQVBAYlKiL/tFyWGX+dz8Xw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -984,8 +1154,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -993,8 +1163,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + '@radix-ui/react-focus-scope@1.1.12': + resolution: {integrity: sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1006,8 +1176,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-hover-card@1.1.15': - resolution: {integrity: sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==} + '@radix-ui/react-hover-card@1.1.19': + resolution: {integrity: sha512-2KTgMLQtKvicznQgbindEI2RZ3QbDIwU5gabjUPwFJsormjGDz+rUvO4NANmYwzEEpTcTONUt33vBHIfTIVSfw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1024,8 +1194,8 @@ packages: peerDependencies: react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1033,8 +1203,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-label@2.1.7': - resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + '@radix-ui/react-label@2.1.11': + resolution: {integrity: sha512-3PKvDDxOn62k0oV1n4QtNtD2vpu+zYjXR7ojLBPaO6SPvhy53yg0vAmgNeBQeJW5rV3dffoRG+HYfLBZuzw0CQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1046,8 +1216,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-menu@2.1.16': - resolution: {integrity: sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==} + '@radix-ui/react-menu@2.1.20': + resolution: {integrity: sha512-VsUrXxFe9d2ScbZF0fR/oPR1+qjyeLs5p0jzG8h90puMoA9bq4SirYlXbE+USRg9Q2qTeJSFNqjw2nts8jJe4w==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1059,8 +1229,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popover@1.1.15': - resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + '@radix-ui/react-popover@1.1.19': + resolution: {integrity: sha512-jkrTdQVxnIB8fpn0NyyxW9CTB5aCXZZelVz5z+Xmii6g5WxMqS3fInNslZ63puP39+Puu4jYohUK31y3dT87gQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1072,8 +1242,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + '@radix-ui/react-popper@1.3.3': + resolution: {integrity: sha512-mS7dGpyjv6b+gsDjLF7e0ia1W4Im1B1hSCy2yuXlHuvnZxHKagfDaobt/KAKt27EpZMit2pss8eJBVyVjEWM+g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1085,8 +1255,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1098,8 +1268,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-presence@1.1.5': - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + '@radix-ui/react-presence@1.1.7': + resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1111,8 +1281,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1124,8 +1294,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-radio-group@1.3.8': - resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==} + '@radix-ui/react-radio-group@1.4.3': + resolution: {integrity: sha512-WwZFjWV4s3aC1QtR3k04R+oANHtX2q6fgKlc7MCEiDNlnTxCZ3H8k3mHtEgVlOejystwk1WQgarQhNOQZ2bK1g==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1137,8 +1307,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-roving-focus@1.1.11': - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + '@radix-ui/react-roving-focus@1.1.15': + resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1150,8 +1320,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-scroll-area@1.2.10': - resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} + '@radix-ui/react-scroll-area@1.2.14': + resolution: {integrity: sha512-bBODCWZK7JTbQLHs0uIP4f73wIWatakK4OS33UzkR1x897wu0PuO658a3f+6P2GEGyDzGYMuHRatMVoAk9WZTw==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1163,8 +1333,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1172,8 +1342,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.2.6': - resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} + '@radix-ui/react-switch@1.3.3': + resolution: {integrity: sha512-1+mlB4/lxJfk5tgJ4g+R5mUCbRpPE1T9+UsEyeLYbGgMtwiMgmuTnfKz4Mw1nHALHjuwyxw4MLd4cSHn6pNSlQ==} peerDependencies: '@types/react': '*' '@types/react-dom': '*' @@ -1185,8 +1355,8 @@ packages: '@types/react-dom': optional: true - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1194,8 +1364,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1203,8 +1373,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1212,8 +1382,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1221,8 +1391,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-is-hydrated@0.1.0': - resolution: {integrity: sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==} + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1230,8 +1400,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + '@radix-ui/react-use-previous@1.1.2': + resolution: {integrity: sha512-IGBQPtRFdhN6MQ8dbegVmBq1LVZluya3F1jWY+puIcQC3MHctRwTDSBWCkL/3ZcnMJLTMJ++Z+ktmvg0F89iCw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1239,8 +1409,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-previous@1.1.1': - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + '@radix-ui/react-use-rect@1.1.2': + resolution: {integrity: sha512-d8a+bBY/FxikNPlgJJoaBHZX+zKVbWHYJGTLnLvveQgFSTntkGdEKv3JDtHrMS0DNYpllz2nRsTLGLKYttbpmw==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1248,8 +1418,8 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + '@radix-ui/react-use-size@1.1.2': + resolution: {integrity: sha512-giWQp+4mxjBPt4KZ0MmyuykFNWfbDxKt4x+fPkRYmgRFJSbCZFzUglvMb/Kjn38tm10YP4ufiQZDx3zna4LU6w==} peerDependencies: '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc @@ -1257,31 +1427,134 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true + '@radix-ui/rect@1.1.2': + resolution: {integrity: sha512-xnXE7wG13PI+cxieVssYXlQJuYVRhH9NBoxt3KNwzghDIA69GMm7d4wXRouHIYjE+KvS6U/MsMO73NdS2MH9ZA==} - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@resreq/event-hub@1.7.2': + resolution: {integrity: sha512-8zbACYupTvvM+MyPcPvRRbyMgWfVIXDfAZ2M7RIgmE4AscN9nuQrhTnOPgN2ikrx9Op4TLofasQlue9w0sQkmg==} - '@resreq/event-hub@1.6.0': - resolution: {integrity: sha512-SEzPuAc2bQdMV9cKrBIjW3EvntpU8NcBxdgdEo9yLKAeRfvdM4oM/sG8pFPRZOpYwOytaJqWGqwjc+RSRFFTvQ==} + '@rolldown/binding-android-arm64@1.1.5': + resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] - '@rolldown/pluginutils@1.0.0-beta.38': - resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==} + '@rolldown/binding-darwin-arm64@1.1.5': + resolution: {integrity: sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: + '@rolldown/binding-darwin-x64@1.1.5': + resolution: {integrity: sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.1.5': + resolution: {integrity: sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + resolution: {integrity: sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + resolution: {integrity: sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.1.5': + resolution: {integrity: sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + resolution: {integrity: sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + resolution: {integrity: sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.1.5': + resolution: {integrity: sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.1.5': + resolution: {integrity: sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.1.5': + resolution: {integrity: sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.1.5': + resolution: {integrity: sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.1.5': + resolution: {integrity: sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.1.5': + resolution: {integrity: sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/plugin-babel@0.2.3': + resolution: {integrity: sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==} + engines: {node: '>=22.12.0 || ^24.0.0'} + peerDependencies: + '@babel/core': ^7.29.0 || ^8.0.0-rc.1 + '@babel/plugin-transform-runtime': ^7.29.0 || ^8.0.0-rc.1 + '@babel/runtime': ^7.27.0 || ^8.0.0-rc.1 + rolldown: ^1.0.0-rc.5 + vite: ^8.0.0 + peerDependenciesMeta: + '@babel/plugin-transform-runtime': + optional: true + '@babel/runtime': + optional: true + vite: + optional: true + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rollup/pluginutils@5.4.0': + resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: optional: true '@rollup/rollup-android-arm-eabi@4.52.3': @@ -1318,56 +1591,67 @@ packages: resolution: {integrity: sha512-IoerZJ4l1wRMopEHRKOO16e04iXRDyZFZnNZKrWeNquh5d6bucjezgd+OxG03mOMTnS1x7hilzb3uURPkJ0OfA==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.52.3': resolution: {integrity: sha512-ZYdtqgHTDfvrJHSh3W22TvjWxwOgc3ThK/XjgcNGP2DIwFIPeAPNsQxrJO5XqleSlgDux2VAoWQ5iJrtaC1TbA==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.52.3': resolution: {integrity: sha512-NcViG7A0YtuFDA6xWSgmFb6iPFzHlf5vcqb2p0lGEbT+gjrEEz8nC/EeDHvx6mnGXnGCC1SeVV+8u+smj0CeGQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.52.3': resolution: {integrity: sha512-d3pY7LWno6SYNXRm6Ebsq0DJGoiLXTb83AIPCXl9fmtIQs/rXoS8SJxxUNtFbJ5MiOvs+7y34np77+9l4nfFMw==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.52.3': resolution: {integrity: sha512-3y5GA0JkBuirLqmjwAKwB0keDlI6JfGYduMlJD/Rl7fvb4Ni8iKdQs1eiunMZJhwDWdCvrcqXRY++VEBbvk6Eg==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.52.3': resolution: {integrity: sha512-AUUH65a0p3Q0Yfm5oD2KVgzTKgwPyp9DSXc3UA7DtxhEb/WSPfbG4wqXeSN62OG5gSo18em4xv6dbfcUGXcagw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.52.3': resolution: {integrity: sha512-1makPhFFVBqZE+XFg3Dkq+IkQ7JvmUrwwqaYBL2CE+ZpxPaqkGaiWFEWVGyvTwZace6WLJHwjVh/+CXbKDGPmg==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.52.3': resolution: {integrity: sha512-OOFJa28dxfl8kLOPMUOQBCO6z3X2SAfzIE276fwT52uXDWUS178KWq0pL7d6p1kz7pkzA0yQwtqL0dEPoVcRWg==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.52.3': resolution: {integrity: sha512-jMdsML2VI5l+V7cKfZx3ak+SLlJ8fKvLJ0Eoa4b9/vCUrzXKgoKxvHqvJ/mkWhFiyp88nCkM5S2v6nIwRtPcgg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.52.3': resolution: {integrity: sha512-tPgGd6bY2M2LJTA1uGq8fkSPK8ZLYjDjY+ZLK9WHncCnfIz29LIXIqUgzCR0hIefzy6Hpbe8Th5WOSwTM8E7LA==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.52.3': resolution: {integrity: sha512-BCFkJjgk+WFzP+tcSMXq77ymAPIxsX9lFJWs+2JzuZTLtksJ2o5hvgTdIcZ5+oKzUDMwI0PfWzRBYAydAHF2Mw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.52.3': resolution: {integrity: sha512-KTD/EqjZF3yvRaWUJdD1cW+IQBk4fbQaHYJUmP8N4XoKFZilVL8cobFSTDnjTtxWJQ3JYaMgF4nObY/+nYkumA==} @@ -1435,24 +1719,36 @@ packages: peerDependencies: semantic-release: '>=18.0.0' - '@semantic-release/github@11.0.6': - resolution: {integrity: sha512-ctDzdSMrT3H+pwKBPdyCPty6Y47X8dSrjd3aPZ5KKIKKWTwZBE9De8GtsH3TyAlw3Uyo2stegMx6rJMXKpJwJA==} - engines: {node: '>=20.8.1'} + '@semantic-release/github@12.0.9': + resolution: {integrity: sha512-ODIqb0V3QqndipryEEiaBxUQCFjvv7Oese5Dt4omMGa60YRNEW0Sx3K+zri0uac2Y6S9nOlMehciWIzvvRCTGQ==} + engines: {node: ^22.14.0 || >= 24.10.0} peerDependencies: semantic-release: '>=24.1.0' - '@semantic-release/npm@12.0.2': - resolution: {integrity: sha512-+M9/Lb35IgnlUO6OSJ40Ie+hUsZLuph2fqXC/qrKn0fMvUU/jiCjpoL6zEm69vzcmaZJ8yNKtMBEKHWN49WBbQ==} - engines: {node: '>=20.8.1'} + '@semantic-release/npm@13.1.5': + resolution: {integrity: sha512-Hq5UxzoatN3LHiq2rTsWS54nCdqJHlsssGERCo8WlvdfFA9LoN0vO+OuKVSjtNapIc/S8C2LBj206wKLHg62mg==} + engines: {node: ^22.14.0 || >= 24.10.0} peerDependencies: semantic-release: '>=20.1.0' - '@semantic-release/release-notes-generator@14.1.0': - resolution: {integrity: sha512-CcyDRk7xq+ON/20YNR+1I/jP7BYKICr1uKd1HHpROSnnTdGqOTburi4jcRiTYz0cpfhxSloQO3cGhnoot7IEkA==} + '@semantic-release/release-notes-generator@14.1.1': + resolution: {integrity: sha512-Pbd2e2XRMUD0OxehHpgd5/YghsE76cddkRHSoDvKLK+OCy4Ewxn49rWR631MEUU01lgwF/uyVXvbnVuu6+Z6VA==} engines: {node: '>=20.8.1'} peerDependencies: semantic-release: '>=20.1.0' + '@simple-libs/child-process-utils@2.0.0': + resolution: {integrity: sha512-dvNoRKLijXnD0XoJAz94pbNuB5GQgDr55UhpSPhffDkTT0Cmcqh9jSCOtwfT2d4H6MI9E7c4SgtMuJXZ6F3c6A==} + engines: {node: '>=22'} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@simple-libs/stream-utils@2.0.0': + resolution: {integrity: sha512-fCTuZK4QBa+39Oz9l4OGfJfz+GpwCp3AqO7Zch3to99xHPgstVsRFpeQ8LNd2o1Gv8raL2mCFwiaHh7bFSp5DQ==} + engines: {node: '>=22'} + '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} @@ -1464,6 +1760,9 @@ packages: '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} @@ -1535,65 +1834,69 @@ packages: peerDependencies: '@svgr/core': '*' - '@tailwindcss/node@4.1.14': - resolution: {integrity: sha512-hpz+8vFk3Ic2xssIA3e01R6jkmsAhvkQdXlEbRTk6S10xDAtiQiM3FyvZVGsucefq764euO/b8WUW9ysLdThHw==} + '@tailwindcss/node@4.3.3': + resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - '@tailwindcss/oxide-android-arm64@4.1.14': - resolution: {integrity: sha512-a94ifZrGwMvbdeAxWoSuGcIl6/DOP5cdxagid7xJv6bwFp3oebp7y2ImYsnZBMTwjn5Ev5xESvS3FFYUGgPODQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-android-arm64@4.3.3': + resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} + engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.1.14': - resolution: {integrity: sha512-HkFP/CqfSh09xCnrPJA7jud7hij5ahKyWomrC3oiO2U9i0UjP17o9pJbxUN0IJ471GTQQmzwhp0DEcpbp4MZTA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-arm64@4.3.3': + resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} + engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.1.14': - resolution: {integrity: sha512-eVNaWmCgdLf5iv6Qd3s7JI5SEFBFRtfm6W0mphJYXgvnDEAZ5sZzqmI06bK6xo0IErDHdTA5/t7d4eTfWbWOFw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-darwin-x64@4.3.3': + resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} + engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.1.14': - resolution: {integrity: sha512-QWLoRXNikEuqtNb0dhQN6wsSVVjX6dmUFzuuiL09ZeXju25dsei2uIPl71y2Ic6QbNBsB4scwBoFnlBfabHkEw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-freebsd-x64@4.3.3': + resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} + engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': - resolution: {integrity: sha512-VB4gjQni9+F0VCASU+L8zSIyjrLLsy03sjcR3bM0V2g4SNamo0FakZFKyUQ96ZVwGK4CaJsc9zd/obQy74o0Fw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': + resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} + engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': - resolution: {integrity: sha512-qaEy0dIZ6d9vyLnmeg24yzA8XuEAD9WjpM5nIM1sUgQ/Zv7cVkharPDQcmm/t/TvXoKo/0knI3me3AGfdx6w1w==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': + resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.1.14': - resolution: {integrity: sha512-ISZjT44s59O8xKsPEIesiIydMG/sCXoMBCqsphDm/WcbnuWLxxb+GcvSIIA5NjUw6F8Tex7s5/LM2yDy8RqYBQ==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': + resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} + engines: {node: '>= 20'} cpu: [arm64] os: [linux] + libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.1.14': - resolution: {integrity: sha512-02c6JhLPJj10L2caH4U0zF8Hji4dOeahmuMl23stk0MU1wfd1OraE7rOloidSF8W5JTHkFdVo/O7uRUJJnUAJg==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': + resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.1.14': - resolution: {integrity: sha512-TNGeLiN1XS66kQhxHG/7wMeQDOoL0S33x9BgmydbrWAb9Qw0KYdd8o1ifx4HOGDWhVmJ+Ul+JQ7lyknQFilO3Q==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-linux-x64-musl@4.3.3': + resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} + engines: {node: '>= 20'} cpu: [x64] os: [linux] + libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.1.14': - resolution: {integrity: sha512-uZYAsaW/jS/IYkd6EWPJKW/NlPNSkWkBlaeVBi/WsFQNP05/bzkebUL8FH1pdsqx4f2fH/bWFcUABOM9nfiJkQ==} + '@tailwindcss/oxide-wasm32-wasi@4.3.3': + resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1604,58 +1907,49 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': - resolution: {integrity: sha512-Az0RnnkcvRqsuoLH2Z4n3JfAef0wElgzHD5Aky/e+0tBUxUhIeIqFBTMNQvmMRSP15fWwmvjBxZ3Q8RhsDnxAA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': + resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} + engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.1.14': - resolution: {integrity: sha512-ttblVGHgf68kEE4om1n/n44I0yGPkCPbLsqzjvybhpwa6mKKtgFfAzy6btc3HRmuW7nHe0OOrSeNP9sQmmH9XA==} - engines: {node: '>= 10'} + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': + resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} + engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.1.14': - resolution: {integrity: sha512-23yx+VUbBwCg2x5XWdB8+1lkPajzLmALEfMb51zZUBYaYVPDQvBSD/WYDqiVyBIo2BZFa3yw1Rpy3G2Jp+K0dw==} - engines: {node: '>= 10'} + '@tailwindcss/oxide@4.3.3': + resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} + engines: {node: '>= 20'} - '@tailwindcss/postcss@4.1.14': - resolution: {integrity: sha512-BdMjIxy7HUNThK87C7BC8I1rE8BVUsfNQSI5siQ4JK3iIa3w0XyVvVL9SXLWO//CtYTcp1v7zci0fYwJOjB+Zg==} + '@tailwindcss/postcss@4.3.3': + resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - '@tailwindcss/typography@0.5.19': - resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + '@tailwindcss/typography@0.5.20': + resolution: {integrity: sha512-hwbzQuNUfcPvbegQFatVPl/MY/tcM9KLl963hQ5laJKPh81TEZ1+dNG9PirGvcaDBkp+BCshExAyKVPW91dozw==} peerDependencies: - tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + tailwindcss: '>=3.0.0 || >=4.0.0 || insiders' - '@tailwindcss/vite@4.1.14': - resolution: {integrity: sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA==} + '@tailwindcss/vite@4.3.3': + resolution: {integrity: sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==} peerDependencies: - vite: ^5.2.0 || ^6 || ^7 - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.27.0': - resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + vite: ^5.2.0 || ^6 || ^7 || ^8 - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} - '@types/babel__traverse@7.28.0': - resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} - '@types/conventional-commits-parser@5.0.1': - resolution: {integrity: sha512-7uz5EHdzz2TqoMfV7ee61Egf5y6NkcO4FB/1iCCQnbeiI1F3xzv3vK5dBCXUCLQgGYS+mUeigK1iKQzvED+QnQ==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} - '@types/eslint-plugin-tailwindcss@3.17.0': - resolution: {integrity: sha512-ucQGf2YIdTcndYcxRU3UdZgmhUHsOlbIF4BaRtl0op+7k2JmqM2i3aXZ6XIcfZgVq1ZKov7VM5c/BR81ukmkyg==} - - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -1663,6 +1957,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + '@types/filesystem@0.0.36': resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==} @@ -1672,11 +1969,11 @@ packages: '@types/har-format@1.2.16': resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==} - '@types/hast@3.0.4': - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/hast@3.0.5': + resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - '@types/js-cookie@2.2.7': - resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + '@types/js-cookie@3.0.6': + resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -1690,22 +1987,19 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.6.2': - resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/react-dom@19.2.0': - resolution: {integrity: sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: '@types/react': ^19.2.0 - '@types/react@19.2.0': - resolution: {integrity: sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1713,131 +2007,215 @@ packages: '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/webextension-polyfill@0.8.3': - resolution: {integrity: sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg==} + '@types/webextension-polyfill@0.12.5': + resolution: {integrity: sha512-uKSAv6LgcVdINmxXMKBuVIcg/2m5JZugoZO8x20g7j2bXJkPIl/lVGQcDlbV+aXAiTyXT2RA5U5mI4IGCDMQeg==} - '@types/yauzl@2.10.3': - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] - '@typescript-eslint/eslint-plugin@8.45.0': - resolution: {integrity: sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@typescript-eslint/parser': ^8.45.0 - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] - '@typescript-eslint/parser@8.45.0': - resolution: {integrity: sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] - '@typescript-eslint/project-service@8.45.0': - resolution: {integrity: sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] - '@typescript-eslint/scope-manager@8.45.0': - resolution: {integrity: sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] - '@typescript-eslint/tsconfig-utils@8.45.0': - resolution: {integrity: sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] - '@typescript-eslint/type-utils@8.45.0': - resolution: {integrity: sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] - '@typescript-eslint/types@8.45.0': - resolution: {integrity: sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] - '@typescript-eslint/typescript-estree@8.45.0': - resolution: {integrity: sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] - '@typescript-eslint/utils@8.45.0': - resolution: {integrity: sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] - '@typescript-eslint/visitor-keys@8.45.0': - resolution: {integrity: sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@ungap/structured-clone@1.3.3': + resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - '@vitejs/plugin-react@5.0.4': - resolution: {integrity: sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA==} + '@vitejs/plugin-react@6.0.3': + resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/expect@4.1.10': + resolution: {integrity: sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==} + + '@vitest/mocker@4.1.10': + resolution: {integrity: sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.10': + resolution: {integrity: sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==} + + '@vitest/runner@4.1.10': + resolution: {integrity: sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==} + + '@vitest/snapshot@4.1.10': + resolution: {integrity: sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==} + + '@vitest/spy@4.1.10': + resolution: {integrity: sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==} + + '@vitest/utils@4.1.10': + resolution: {integrity: sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==} '@webcomponents/custom-elements@1.6.0': resolution: {integrity: sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww==} - '@webext-core/fake-browser@1.3.2': - resolution: {integrity: sha512-jFyPWWz+VkHAC9DRIiIPOyu6X/KlC8dYqSKweHz6tsDb86QawtVgZSpYcM+GOQBlZc5DHFo92jJ7cIq4uBnU0A==} + '@webext-core/fake-browser@1.5.2': + resolution: {integrity: sha512-nkDQwOJ23X5Q7cEtN6LRuBtVFf1KVOFi5GoQAro0lzqdh59F5E+K350j1isbnqYbzsXRh1NJtboudIcHfZtvOQ==} - '@webext-core/isolated-element@1.1.2': - resolution: {integrity: sha512-CNHYhsIR8TPkPb+4yqTIuzaGnVn/Fshev5fyoPW+/8Cyc93tJbCjP9PC1XSK6fDWu+xASdPHLZaoa2nWAYoxeQ==} + '@webext-core/isolated-element@1.1.5': + resolution: {integrity: sha512-4m6oP8Vzm/68YO1QmkUOZqqUcmyBtA53tji2g00/nYXE3E3IceYgeub7eIqvXDV2Z7xU6cm6qO1IMt4XFVwtvQ==} - '@webext-core/match-patterns@1.0.3': - resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} + '@webext-core/match-patterns@1.1.0': + resolution: {integrity: sha512-vebVVbcOyva4jyvljIzRJwjFi/OKMLr96LIIxiPeXfA38gE4Z3+H6Y9DwRmn7pWErJGNNHU6XOhOb5ZwicGs7Q==} - '@webext-core/messaging@2.3.0': - resolution: {integrity: sha512-gChSVKdRs7JEq5hFH0jVROSvTq+sKq9afXTA/gBswep3RWNLhXyDsXFlvPMkYbmML1XZ8QKKC9ou2MlCKRZwSQ==} - - '@webext-core/proxy-service@1.2.1': - resolution: {integrity: sha512-MTs/7o8dZ8q7ySO0Kkkz9DYKPdTvMZqCYi7VVj1onzIHVy7m9b/OesQ8c3rrPzu8ZaljuZQJKfO3GITI6tSj6A==} - peerDependencies: - '@webext-core/messaging': '>=1.3.1' - webextension-polyfill: '>=0.10.0' + '@wxt-dev/browser@0.1.43': + resolution: {integrity: sha512-RMB0zgfe7uuiTp18s9taLeKXoOCLBV418797dnEggiMWmM1JDJekiLtlvrNc6QeZg+amDBy2/KKYuQB2kh/K9A==} - '@wxt-dev/browser@0.1.4': - resolution: {integrity: sha512-9x03I15i79XU8qYwjv4le0K2HdMl/Yga2wUBSoUbcrCnamv8P3nvuYxREQ9C5QY/qPAfeEVdAtaTrS3KWak71g==} + '@wxt-dev/browser@0.2.2': + resolution: {integrity: sha512-QqLOdEE1UQxieRuMbv9rwczD+xUv45fy+i5gw1eo+/vlPtGX+/rBd6tlIfLFCU3xAN/UTH57bxqOeU2IZg7dEg==} - '@wxt-dev/storage@1.2.0': - resolution: {integrity: sha512-4A44zCpwl5GZdmUdSJvUWJ6ekZZ+Fz5ttYqTGPIRJSsyosKX8X8Yl7D2Loy1ZlqIg6oJHysaiFXALtTE+pFjpw==} + '@wxt-dev/storage@1.2.8': + resolution: {integrity: sha512-GWCFKgF5+d7eslOxUDFC70ypA9njupmJb1nQM8uZoX0J3sWT2BO5xJLzb1sYahWAfID9p2BMtnUBN1lkWxPsbQ==} '@xobotyi/scrollbar-width@1.9.5': resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.5.16: - resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} + adm-zip@0.5.18: + resolution: {integrity: sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==} engines: {node: '>=12.0'} - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} + agent-base@9.0.0: + resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==} + engines: {node: '>= 20'} aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} @@ -1847,20 +2225,20 @@ packages: resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==} engines: {node: '>=18'} - aidly@1.36.0: - resolution: {integrity: sha512-kLcRM0x4lWb9lFgh8L5sehy4SG0cO/0HTVzRlV0kFK2xJKNk/pK/UJFlcAbSr03xIyy1AlO0BwuhLuEJY4n0RA==} + aidly@1.37.0: + resolution: {integrity: sha512-IMbw5e7KKnacRh9PuuHN/65iFibo8NLtqM9NS1QTn7ySqnCVAcc72/Bz0CHnMvwC6FxLtICZWLVIfPddMr9vwA==} - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - ansi-escapes@7.1.1: - resolution: {integrity: sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} ansi-regex@5.0.1: @@ -1893,6 +2271,10 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + argue-cli@3.1.0: + resolution: {integrity: sha512-DhBpBfXL4SS2uC0N922MMajKR3CdrTG0u2or1PNYgXMsrSzViJrbtvT0nCLlLGUI0plam/ZZCs7aAauHtW9thw==} + engines: {node: '>=22'} + argv-formatter@1.0.0: resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} @@ -1919,6 +2301,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} @@ -1933,11 +2319,11 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - atomically@2.0.3: - resolution: {integrity: sha512-kU6FmrwZ3Lx7/7y3hPS5QnbJfaohcIul5fGqf7ok+4KklIEk9tJ0C2IQPdacSbVUWv6zVHXEBWoWd6NrVMT7Cw==} + atomically@2.1.1: + resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.5.4: + resolution: {integrity: sha512-MaU0U/za7N3r6brxD4YB/l4NSrFzLPlANv6wEuQVaIPlD3L4W9rFcQPbL/EilY9BHhHvhfcz3gInDLrEtWT4EA==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -1956,27 +2342,24 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.8.10: - resolution: {integrity: sha512-uLfgBi+7IBNay8ECBO2mVMGZAc1VgZWEChxm4lv+TobGdG82LnXMjuNGo/BSSZZL4UmkWhxEHP2f5ziLNwGWMA==} + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + engines: {node: '>=6.0.0'} hasBin: true before-after-hook@4.0.0: resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} - birecord@0.1.1: - resolution: {integrity: sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw==} - - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - bluebird@3.7.2: resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + boolbase@2.0.0: + resolution: {integrity: sha512-DkVaaQHymRhpYEYo9x1oo7Q7B0Y6KJUsjm3c9eTyFDby4MHLBTwZ6ZDWBel5zrYxj1WsZgC5oLpiz+93MluXeA==} + engines: {node: '>=20.19.0'} bottleneck@2.19.5: resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==} @@ -1985,42 +2368,40 @@ packages: resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} engines: {node: '>=18'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@1.1.16: + resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.7: + resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bufferutil@4.0.9: - resolution: {integrity: sha512-WDtdLmJvAuNNPzByAYpRo2rF1Mmradw6gvWsQKf63476DDXmomT9zUiGypLcG4ibIM67vhAj8jJRdbmEws2Aqw==} + bufferutil@4.1.0: + resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} - c12@3.3.0: - resolution: {integrity: sha512-K9ZkuyeJQeqLEyqldbYLG3wjqwpw4BVaAqvmxq3GYKK0b1A/yYQdIcJxkzAOWcNVWhJpRXAPfZFueekiY/L8Dw==} + c12@3.3.4: + resolution: {integrity: sha512-cM0ApFQSBXuourJejzwv/AuPRvAxordTyParRVcHjjtXirtkzM0uK2L9TTn9s0cXZbG7E55jCivRQzoxYmRAlA==} peerDependencies: - magicast: ^0.3.5 + magicast: '*' peerDependenciesMeta: magicast: optional: true @@ -2029,12 +2410,16 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: @@ -2053,12 +2438,16 @@ packages: resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} engines: {node: '>=16'} - caniuse-lite@1.0.30001746: - resolution: {integrity: sha512-eA7Ys/DGw+pnkWWSE/id29f2IcPHVoE8wxtvE5JdvD2V28VTDPy1yEeo11Guz0sJ4ZeGRcm3uaTcAqK1LXaphA==} + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -2087,25 +2476,21 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - chownr@3.0.0: - resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} - engines: {node: '>=18'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chrome-launcher@1.2.0: resolution: {integrity: sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q==} engines: {node: '>=12.13.0'} hasBin: true - ci-info@4.3.0: - resolution: {integrity: sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} engines: {node: '>=8'} - citty@0.1.6: - resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + citty@0.2.2: + resolution: {integrity: sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==} class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -2122,10 +2507,6 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} @@ -2135,20 +2516,12 @@ packages: engines: {node: '>=8.0.0', npm: '>=5.0.0'} hasBin: true - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-table3@0.6.5: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} - cli-truncate@4.0.0: - resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} - engines: {node: '>=18'} - - cli-truncate@5.1.0: - resolution: {integrity: sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g==} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} engines: {node: '>=20'} cliui@7.0.4: @@ -2158,16 +2531,16 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - cobe@0.6.5: - resolution: {integrity: sha512-MA8bu81EFY6JjQpj+FovEuhyJ25khx2Q7Lh+ot/UkCJe5yKyDgzdc6u2lGZIOmsZTXK6Itg1i4lQZIJZbPWnAg==} + cobe@2.0.1: + resolution: {integrity: sha512-aaa6vcIlaC8C1SF50LDH0Anybo/EAXnrxqe+bwvr4+YUtZydqjeBjTTD7ziCCkbRrRGSns3I3F6cZsf3W+L+ag==} color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -2182,19 +2555,12 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - - comctx@1.4.3: - resolution: {integrity: sha512-Z1fisV3l6kucW+GZDg3AI2+ffWGd+Q/RCBKGbpQ8oe6U3p6wmTM/NciP4RRgLYYDW4aZgZIXRUQz1YnFX1QsjQ==} + comctx@1.7.5: + resolution: {integrity: sha512-0fsxsxr1Hg2T99wOIteUbsJOX6jMmnhAJepcVRqNRMWpcbxRhbm2+0R8qEuQEaE4gWjfdXaKeAGYAn0yeElylQ==} comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@14.0.1: - resolution: {integrity: sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==} - engines: {node: '>=20'} - commander@2.9.0: resolution: {integrity: sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==} engines: {node: '>= 0.6.x'} @@ -2206,9 +2572,6 @@ packages: compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - compare-versions@6.1.1: - resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2219,8 +2582,8 @@ packages: confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - confbox@0.2.2: - resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -2233,20 +2596,24 @@ packages: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} - conventional-changelog-angular@7.0.0: - resolution: {integrity: sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ==} - engines: {node: '>=16'} + content-type@2.0.0: + resolution: {integrity: sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==} + engines: {node: '>=18'} - conventional-changelog-angular@8.0.0: - resolution: {integrity: sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==} + conventional-changelog-angular@8.3.1: + resolution: {integrity: sha512-6gfI3otXK5Ph5DfCOI1dblr+kN3FAm5a97hYoQkqNZxOaYa5WKfXH+AnpsmS+iUH2mgVC2Cg2Qw9m5OKcmNrIg==} engines: {node: '>=18'} - conventional-changelog-conventionalcommits@7.0.2: - resolution: {integrity: sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w==} - engines: {node: '>=16'} + conventional-changelog-angular@9.2.1: + resolution: {integrity: sha512-oWSL6ZhnXbYraOFTK3PgRAQJ8fADDAEv5K6AdeyQPLvjFmhG8+ejL0jZZp/R7vTmGJaBvZEE+sE7dB4bCv7sAw==} + engines: {node: '>=22'} - conventional-changelog-writer@8.2.0: - resolution: {integrity: sha512-Y2aW4596l9AEvFJRwFGJGiQjt2sBYTjPD18DdvxX9Vpz0Z7HQ+g1Z+6iYDAm1vR3QOJrDBkRHixHK/+FhkR6Pw==} + conventional-changelog-conventionalcommits@10.2.1: + resolution: {integrity: sha512-n4Kr1HFMTf3iMbES0TMxKIcYtUUv4rKqyQQp2JwfOEfFCOfGT3Tq4mCyJ8S9/YPyWhydjfKrrvnyl+gCjA+mJQ==} + engines: {node: '>=22'} + + conventional-changelog-writer@8.4.0: + resolution: {integrity: sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==} engines: {node: '>=18'} hasBin: true @@ -2254,14 +2621,14 @@ packages: resolution: {integrity: sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==} engines: {node: '>=18'} - conventional-commits-parser@5.0.0: - resolution: {integrity: sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA==} - engines: {node: '>=16'} + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} hasBin: true - conventional-commits-parser@6.2.0: - resolution: {integrity: sha512-uLnoLeIW4XaoFtH37qEcg/SXMJmKF4vi7V0H2rnPueg+VEtFGA/asSCNTcq4M/GQ6QmlzchAEtOoDTtKqWeHag==} - engines: {node: '>=18'} + conventional-commits-parser@7.1.0: + resolution: {integrity: sha512-DPp6hkUjvwIivxbkrTiLXeRswNv1A/4GFA2X6scXma0AMa9632V3TwxmrlkUIEtUktiM3Ln+RrSH2xlP3/jUTw==} + engines: {node: '>=22'} hasBin: true convert-hrtime@5.0.0: @@ -2271,17 +2638,20 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie-es@1.2.2: - resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie-es@1.2.3: + resolution: {integrity: sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==} copy-to-clipboard@3.3.3: resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + core-js@3.49.0: + resolution: {integrity: sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cosmiconfig-typescript-loader@6.1.0: - resolution: {integrity: sha512-tJ1w35ZRUiM5FeTzT7DtYWAFFv37ZLqSRkGi2oeCK1gPhvaWjkAtfXvLmvE1pRfxxp9aQo6ba/Pvg1dKj05D4g==} + cosmiconfig-typescript-loader@6.3.0: + resolution: {integrity: sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA==} engines: {node: '>=v18'} peerDependencies: '@types/node': '*' @@ -2297,8 +2667,8 @@ packages: typescript: optional: true - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + cosmiconfig@9.0.2: + resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} engines: {node: '>=14'} peerDependencies: typescript: '>=4.9.5' @@ -2329,16 +2699,17 @@ packages: css-in-js-utils@3.1.0: resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} - css-select@5.2.2: - resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + css-select@7.0.0: + resolution: {integrity: sha512-snmjEVXy+1LnwXdxhYvTMj1d9tOh4HxkA1YmoayVBeeyR2C14Pum7fcxJIm4SswYspVy866eYNwlH6xC3/VH5g==} + engines: {node: '>=20.19.0'} css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} + css-what@8.0.0: + resolution: {integrity: sha512-DH0Bqq3DNp5tdOReuNyAA+Ev4Y2GS5FMbZpeTLP6C4CDi0h5nL0BmUPChXw3o/qbHLDWHl49sbNqQVY7bMSDdw==} + engines: {node: '>=20.19.0'} cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} @@ -2348,15 +2719,11 @@ packages: cssom@0.5.0: resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - danmu@0.18.1: - resolution: {integrity: sha512-8x4CyuBILk1uoFil3J40LYmXKLfJpk2OtsxCxCTsYrLsa8CuKdwmSen6iFW1ScZYA5gkdlEOMzN8gOkVILpdsA==} - - dargs@8.1.0: - resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==} - engines: {node: '>=12'} + danmu@0.20.0: + resolution: {integrity: sha512-J58V1tQZwvEOc8/APOPq9r7Z78+SfGIznPp75zgEsYBVQDQddcOyPuPqMgEzT9XmZk66QKR8ezGGAjzn2gr92w==} data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} @@ -2370,8 +2737,8 @@ packages: resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} + date-fns@4.4.0: + resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -2394,8 +2761,8 @@ packages: supports-color: optional: true - decode-named-character-reference@1.2.0: - resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} deep-extend@0.6.0: resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} @@ -2404,17 +2771,14 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} + default-browser-id@5.0.1: + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} engines: {node: '>=18'} - default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} + default-browser@5.5.0: + resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} engines: {node: '>=18'} - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2431,8 +2795,8 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - defu@6.1.4: - resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} @@ -2441,8 +2805,8 @@ packages: destr@2.0.5: resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} - detect-libc@2.1.1: - resolution: {integrity: sha512-ecqj/sy1jcK1uWrwpR67UhYrIFQ+5WlGxth34WquCbamhFA6hkkwiu37o6J5xCHdo1oixJRfVRw+ywV+Hq/0Aw==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} detect-node-es@1.1.0: @@ -2458,19 +2822,32 @@ packages: dom-serializer@2.0.0: resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dom-serializer@3.1.1: + resolution: {integrity: sha512-4MEa38/QexBob6gFNwu+EGdWvhJ1OKuNwdYY3Y3NyeWDQfnGeDYQUDfIRzWu5B5gsv03so2Uxd28YC6zrsx3Lw==} + engines: {node: '>=20.19.0'} + domelementtype@2.3.0: resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + domelementtype@3.0.0: + resolution: {integrity: sha512-umCQid3jKbDmVjx8jGaW7uUykm4DEUeyV21hPxNMo2nV955DhUThwqyOIDtreepP31hl84X7G5U9ZfsWvIB3Pg==} + engines: {node: '>=20.19.0'} + domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.2.7: - resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + domhandler@6.0.1: + resolution: {integrity: sha512-gYzvtM72ZtxQO0T048kd6HWSbbGCNOUwcnfQ01cqIJ4X2IYKFFHZ5mKvrQETcFXxsRObZulDaKmy//R7TPtsBg==} + engines: {node: '>=20.19.0'} domutils@3.2.2: resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + domutils@4.0.2: + resolution: {integrity: sha512-qI4JLRKnSzqFqr7hAlS5xQDusBCjKSEG4t4+7aNrIQMHBcsC2TGEhuyABJdYkgSewL57PNLYEiibY2iPKhKpaA==} + engines: {node: '>=20.19.0'} + dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -2490,8 +2867,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} dunder-proto@1.0.1: @@ -2501,46 +2878,44 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - electron-to-chromium@1.5.228: - resolution: {integrity: sha512-nxkiyuqAn4MJ1QbobwqJILiDtu/jk14hEAWaMiJmNPh1Z+jqoFlBFZjdXwLWGeVSeu9hGLg6+2G9yJaW8rBIFA==} + electron-to-chromium@1.5.392: + resolution: {integrity: sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==} - emoji-regex@10.5.0: - resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - engine.io-client@6.6.3: - resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} + engine.io-client@6.6.6: + resolution: {integrity: sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==} engine.io-parser@5.2.3: resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==} engines: {node: '>=10.0.0'} - enhanced-resolve@5.18.3: - resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + enhanced-resolve@5.24.2: + resolution: {integrity: sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw==} engines: {node: '>=10.13.0'} entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + env-ci@11.2.0: resolution: {integrity: sha512-D5kWfzkmaOQDioPmiviWAVtKmpPT4/iJmMVQxWxMPJTFyTkdc5JQUfc5iXEeWxcOdsYTKSAiA/Age4NUOqKsRA==} engines: {node: ^18.17 || >=20.6.1} @@ -2559,8 +2934,12 @@ packages: error-stack-parser@2.1.4: resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} - es-abstract@1.24.0: - resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -2571,26 +2950,29 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.3.1: + resolution: {integrity: sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} engines: {node: '>= 0.4'} + es-toolkit@1.49.0: + resolution: {integrity: sha512-G5iZ6Pc/FNRY/soKZHC+TxGDD83rHUDXxzaWhGCX44vAv/tMs56WMusnm/KMNK+luUPsgA9U28cGr4RDlSzL2g==} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} engines: {node: '>=18'} hasBin: true @@ -2614,89 +2996,21 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - eslint-config-prettier@10.1.8: - resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==} - hasBin: true - peerDependencies: - eslint: '>=7.0.0' - - eslint-plugin-prettier@5.5.4: - resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - '@types/eslint': '>=8.0.0' - eslint: '>=8.0.0' - eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' - prettier: '>=3.0.0' - peerDependenciesMeta: - '@types/eslint': - optional: true - eslint-config-prettier: - optional: true - - eslint-plugin-react-debug@2.0.5: - resolution: {integrity: sha512-OsA5FiPFOnlsQoFdijV/50bXntGr37rB4uiiADx30mJT0Z5RFLp8O7qAJ5OIwmiJMr8q7DwdzDipCdHxk8s5+g==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-react-dom@2.0.5: - resolution: {integrity: sha512-o0qwLEsM7OW2HFkr69/VSrx9z7cyT7Swhc9dDVpeoMofDkPDVS5Cvb0+0lzf1yWCkmh+VH5W1r0fuMn1kvlzhA==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-react-hooks-extra@2.0.5: - resolution: {integrity: sha512-kRxcEZ2QS5G5YMic1kZZCMIDicmtEH9MxycvsffyUHwTYwi9XflnUtTkihS1TxcdRJbBOdyCB87bX9eejO8hLA==} - engines: {node: '>=20.0.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-react-naming-convention@2.0.5: - resolution: {integrity: sha512-MNz0gJZO7X0PYwrGMqq83pPXRoiq6p4w8eLVonXe8rCyvOaV2lLJdyw1qtDOqQjDr6LHYKpQ9QRu0T1XkELmsA==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-react-web-api@2.0.5: - resolution: {integrity: sha512-fYdPlN4v5x7tx5bR4hugEhvwVUKDAxXQJy+Jl44m9b4XqCm5LSwqwHWZDXkRpUWX7M5zZGc/1hpDVWnfis/lbQ==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-react-x@2.0.5: - resolution: {integrity: sha512-NM6rQ8ia9UuqFA7LqFsIU/4oDBBEUU3JowObNCIKDdrMH4k8+w4gnJG2LfugZOC2PX5GQIsVeF3cBzyMqxb8Dw==} - engines: {node: '>=20.19.0'} - peerDependencies: - eslint: ^9.36.0 - typescript: ^5.9.3 - - eslint-plugin-tailwindcss@3.18.2: - resolution: {integrity: sha512-QbkMLDC/OkkjFQ1iz/5jkMdHfiMu/uwujUHLAJK5iwNHD8RTxVTlsUezE0toTZ6VhybNBsk+gYGPDq2agfeRNA==} - engines: {node: '>=18.12.0'} - peerDependencies: - tailwindcss: ^3.4.0 - - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.36.0: - resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.7.0: + resolution: {integrity: sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -2707,12 +3021,12 @@ packages: esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -2736,8 +3050,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} @@ -2747,34 +3061,27 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.6.0: - resolution: {integrity: sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw==} + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} - exsolve@1.0.7: - resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + exsolve@1.1.0: + resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - - fast-content-type-parse@3.0.0: - resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fake-indexeddb@6.2.5: + resolution: {integrity: sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==} + engines: {node: '>=18'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2788,18 +3095,12 @@ packages: fast-shallow-equal@1.0.0: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-uri@3.1.3: + resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} fastest-stable-stringify@2.0.2: resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} - fastq@1.19.1: - resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} - - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2821,8 +3122,8 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - filesize@11.0.13: - resolution: {integrity: sha512-mYJ/qXKvREuO0uH8LTQJ6v7GsUvVOguqxg2VTwQUkyTPXXRRWPdjuUPVqdBrJQhvci48OHlNGRnux+Slr2Rnvw==} + filesize@11.0.22: + resolution: {integrity: sha512-RlCVs9CY+oSsRnNZn95J9vDXjNjOwddKyTFjOYtA4yxYVIxBnwiVVGJX+TFhsmu3uUf81JDGyijtYL9xgawlTw==} engines: {node: '>= 10.8.0'} fill-range@7.1.1: @@ -2841,10 +3142,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-up@7.0.0: - resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} - engines: {node: '>=18'} - find-versions@6.0.0: resolution: {integrity: sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==} engines: {node: '>=18'} @@ -2858,26 +3155,26 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} + form-data-encoder@4.1.0: + resolution: {integrity: sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==} + engines: {node: '>= 18'} formdata-node@6.0.3: resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} engines: {node: '>= 18'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - framer-motion@12.23.22: - resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==} + framer-motion@12.42.2: + resolution: {integrity: sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2890,11 +3187,8 @@ packages: react-dom: optional: true - from2@2.3.0: - resolution: {integrity: sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==} - - fs-extra@11.3.2: - resolution: {integrity: sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==} + fs-extra@11.3.6: + resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} engines: {node: '>=14.14'} fsevents@2.3.3: @@ -2909,8 +3203,8 @@ packages: resolution: {integrity: sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==} engines: {node: '>=18'} - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -2932,8 +3226,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} engines: {node: '>=18'} get-intrinsic@1.3.0: @@ -2951,18 +3245,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-stream@7.0.1: - resolution: {integrity: sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==} - engines: {node: '>=16'} - get-stream@8.0.1: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} @@ -2975,26 +3261,13 @@ packages: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-value@3.0.1: - resolution: {integrity: sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==} - engines: {node: '>=6.0'} - - giget@2.0.0: - resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} + giget@3.3.0: + resolution: {integrity: sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==} hasBin: true git-log-parser@1.2.1: resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} - git-raw-commits@4.0.0: - resolution: {integrity: sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==} - engines: {node: '>=16'} - hasBin: true - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} @@ -3002,22 +3275,17 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@11.0.3: - resolution: {integrity: sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==} - engines: {node: 20 || >=22} - hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} - - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} - engines: {node: '>=18'} + global-directory@5.0.0: + resolution: {integrity: sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==} + engines: {node: '>=20'} globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} @@ -3036,17 +3304,14 @@ packages: graceful-readlink@1.0.1: resolution: {integrity: sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==} - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - growly@1.3.0: resolution: {integrity: sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==} - h3@1.15.4: - resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + h3@1.15.11: + resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} engines: {node: '>=0.4.7'} hasBin: true @@ -3077,11 +3342,11 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} - hash-it@6.0.0: - resolution: {integrity: sha512-KHzmSFx1KwyMPw0kXeeUD752q/Kfbzhy6dAZrjXV9kAIXGqzGvv8vhkUqj+2MGZldTo0IBpw6v7iWE7uxsvH0w==} + hash-it@7.0.3: + resolution: {integrity: sha512-+TxgI3g3ZhwkEzK918gD3rvr0/89Ai9px7pS5xB7Hnur7gFooPSEuWfgdlUkleXC+C/lizGoBV2bx96S9dulQw==} - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} hast-util-to-jsx-runtime@2.3.6: @@ -3097,8 +3362,8 @@ packages: resolution: {integrity: sha512-IHI4bEVOt3vRUDJ+bFA9VUJlo7SzvFARPNLw75pqSmAOP2HmTWfFJtPvLBrDrlgjEYXY9zs7SFdHPQaJShkSCQ==} engines: {node: '>=20'} - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} hooks-plugin@1.3.4: resolution: {integrity: sha512-2cQFChc7gYjM1A2B7N9fmb3iG+0Ns8Fi9FOEg7P3doH9scDSSJf423V7mxPewc/acshGP1StrQynOgSBM8Pkwg==} @@ -3110,9 +3375,9 @@ packages: resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} engines: {node: ^16.14.0 || >=18.0.0} - hosted-git-info@8.1.0: - resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} - engines: {node: ^18.17.0 || >=20.5.0} + hosted-git-info@9.0.3: + resolution: {integrity: sha512-Hc+ghLoSt6QaYZUv0WBiIvmMDZuZZ7oaDvdH8MbfOO4lOsxdXLEvuC6ePoGs9H1X9oCLyq6+NVN0MKqD+ydxyg==} + engines: {node: ^20.17.0 || >=22.9.0} html-escaper@3.0.3: resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} @@ -3120,16 +3385,16 @@ packages: html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + htmlparser2@10.1.0: + resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} + http-proxy-agent@9.1.0: + resolution: {integrity: sha512-2NxoveTT58mjYT4n3RPTEfCZGLMbidoO8XEieXfpSYxu+PQJ1qpx4ypwH6N+uF9twBPIvRRgvkvW5HUTYWENig==} + engines: {node: '>= 20'} - https-proxy-agent@7.0.6: - resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} - engines: {node: '>= 14'} + https-proxy-agent@9.1.0: + resolution: {integrity: sha512-ag87y7cJJ9/3+GxFr8Oy4O5faDsGRGnBGsJj/YjOSsSx/5eadKLYTMPlzuR6obgoCDDm0abAAZitXXQkMOPSpA==} + engines: {node: '>= 20'} human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} @@ -3151,22 +3416,18 @@ packages: hyphenate-style-name@1.1.0: resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} - idb-keyval@6.2.2: - resolution: {integrity: sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==} + idb-keyval@6.3.0: + resolution: {integrity: sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==} - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + idb@8.0.3: + resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} - engines: {node: '>= 4'} - - imgcap@1.0.2: - resolution: {integrity: sha512-KY04sGVRupPrRBIruSBDnBBEe/5iwyivnukzmhOBclIkdyMJcbfrlqZrSZjQi1254NJXxOtpB3ddmHUjdKNydg==} + imgcap@1.1.0: + resolution: {integrity: sha512-eS7G2zQ8Kki7JwVPLa40JZGQl8OHuMH0x4iVpItAXwUN4kOkY8EzlceZdtXsm2n6OtrnojCmJrIqDfhWPREqJQ==} immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -3212,8 +3473,12 @@ packages: resolution: {integrity: sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - inline-style-parser@0.2.4: - resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + ini@6.0.0: + resolution: {integrity: sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==} + engines: {node: ^20.17.0 || >=22.9.0} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} inline-style-prefixer@7.0.1: resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} @@ -3222,10 +3487,6 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - into-stream@7.0.0: - resolution: {integrity: sha512-2dYz766i9HprMBasCMvHMuazJ7u4WzhJwo5kb3iPSiW/iRYV6uPari3zHoqZlnuaR7V1bEiNMxikhp37rdBXbw==} - engines: {node: '>=12'} - iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -3262,8 +3523,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -3287,6 +3548,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3299,10 +3564,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} @@ -3318,17 +3579,15 @@ packages: is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - is-immutable-type@5.0.1: - resolution: {integrity: sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg==} - peerDependencies: - eslint: '*' - typescript: '>=4.7.4' - is-in-ci@1.0.0: resolution: {integrity: sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==} engines: {node: '>=18'} hasBin: true + 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'} @@ -3338,10 +3597,6 @@ packages: resolution: {integrity: sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==} engines: {node: '>=18'} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -3425,18 +3680,10 @@ packages: resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} - is-text-path@2.0.0: - resolution: {integrity: sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw==} - engines: {node: '>=8'} - is-typed-array@1.1.15: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -3457,8 +3704,8 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + is-wsl@3.1.1: + resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} isarray@1.0.0: @@ -3477,14 +3724,10 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - issue-parser@7.0.1: - resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} + issue-parser@7.0.2: + resolution: {integrity: sha512-7atWPjhGEIX3JEtMrOYd8TKzboYlq+5sNbdl9POiLYOI14G5HZiQbZP0Xj5EZdrufQVXfJlpTV0hys0CuxwxZw==} engines: {node: ^18.17 || >=20.6.1} - jackspeak@4.1.1: - resolution: {integrity: sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==} - engines: {node: 20 || >=22} - java-properties@1.0.2: resolution: {integrity: sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==} engines: {node: '>= 0.6.0'} @@ -3493,8 +3736,12 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true - js-cookie@2.2.1: - resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + jiti@2.7.0: + resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} + hasBin: true + + js-cookie@3.0.8: + resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -3502,8 +3749,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.3.0: + resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true jsesc@3.1.0: @@ -3533,21 +3780,30 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-with-bigint@3.5.10: + resolution: {integrity: sha512-Vcx+JVNEBts/xfcoCS69sKrOhOk/3TVlvlT+XzUOefVKnnrbYSCKpDCm10pohsJFtsJVYnwa/cXRZ4eElzaM6w==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} jszip@3.10.1: resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -3555,8 +3811,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - ky@1.11.0: - resolution: {integrity: sha512-NEyo0ICpS0cqSuyoJFMCnHOZJILqXsKhIZlHJGDYaH8OB5IFrGzuBpEwyoMZG6gUKMPrazH30Ax5XKaujvD8ag==} + ky@1.14.3: + resolution: {integrity: sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==} engines: {node: '>=18'} latest-version@9.0.0: @@ -3573,68 +3829,78 @@ packages: lighthouse-logger@2.0.2: resolution: {integrity: sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg==} - lightningcss-darwin-arm64@1.30.1: - resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.30.1: - resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.30.1: - resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.30.1: - resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.30.1: - resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] - lightningcss-linux-arm64-musl@1.30.1: - resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] - lightningcss-linux-x64-gnu@1.30.1: - resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] - lightningcss-linux-x64-musl@1.30.1: - resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] - lightningcss-win32-arm64-msvc@1.30.1: - resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.30.1: - resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.30.1: - resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} lines-and-columns@1.2.4: @@ -3644,8 +3910,8 @@ packages: resolution: {integrity: sha512-wM1+Z03eypVAVUCE7QdSqpVIvelbOakn1M0bPDoA4SGWPx3sNDVUiMo3L6To6WWGClB7VyXnhQ4Sn7gxiJbE6A==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - linkedom@0.18.12: - resolution: {integrity: sha512-jalJsOwIKuQJSeTvsgzPe9iJzyfVaEJiEXl+25EkKevsULHvMJzpNqwvj1jOESWdmgKDiXObyjOYwlUqG7wo1Q==} + linkedom@0.18.13: + resolution: {integrity: sha512-ES/o9qotMpzpN2MHs+Iq/JcVoOj8Fa5wiQYrTdFpvAnwXL0g66XHHUc9WUMk6nAlBtGsFQ24ne+SYnvnaQ2FSw==} engines: {node: '>=16'} peerDependencies: canvas: '>= 2' @@ -3653,25 +3919,21 @@ packages: canvas: optional: true - lint-staged@16.2.3: - resolution: {integrity: sha512-1OnJEESB9zZqsp61XHH2fvpS1es3hRCxMplF/AJUDa8Ho8VrscYDIuxGrj3m8KPXbcWZ8fT9XTMUhEQmOVKpKw==} - engines: {node: '>=20.17'} + lint-staged@17.0.8: + resolution: {integrity: sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==} + engines: {node: '>=22.22.1'} hasBin: true - listr2@8.3.3: - resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} - engines: {node: '>=18.0.0'} - - listr2@9.0.4: - resolution: {integrity: sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ==} - engines: {node: '>=20.0.0'} + listr2@10.2.2: + resolution: {integrity: sha512-JtNtbZj8q5BnDMR7trpwvwk3RIrANtIVzEUm8w7amp6xelLgyuq+4WZoTH913XaQAoH/cNdYhaNzBPA2U3xbDw==} + engines: {node: '>=22.13.0'} load-json-file@4.0.0: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} engines: {node: '>=14'} locate-path@2.0.0: @@ -3682,15 +3944,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} lodash.capitalize@4.2.1: resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} @@ -3698,46 +3953,35 @@ packages: lodash.escaperegexp@4.1.2: resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} lodash.isstring@4.0.1: resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - lodash.kebabcase@4.1.1: - resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - lodash.mergewith@4.6.2: - resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - - lodash.snakecase@4.1.1: - resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} - - lodash.startcase@4.4.0: - resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} lodash.uniqby@4.7.0: resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} - lodash.upperfirst@4.3.1: - resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} - - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - log-symbols@5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} - engines: {node: '>=12'} - - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} @@ -3752,29 +3996,34 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.2.2: - resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.544.0: - resolution: {integrity: sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw==} + lucide-react@1.25.0: + resolution: {integrity: sha512-/mdJTRbiwcLOQ1NZZK1amZF9rIZyvO18D6r9TngE6TG1NmqHgFuT4eE7Xrkm9UsXMbBJD1NlfwHVltCDWHrOTw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - magic-string@0.30.19: - resolution: {integrity: sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==} + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.3: + resolution: {integrity: sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==} + + make-asynchronous@1.1.0: + resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} + engines: {node: '>=18'} make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - many-keys-map@2.0.1: - resolution: {integrity: sha512-DHnZAD4phTbZ+qnJdjoNEVU1NecYoSdbOOoVmTDH46AuxDkEVh3MxTVpXq10GtcTC6mndN9dkv1rNfpjRcLnOw==} + many-keys-map@3.0.3: + resolution: {integrity: sha512-1DiZmDHPXMBgMRjeUtHy1q1VYmeJscHxhIAexX9z/zjRMP80+0ETuPfssi8z+kMY4DwUgsKuHqpjxgmeA9gBNA==} + engines: {node: '>=18'} markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} @@ -3800,8 +4049,8 @@ packages: mdast-util-find-and-replace@3.0.2: resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + mdast-util-from-markdown@2.0.3: + resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} mdast-util-gfm-autolink-literal@2.0.1: resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} @@ -3836,8 +4085,8 @@ packages: mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - mdast-util-to-hast@13.2.0: - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -3852,10 +4101,6 @@ packages: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} - meow@12.1.1: - resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==} - engines: {node: '>=16.10'} - meow@13.2.0: resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} engines: {node: '>=18'} @@ -3863,10 +4108,6 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -3972,39 +4213,31 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@10.0.3: - resolution: {integrity: sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==} - engines: {node: 20 || >=22} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + 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'} - - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} - motion-dom@12.23.21: - resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==} + motion-dom@12.42.2: + resolution: {integrity: sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==} - motion-utils@12.23.6: - resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + motion-utils@12.39.0: + resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} - motion@12.23.22: - resolution: {integrity: sha512-iSq6X9vLHbeYwmHvhK//+U74ROaPnZmBuy60XZzqNl0QtZkWfoZyMDHYnpKuWFv0sNMqHgED8aCXk94LCoQPGg==} + motion@12.42.2: + resolution: {integrity: sha512-Atvv11yUKIid41cVrRBDVX5m8tF8kNpExRSlbpt6APClhDjtwQssgFHhQzejxw7/7YYbjHSPKBVbHo05BuJT5Q==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -4033,24 +4266,28 @@ packages: react: '*' react-dom: '*' - nano-spawn@1.0.3: - resolution: {integrity: sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA==} + nano-spawn@2.1.0: + resolution: {integrity: sha512-yTW+2okrElHiH4fsiz/+/zc0EDo9BDDoC3iKk8dpv1GeRc9nUWzUZHx6TofMWErchhUQR8hY9/Eu1Uja9x1nqA==} engines: {node: '>=20.17'} - nanoevents@6.0.2: - resolution: {integrity: sha512-FRS2otuFcPPYDPYViNWQ42+1iZqbXydinkRHTHFxrF4a1CpBfmydR9zkI44WSXAXCyPrkcGtPk5CnpW6Y3lFKQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.16: + resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.1.6: - resolution: {integrity: sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==} + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} engines: {node: ^18 || >=20} hasBin: true + nanoid@6.0.0: + resolution: {integrity: sha512-mkUH+rPkwU2qPadJ0oJZOjeZ5Mxn8Q1UhevwkTRWNuUZzyia3h4rhzK39hxaHTk0o2OxB8W2SQ6A8k23ZDi1pQ==} + engines: {node: ^22 || ^24 || >=26} + hasBin: true + + nanospinner@1.2.2: + resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -4073,22 +4310,23 @@ packages: node-fetch-native@1.6.7: resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-mock-http@1.0.3: - resolution: {integrity: sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==} + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} node-notifier@10.0.1: resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -4097,17 +4335,17 @@ packages: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} + normalize-package-data@8.0.0: + resolution: {integrity: sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==} + engines: {node: ^20.17.0 || >=22.9.0} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - normalize-url@8.1.0: - resolution: {integrity: sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==} - engines: {node: '>=14.16'} + normalize-url@9.0.1: + resolution: {integrity: sha512-ARftfC5HdUNu9jJeL8pHj8debUIHA2b91FizCoMzY4lG6dDX13jdvTK0TBe24IBDRf2HvJSzzwEPvmbkQWHRSg==} + engines: {node: '>=20'} npm-run-all@4.1.5: resolution: {integrity: sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==} @@ -4126,9 +4364,9 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} - npm@10.9.4: - resolution: {integrity: sha512-OnUG836FwboQIbqtefDNlyR0gTHzIfwRfE3DuiNewBvnMnWEpB0VEXwBlFVgqpNzIgYo/MHh3d2Hel/pszapAA==} - engines: {node: ^18.17.0 || >=20.5.0} + npm@11.18.0: + resolution: {integrity: sha512-T67M4L5wNm0cZ7EBLErcEkY1SmzEW/WJ+SADBzsFUY1UdAPfFHXFQtZ6SEXiK0+vzXysCvAsepbMaBTwnrAD+w==} + engines: {node: ^20.17.0 || >=22.9.0} hasBin: true bundledDependencies: - '@isaacs/string-locale-compare' @@ -4136,6 +4374,7 @@ packages: - '@npmcli/config' - '@npmcli/fs' - '@npmcli/map-workspaces' + - '@npmcli/metavuln-calculator' - '@npmcli/package-json' - '@npmcli/promise-spawn' - '@npmcli/redact' @@ -4146,7 +4385,6 @@ packages: - cacache - chalk - ci-info - - cli-columns - fastest-levenshtein - fs-minipass - glob @@ -4160,7 +4398,6 @@ packages: - libnpmdiff - libnpmexec - libnpmfund - - libnpmhook - libnpmorg - libnpmpack - libnpmpublish @@ -4174,7 +4411,6 @@ packages: - ms - node-gyp - nopt - - normalize-package-data - npm-audit-report - npm-install-checks - npm-package-arg @@ -4198,17 +4434,17 @@ packages: - treeverse - validate-npm-package-name - which - - write-file-atomic - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + nth-check@3.0.1: + resolution: {integrity: sha512-GX0gsdbGVCgnRgbeGaubfjpBXyYRWOOCVeYh08bSQvDZqxz5ndXs1OTfAt/h36G1xvI94YIspsI0sVFqAV9+RQ==} + engines: {node: '>=20.19.0'} - number-flow@0.5.8: - resolution: {integrity: sha512-FPr1DumWyGi5Nucoug14bC6xEz70A1TnhgSHhKyfqjgji2SOTz+iLJxKtv37N5JyJbteGYCm6NQ9p1O4KZ7iiA==} + number-flow@0.6.1: + resolution: {integrity: sha512-5NUCwZA6J/kZeEzqApOyq7KOf1Wu/oxjpp/95HB0NBNBFlpK/Pp7i4jH8h0LIXyRDKSTewDAUaYHL7IBrk19Ew==} - nypm@0.6.2: - resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} - engines: {node: ^14.16.0 || >=16.10.0} + nypm@0.6.8: + resolution: {integrity: sha512-Q9K4Diu6l5u6xJQogeFSs/zKtyMSgFKFtRQV+tHP4kL7KPm2grpBU0dFIwFaXwNxN0MtfKWc43VpCugAa+LPsw==} + engines: {node: '>=18'} hasBin: true object-assign@4.1.1: @@ -4227,8 +4463,12 @@ packages: resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} - ofetch@1.4.1: - resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==} + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} @@ -4237,9 +4477,6 @@ packages: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -4252,9 +4489,9 @@ packages: 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.0: + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} + engines: {node: '>=20'} open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} @@ -4264,14 +4501,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@6.3.1: - resolution: {integrity: sha512-ERAyNnZOfqM+Ao3RAvIXkYh5joP220yf59gVe2X/cI6SiCxIdi4c9HZKZD8R6q/RDXEje1THBju6iExiSsgJaQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} - os-shim@0.1.3: resolution: {integrity: sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==} engines: {node: '>= 0.4.0'} @@ -4280,18 +4509,44 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + oxfmt@0.57.0: + resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + svelte: ^5.0.0 + vite-plus: '*' + peerDependenciesMeta: + svelte: + optional: true + vite-plus: + optional: true + + oxlint@1.74.0: + resolution: {integrity: sha512-odGl2s2x5IOJoj3A0v1k0PGBXVFBZeZ2+AK/+K2MJur7Ghi3bkyX5NuLUWHKqa4js1wjep3hJeuTQJOlr+4+dA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + oxlint-tsgolint: '>=0.24.0' + vite-plus: '*' + peerDependenciesMeta: + oxlint-tsgolint: + optional: true + vite-plus: + optional: true + p-each-series@3.0.0: resolution: {integrity: sha512-lastgtAdoH9YaLyDa5i5z64q+kzOcQHsQ5SsZJD3q0VEyI8mq872S3geuNbRUQLVAE9siMfgKrpj7MloKFHruw==} engines: {node: '>=12'} + p-event@6.0.1: + resolution: {integrity: sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==} + engines: {node: '>=16.17'} + p-filter@4.1.0: resolution: {integrity: sha512-37/tPdZ3oJwHaS3gNJdenCDB3Tz26i9sjhnguBtvN0vYlRIiDNnvTWkuh+0hETV9rLPdJ3rlL3yVOYPIAnM8rw==} engines: {node: '>=18'} - p-is-promise@3.0.0: - resolution: {integrity: sha512-Wo8VsW4IRQSKVXsJCn7TomUaVtyfjVDn3nUP7kE967BQk0CwFpdbZs0X0uk5sW9mkBa9eNM7hCMaG93WUAwxYQ==} - engines: {node: '>=8'} - p-limit@1.3.0: resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} engines: {node: '>=4'} @@ -4300,10 +4555,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-locate@2.0.0: resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} engines: {node: '>=4'} @@ -4312,12 +4563,8 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-map@7.0.3: - resolution: {integrity: sha512-VkndIv2fIB99swvQoA65bm+fsmt6UNdGeIB0oxBs+WhAhdh08QA04JXpI7rbB9r08/nkbysKoya9rtDERYOYMA==} + p-map@7.0.5: + resolution: {integrity: sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA==} engines: {node: '>=18'} p-reduce@2.1.0: @@ -4328,6 +4575,10 @@ packages: resolution: {integrity: sha512-xsrIUgI0Kn6iyDYm9StOpOeK29XM1aboGji26+QEortiFST1hGZaUQOLhtEbqHErPpGW/aSz6allwK2qcptp0Q==} engines: {node: '>=12'} + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + p-try@1.0.0: resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} engines: {node: '>=4'} @@ -4386,10 +4637,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-key@2.0.1: resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} engines: {node: '>=4'} @@ -4405,9 +4652,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@2.0.0: - resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} - 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-type@3.0.0: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} @@ -4420,24 +4667,18 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - - perfect-debounce@2.0.0: - resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} - - phenomenon@1.6.0: - resolution: {integrity: sha512-7h9/fjPD3qNlgggzm88cY58l9sudZ6Ey+UmZsizfhtawO6E3srZQXywaNm2lBwT72TbpHYRPy7ytIHeBUD/G0A==} + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pidtree@0.3.1: @@ -4445,11 +4686,6 @@ packages: engines: {node: '>=0.10'} hasBin: true - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - pify@3.0.0: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} @@ -4457,8 +4693,8 @@ packages: pino-abstract-transport@2.0.0: resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} + pino-std-serializers@7.1.0: + resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} pino@9.7.0: resolution: {integrity: sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg==} @@ -4471,16 +4707,23 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-rem-to-responsive-pixel@6.1.0: - resolution: {integrity: sha512-rttz/ThJWKpmhS134NcQCZQBE4CUJIAJAMx9DB0YudIeTH4fNp/hhhjxo14RnGF/OGdBpX9PQw1bnPZ56H0E1Q==} + postcss-plugin-shared@1.1.5: + resolution: {integrity: sha512-lYn9dyw3+BDpK39DI9HdMdDfdPnPKnIMA4OJnkodWnZ0OOZrDCUafpQaIUcBLXlInLBGYFo3YhB/BwyuwUY1Kw==} + peerDependencies: + postcss: ^8 + + postcss-rem-to-responsive-pixel@7.0.4: + resolution: {integrity: sha512-KLWSuwZotO0RBRboavb1Ui0W1EjempzgnvvNJqQ2BOduTBIiJJhrizZpglVBkcwF3EXqhRv/kER0zHi+Wxr5Mg==} engines: {node: '>=16.6.0'} + peerDependencies: + postcss: ^8 postcss-selector-parser@6.0.10: resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} @@ -4489,23 +4732,18 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.19: + resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} engines: {node: ^10 || ^12 || >=14} + powershell-utils@0.1.0: + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} + engines: {node: '>=20'} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier-linter-helpers@1.0.0: - resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} - engines: {node: '>=6.0.0'} - - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} - engines: {node: '>=14'} - hasBin: true - pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -4524,19 +4762,25 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - property-information@7.1.0: - resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + property-information@7.2.0: + resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - publish-browser-extension@3.0.2: - resolution: {integrity: sha512-yZLPF/WyyaKYUHmurDcSMYpgZLqpUkx/4482bLpelHyRlyghjo3951pJXw/KunMnO6pdwWEZGr0AJnvlls2H8g==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true + proxy-agent-negotiate@1.1.0: + resolution: {integrity: sha512-N8IBcM3UgCVzz2L2Lqv8DVntDnnC8/hiV4nEDUPkqq72TPUgYWjQc+bdZlBPZK9LzPAvOY//gAt0S0DApoOXWQ==} + engines: {node: '>= 20'} + peerDependencies: + kerberos: ^2.0.0 + peerDependenciesMeta: + kerberos: + optional: true - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + publish-browser-extension@4.0.5: + resolution: {integrity: sha512-EePAn3VIHJS/jqCuvs1NgPgoecCT8+RsES76hbgYe2Ze1dyvB0tX60C1PCrV8Z8fv56mW3E59s9Gd/GwWiw7dw==} + engines: {node: '>=18.0.0'} + hasBin: true punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} @@ -4549,29 +4793,26 @@ packages: quansync@0.2.11: resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - quick-format-unescaped@4.0.4: resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} radix3@1.1.2: resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} - rc9@2.1.2: - resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc9@3.0.1: + resolution: {integrity: sha512-gMDyleLWVE+i6Sgtc0QbbY6pEKqYs97NGi6isHQPqYlLemPoO8dxQ3uGi0f4NiP98c+jMW6cG1Kx9dDwfvqARQ==} rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} peerDependencies: - react: ^19.2.0 + react: ^19.2.7 - react-hook-form@7.63.0: - resolution: {integrity: sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==} + react-hook-form@7.81.0: + resolution: {integrity: sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17 || ^18 || ^19 @@ -4582,10 +4823,6 @@ packages: '@types/react': '>=18' react: '>=18' - react-refresh@0.17.0: - resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} - engines: {node: '>=0.10.0'} - react-remove-scroll-bar@2.3.8: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} @@ -4596,8 +4833,8 @@ packages: '@types/react': optional: true - react-remove-scroll@2.7.1: - resolution: {integrity: sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==} + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: '@types/react': '*' @@ -4622,26 +4859,34 @@ packages: react: '*' tslib: '*' - react-use@17.6.0: - resolution: {integrity: sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==} + react-use@17.6.1: + resolution: {integrity: sha512-uibb3pgzV4LFsYPHyXYGu7dD2+pyk/ZJlPH+AizBR3zolqPWyCleKcWWbUQaKSKfydwgnQ3ymGm1Ab3/saGHCA==} peerDependencies: react: '*' react-dom: '*' - react-virtuoso@4.14.1: - resolution: {integrity: sha512-NRUF1ak8lY+Tvc6WN9cce59gU+lilzVtOozP+pm9J7iHshLGGjsiAB4rB2qlBPHjFbcXOQpT+7womNHGDUql8w==} + react-virtuoso@4.18.11: + resolution: {integrity: sha512-Qeeq9vqa5seCxACvzbQTUeq3s2/Bu+VlwOMF0jfy3E/ZKHLiXWwJpXBhv2rckqYkvm1XRVFLCBMCmqCU8JNEJg==} peerDependencies: react: '>=16 || >=17 || >= 18 || >= 19' react-dom: '>=16 || >=17 || >= 18 || >=19' - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} engines: {node: '>=0.10.0'} read-package-up@11.0.0: resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==} engines: {node: '>=18'} + read-package-up@12.0.0: + resolution: {integrity: sha512-Q5hMVBYur/eQNWDdbF4/Wqqr9Bjvtrw2kjGxxBbKLbx8bVCL8gcArjTy8zDUuLGQicftpMuU0riQNcAsbtOVsw==} + engines: {node: '>=20'} + + read-pkg@10.1.0: + resolution: {integrity: sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==} + engines: {node: '>=20'} + read-pkg@3.0.0: resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} engines: {node: '>=4'} @@ -4653,13 +4898,9 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} @@ -4673,8 +4914,8 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - registry-auth-token@5.1.0: - resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + registry-auth-token@5.1.1: + resolution: {integrity: sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==} engines: {node: '>=14'} registry-url@6.0.1: @@ -4739,31 +4980,28 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} engines: {node: '>= 0.4'} hasBin: true - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - restore-cursor@5.1.0: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} - rimraf@6.0.1: - resolution: {integrity: sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A==} + rimraf@6.1.3: + resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} hasBin: true + rolldown@1.1.5: + resolution: {integrity: sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rollup@4.52.3: resolution: {integrity: sha512-RIDh866U8agLgiIcdpB+COKnlCreHJLfIhWC3LVflku5YHfpnsIKigRZeFfMfCc4dVcqNVfQQ5gO/afOck064A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -4776,14 +5014,11 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -4804,8 +5039,9 @@ packages: resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} engines: {node: '>=10'} - sax@1.4.1: - resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -4817,16 +5053,11 @@ packages: scule@1.3.0: resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} - semantic-release@24.2.9: - resolution: {integrity: sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==} - engines: {node: '>=20.8.1'} + semantic-release@25.0.7: + resolution: {integrity: sha512-fFiUD7LNFI0TOGkc49WDHUX4GYKrOMDKct5ReSB1EJCZiPsPdbIPIJGys1xb2ILpK1v9JMsRkjJQgTRpbr7DgQ==} + engines: {node: ^22.14.0 || >= 24.10.0} hasBin: true - semver-diff@5.0.0: - resolution: {integrity: sha512-0HbGtOm+S7T6NGQ/pxJSJipJvc4DK3FcRVMRkhsIwJDJ4Jcz5DQC1cPPzB5GhzyHjwttW878HaWQq46CkL3cqg==} - engines: {node: '>=12'} - deprecated: Deprecated as the semver package now supports this built-in. - semver-regex@4.0.5: resolution: {integrity: sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==} engines: {node: '>=12'} @@ -4839,19 +5070,11 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} engines: {node: '>=10'} hasBin: true - serialize-error@11.0.3: - resolution: {integrity: sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==} - engines: {node: '>=14.16'} - - serialize-error@9.1.1: - resolution: {integrity: sha512-6uZQLGyUkNA4N+Zii9fYukmNu9PEA1F5rqcwXzN/3LtBjwl2dFBbVZ1Zyn08/CGkB4H440PIemdOQBt1Wvjbrg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4894,18 +5117,18 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + shell-quote@1.7.3: resolution: {integrity: sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==} - shell-quote@1.8.3: - resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} - engines: {node: '>= 0.4'} - shellwords@0.1.1: resolution: {integrity: sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -4916,10 +5139,13 @@ packages: resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} engines: {node: '>= 0.4'} - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} engines: {node: '>= 0.4'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -4938,30 +5164,30 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + small-queue@1.1.2: resolution: {integrity: sha512-K6zQPh7cRjkzv+upcwrOTbR4kU3neY3KZR2F1nc8Fuez8uJyh4BguOUs4KGKHcuzU1LJmK8yfCkMUZbconLe8Q==} snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - socket.io-client@4.8.1: - resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==} + socket.io-client@4.8.3: + resolution: {integrity: sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==} engines: {node: '>=10.0.0'} - socket.io-parser@4.2.4: - resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==} + socket.io-parser@4.2.7: + resolution: {integrity: sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==} engines: {node: '>=10.0.0'} - sonic-boom@4.2.0: - resolution: {integrity: sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==} + sonic-boom@4.2.1: + resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} @@ -5006,8 +5232,8 @@ packages: spdx-expression-parse@3.0.1: resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} split2@1.0.0: resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} @@ -5022,6 +5248,9 @@ packages: stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -5031,13 +5260,8 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - stdin-discarder@0.1.0: - resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} @@ -5050,35 +5274,28 @@ packages: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} - string-ts@2.2.1: - resolution: {integrity: sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw==} - string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - string-width@7.2.0: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} - string-width@8.1.0: - resolution: {integrity: sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==} + string-width@8.2.2: + resolution: {integrity: sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==} engines: {node: '>=20'} string.prototype.padend@3.1.6: resolution: {integrity: sha512-XZpspuSB7vJWhvJc9DLSlrXl1mcA2BdoY5jjnS135ydXqLoqhs96JjDtCkjJEQHvfqZIp9hBuBMgI589peyx9Q==} engines: {node: '>= 0.4'} - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: @@ -5088,9 +5305,6 @@ packages: string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} @@ -5098,8 +5312,8 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@3.0.0: @@ -5126,10 +5340,6 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - strip-json-comments@5.0.2: resolution: {integrity: sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==} engines: {node: '>=14.16'} @@ -5137,20 +5347,23 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} - stubborn-fs@1.2.5: - resolution: {integrity: sha512-H2N9c26eXjzL/S/K+i/RHHcFanE74dptvvjM8iwzwbVcWY/zjBbgRqF3K0DY4+OD+uTTASTBvDoxPDaPN02D7g==} + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} - style-to-js@1.1.17: - resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - style-to-object@1.0.9: - resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - stylis@4.3.6: - resolution: {integrity: sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} - super-regex@1.0.0: - resolution: {integrity: sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==} + super-regex@1.1.0: + resolution: {integrity: sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==} engines: {node: '>=18'} supports-color@5.5.0: @@ -5172,40 +5385,28 @@ packages: svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} - engines: {node: ^14.18.0 || >=16.0.0} - tagged-tag@1.0.0: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} - tailwind-merge@3.3.1: - resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - tailwindcss@4.1.14: - resolution: {integrity: sha512-b7pCxjGO98LnxVkKjaZSDeNuljC4ueKUddjENJOADtubtdo8llTaJy7HwBMeLNSSo2N5QIAgklslK1+Ir8r6CA==} + tailwindcss@4.3.3: + resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.3: + resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} engines: {node: '>=6'} - tar@7.5.1: - resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} - engines: {node: '>=18'} - temp-dir@3.0.0: resolution: {integrity: sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==} engines: {node: '>=14.16'} - tempy@3.1.0: - resolution: {integrity: sha512-7jDLIdD2Zp0bDe5r3D2qtkd1QOCacylBuL7oa4udvN6v2pqr4+LcCr67C8DR1zkpaZ8XosF5m1yQSabKAW6f2g==} + tempy@3.2.0: + resolution: {integrity: sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==} engines: {node: '>=14.16'} - text-extensions@2.4.0: - resolution: {integrity: sha512-te/NtwBwfiNRLf9Ijqx3T0nlqZiQ2XrrtBvu+cLL8ZRrGkO0NHTug8MYFKyoSrv/sHTaSKfilUkizV6XhxMJ3g==} - engines: {node: '>=8'} - thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -5213,8 +5414,8 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + thread-stream@3.2.0: + resolution: {integrity: sha512-zLBvqpwr4Esa0kRjcrzGU6zL25lePWaCLMx0RQFrmteozIfeNdaMLpG5U7PeHzvlFkAWaRKA9/KVW4F60iB+qw==} throttle-debounce@3.0.1: resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} @@ -5230,16 +5431,25 @@ packages: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} - tiny-uid@1.1.2: - resolution: {integrity: sha512-0beRFXR+fv4C40ND2PqgNjq6iyB1dKXciKJjslLw0kPYCcR82aNd2b+Tt2yy06LimIlvtoehgvrm/fUZCutSfg==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@1.0.1: - resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} + tinyexec@1.2.4: + resolution: {integrity: sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==} + engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} engines: {node: '>=12.0.0'} + tinypool@2.1.0: + resolution: {integrity: sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==} + engines: {node: ^20.0.0 || >=22.0.0} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + tmp@0.2.5: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} @@ -5261,26 +5471,16 @@ packages: trough@2.2.0: resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - ts-api-utils@2.1.0: - resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} - engines: {node: '>=18.12'} - peerDependencies: - typescript: '>=4.8.4' - - ts-declaration-location@1.0.7: - resolution: {integrity: sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA==} - peerDependencies: - typescript: '>=4.0.0' - ts-easing@0.2.0: resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} - ts-pattern@5.8.0: - resolution: {integrity: sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tunnel@0.0.6: + resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==} + engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'} + tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} @@ -5304,8 +5504,8 @@ packages: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} - type-fest@5.0.1: - resolution: {integrity: sha512-9MpwAI52m8H6ssA542UxSLnSiSD2dsC3/L85g6hVubLSXd82wdI80eZwTWhdOfN67NlA+D+oipAs1MlcTcu3KA==} + type-fest@5.8.0: + resolution: {integrity: sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==} engines: {node: '>=20'} typed-array-buffer@1.0.3: @@ -5320,27 +5520,20 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - typescript-eslint@8.45.0: - resolution: {integrity: sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} uglify-js@3.19.3: resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} @@ -5350,10 +5543,6 @@ packages: uhyphen@0.2.0: resolution: {integrity: sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA==} - uid@2.0.2: - resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} - engines: {node: '>=8'} - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -5361,8 +5550,16 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - undici-types@7.13.0: - resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==} + undici-types@8.3.0: + resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} + + undici@6.27.0: + resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} + engines: {node: '>=18.17'} + + undici@7.28.0: + resolution: {integrity: sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==} + engines: {node: '>=20.18.1'} unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} @@ -5376,19 +5573,31 @@ packages: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} + unicorn-magic@0.4.0: + resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + engines: {node: '>=20'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unimport@5.4.1: - resolution: {integrity: sha512-wMZ2JKUCleCK2zfRHeWcbrUHKXOC3SVBYkyn/wTGzh0THX6sT4hSjuKXxKANN4/WMbT6ZPM4JzcDcnhD2x9Bpg==} + unimport@6.3.0: + resolution: {integrity: sha512-M+Dxk5W9WRd+8j56W9tp8lGW/dmMc7g5zj7BWQnEjKQhryBstqsi1V0izb0zHwSkEN8cSYV7K75/bykairV2tA==} engines: {node: '>=18.12.0'} + peerDependencies: + oxc-parser: '*' + rolldown: ^1.0.0 + peerDependenciesMeta: + oxc-parser: + optional: true + rolldown: + optional: true unique-string@3.0.0: resolution: {integrity: sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==} engines: {node: '>=12'} - unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -5396,11 +5605,11 @@ packages: unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} universal-user-agent@7.0.3: resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} @@ -5409,16 +5618,45 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} - unplugin-utils@0.3.0: - resolution: {integrity: sha512-JLoggz+PvLVMJo+jZt97hdIIIZ2yTzGgft9e9q8iMrC4ewufl62ekeW7mixBghonn2gVb/ICjyvlmOCUBnJLQg==} + unplugin-utils@0.3.2: + resolution: {integrity: sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==} engines: {node: '>=20.19.0'} - unplugin@2.3.10: - resolution: {integrity: sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==} - engines: {node: '>=18.12.0'} + unplugin@3.3.0: + resolution: {integrity: sha512-qa66K+crbfyE6JK10GjvbJeRrOsuC/JpbnHctfyp/i4oBTxWOzJfRZyDiOk1PtErMFRu8JhsU/wPvOdBNWe5Rg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@farmfe/core': '*' + '@rspack/core': '*' + bun-types-no-globals: '*' + esbuild: '*' + rolldown: '*' + rollup: '*' + unloader: '*' + vite: '*' + webpack: '*' + peerDependenciesMeta: + '@farmfe/core': + optional: true + '@rspack/core': + optional: true + bun-types-no-globals: + optional: true + esbuild: + optional: true + rolldown: + optional: true + rollup: + optional: true + unloader: + optional: true + vite: + optional: true + webpack: + optional: true - unstorage@1.17.1: - resolution: {integrity: sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==} + unstorage@1.17.5: + resolution: {integrity: sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==} peerDependencies: '@azure/app-configuration': ^1.8.0 '@azure/cosmos': ^4.2.0 @@ -5426,14 +5664,14 @@ packages: '@azure/identity': ^4.6.0 '@azure/keyvault-secrets': ^4.9.0 '@azure/storage-blob': ^12.26.0 - '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@capacitor/preferences': ^6 || ^7 || ^8 '@deno/kv': '>=0.9.0' '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 '@planetscale/database': ^1.19.0 '@upstash/redis': ^1.34.3 '@vercel/blob': '>=0.27.1' '@vercel/functions': ^2.2.12 || ^3.0.0 - '@vercel/kv': ^1.0.1 + '@vercel/kv': ^1 || ^2 || ^3 aws4fetch: ^1.0.20 db0: '>=0.2.1' idb-keyval: ^6.2.1 @@ -5479,8 +5717,8 @@ packages: uploadthing: optional: true - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -5521,8 +5759,8 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - utf-8-validate@6.0.5: - resolution: {integrity: sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==} + utf-8-validate@6.0.6: + resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} engines: {node: '>=6.14.2'} util-deprecate@1.0.2: @@ -5530,10 +5768,11 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true - valibot@1.1.0: - resolution: {integrity: sha512-Nk8lX30Qhu+9txPYTwM0cFlWLdPFsFr6LblzqIySfbZph9+BFsAHsNvHOymEviUepeIW6KFHzpX8TKhbptBXXw==} + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -5549,25 +5788,26 @@ packages: vfile@6.0.3: resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vite-node@6.0.0: + resolution: {integrity: sha512-oj4PVrT+pDh6GYf5wfUXkcZyekYS8kKPfLPXVl8qe324Ec6l4K2DUKNadRbZ3LQl0qGcDz+PyOo7ZAh00Y+JjQ==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - vite-plugin-svgr@4.5.0: - resolution: {integrity: sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==} + vite-plugin-svgr@5.2.0: + resolution: {integrity: sha512-qj2eAKF8C6PZWemVTvQA0xgQIcP1hHU6Buh7fl6BhvayWwnuxE+z417miKxeDvRWbDrupQ1oK99hfxElopJ3sQ==} peerDependencies: - vite: '>=2.6.0' + vite: '>=3.0.0' - vite@7.1.8: - resolution: {integrity: sha512-oBXvfSHEOL8jF+R9Am7h59Up07kVVGH1NrFGFoEG6bPDZP3tGpQhvkBpy5x7U6+E6wZCu9OihsWgJqDbQIm8LQ==} + vite@8.1.5: + resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.3.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -5578,12 +5818,14 @@ packages: peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -5599,34 +5841,63 @@ packages: yaml: optional: true - watchpack@2.4.4: - resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} - engines: {node: '>=10.13.0'} - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - web-ext-run@0.2.4: - resolution: {integrity: sha512-rQicL7OwuqWdQWI33JkSXKcp7cuv1mJG8u3jRQwx/8aDsmhbTHs9ZRmNYOL+LX0wX8edIEQX8jj4bB60GoXtKA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - - webext-bridge@6.0.1: - resolution: {integrity: sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==} - - webextension-polyfill@0.10.0: - resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==} - - webextension-polyfill@0.12.0: - resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==} - - webextension-polyfill@0.9.0: - resolution: {integrity: sha512-LTtHb0yR49xa9irkstDxba4GATDAcDw3ncnFH9RImoFwDlW47U95ME5sn5IiQX2ghfaECaf6xyXM8yvClIBkkw==} - - webpack-virtual-modules@0.6.2: + vitest@4.1.10: + resolution: {integrity: sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.10 + '@vitest/browser-preview': 4.1.10 + '@vitest/browser-webdriverio': 4.1.10 + '@vitest/coverage-istanbul': 4.1.10 + '@vitest/coverage-v8': 4.1.10 + '@vitest/ui': 4.1.10 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + watchpack@2.4.4: + resolution: {integrity: sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==} + engines: {node: '>=10.13.0'} + + web-ext-run@0.2.4: + resolution: {integrity: sha512-rQicL7OwuqWdQWI33JkSXKcp7cuv1mJG8u3jRQwx/8aDsmhbTHs9ZRmNYOL+LX0wX8edIEQX8jj4bB60GoXtKA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + + web-worker@1.5.0: + resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} + + webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - when-exit@2.1.4: - resolution: {integrity: sha512-4rnvd3A1t16PWzrBUcSDZqcAmsUIy4minDXT/CZ8F2mVDgd65i4Aalimgz1aQkRGU0iH5eT5+6Rx2TK8o443Pg==} + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} when@3.7.7: resolution: {integrity: sha512-9lFZp/KHoqH6bPKjbWqa+3Dg/K/r2v0X/3/G2x4DBGchVS2QX2VXL3cZV994WQVnTM1/PD71Az25nAzryEUugw==} @@ -5643,8 +5914,8 @@ packages: resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} engines: {node: '>= 0.4'} - which-typed-array@1.1.19: - resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} engines: {node: '>= 0.4'} which@1.2.4: @@ -5660,6 +5931,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + widest-line@5.0.0: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} @@ -5674,23 +5950,20 @@ packages: wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - wrap-ansi@9.0.2: resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} engines: {node: '>=18'} - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} + ws@8.21.1: + resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -5701,13 +5974,19 @@ packages: utf-8-validate: optional: true - wsl-utils@0.1.0: - resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} - engines: {node: '>=18'} + wsl-utils@0.3.1: + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} + engines: {node: '>=20'} - wxt@0.20.11: - resolution: {integrity: sha512-DqqHc/5COs8GR21ii99bANXf/mu6zuDpiXFV1YKNsqO5/PvkrCx5arY0aVPL5IATsuacAnNzdj4eMc1qbzS53Q==} + wxt@0.20.27: + resolution: {integrity: sha512-dm6yixz2awM4YMqpTJnsCa8aOPiTrjiIjbWslgR5hCjgwhuft+hLlla339Dt7gIKwQloee4oOruoK++vaX0APA==} + engines: {bun: '>=1.2.0', node: '>=20.12.0'} hasBin: true + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true xdg-basedir@5.1.0: resolution: {integrity: sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==} @@ -5736,12 +6015,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@5.0.0: - resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} - engines: {node: '>=18'} - - yaml@2.8.1: - resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} engines: {node: '>= 14.6'} hasBin: true @@ -5753,25 +6028,26 @@ packages: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} engines: {node: '>=10'} - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + yargs@17.7.3: + resolution: {integrity: sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==} engines: {node: '>=12'} - yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.2.1: - resolution: {integrity: sha512-AyeEbWOu/TAXdxlV9wmGcR0+yh2j3vYPGOECcIj2S7MkrLyC7ne+oye2BKTItt0ii2PHk4cDy+95+LshzbXnGg==} - engines: {node: '>=12.20'} - yoctocolors@2.1.2: resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} engines: {node: '>=18'} @@ -5779,52 +6055,65 @@ packages: zip-dir@2.0.0: resolution: {integrity: sha512-uhlsJZWz26FLYXOD6WVuq+fIcZ3aBPGo/cFdiLlv3KNwpa52IF3ISV8fLhQLiqVu5No3VhlqlgthN6gehil1Dg==} - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zod@4.1.11: - resolution: {integrity: sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: - '@1natsu/wait-element@4.1.2': + '@1natsu/wait-element@4.2.0': + dependencies: + defu: 6.1.7 + many-keys-map: 3.0.3 + + '@actions/core@3.0.1': + dependencies: + '@actions/exec': 3.0.0 + '@actions/http-client': 4.0.1 + + '@actions/exec@3.0.0': dependencies: - defu: 6.1.4 - many-keys-map: 2.0.1 + '@actions/io': 3.0.2 + + '@actions/http-client@4.0.1': + dependencies: + tunnel: 0.0.6 + undici: 6.27.0 + + '@actions/io@3.0.2': {} '@aklinker1/rollup-plugin-visualizer@5.12.0(rollup@4.52.3)': dependencies: open: 8.4.2 - picomatch: 2.3.1 + picomatch: 2.3.2 source-map: 0.7.6 - yargs: 17.7.2 + yargs: 17.7.3 optionalDependencies: rollup: 4.52.3 '@alloc/quick-lru@5.2.0': {} - '@babel/code-frame@7.27.1': + '@babel/code-frame@7.29.7': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.29.7': {} - '@babel/core@7.28.4': + '@babel/core@7.29.7': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -5834,206 +6123,203 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.29.7': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.29.7': dependencies: - '@babel/compat-data': 7.28.4 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.6 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-globals@7.28.0': {} + '@babel/helper-globals@7.29.7': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-option@7.27.1': {} + '@babel/helper-validator-identifier@7.29.7': {} - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 - - '@babel/parser@7.28.4': - dependencies: - '@babel/types': 7.28.4 + '@babel/helper-validator-option@7.29.7': {} - '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.4)': + '@babel/helpers@7.29.7': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 - '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.4)': + '@babel/parser@7.29.7': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/types': 7.29.7 '@babel/runtime@7.28.2': {} - '@babel/runtime@7.28.4': {} + '@babel/runtime@7.29.7': {} - '@babel/template@7.27.2': + '@babel/template@7.29.7': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 - '@babel/traverse@7.28.4': + '@babel/traverse@7.29.7': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.29.7': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 '@colors/colors@1.5.0': optional: true - '@commitlint/cli@20.1.0(@types/node@24.6.2)(typescript@5.9.3)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': dependencies: - '@commitlint/format': 20.0.0 - '@commitlint/lint': 20.0.0 - '@commitlint/load': 20.1.0(@types/node@24.6.2)(typescript@5.9.3) - '@commitlint/read': 20.0.0 - '@commitlint/types': 20.0.0 - tinyexec: 1.0.1 - yargs: 17.7.2 + '@commitlint/config-conventional': 21.2.0 + '@commitlint/format': 21.2.0 + '@commitlint/lint': 21.2.0 + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) + '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) + '@commitlint/types': 21.2.0 + tinyexec: 1.2.4 + yargs: 18.0.0 transitivePeerDependencies: - '@types/node' + - conventional-commits-filter + - conventional-commits-parser - typescript - '@commitlint/config-conventional@20.0.0': + '@commitlint/config-conventional@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - conventional-changelog-conventionalcommits: 7.0.2 + '@commitlint/types': 21.2.0 + conventional-changelog-conventionalcommits: 10.2.1 - '@commitlint/config-validator@20.0.0': + '@commitlint/config-validator@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - ajv: 8.17.1 + '@commitlint/types': 21.2.0 + ajv: 8.20.0 - '@commitlint/ensure@20.0.0': + '@commitlint/ensure@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - lodash.startcase: 4.4.0 - lodash.upperfirst: 4.3.1 + '@commitlint/types': 21.2.0 + es-toolkit: 1.49.0 - '@commitlint/execute-rule@20.0.0': {} + '@commitlint/execute-rule@21.0.1': {} - '@commitlint/format@20.0.0': + '@commitlint/format@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - chalk: 5.6.2 + '@commitlint/types': 21.2.0 + picocolors: 1.1.1 - '@commitlint/is-ignored@20.0.0': + '@commitlint/is-ignored@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - semver: 7.7.2 + '@commitlint/types': 21.2.0 + semver: 7.8.5 - '@commitlint/lint@20.0.0': + '@commitlint/lint@21.2.0': dependencies: - '@commitlint/is-ignored': 20.0.0 - '@commitlint/parse': 20.0.0 - '@commitlint/rules': 20.0.0 - '@commitlint/types': 20.0.0 + '@commitlint/is-ignored': 21.2.0 + '@commitlint/parse': 21.2.0 + '@commitlint/rules': 21.2.0 + '@commitlint/types': 21.2.0 - '@commitlint/load@20.1.0(@types/node@24.6.2)(typescript@5.9.3)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': dependencies: - '@commitlint/config-validator': 20.0.0 - '@commitlint/execute-rule': 20.0.0 - '@commitlint/resolve-extends': 20.1.0 - '@commitlint/types': 20.0.0 - chalk: 5.6.2 - cosmiconfig: 9.0.0(typescript@5.9.3) - cosmiconfig-typescript-loader: 6.1.0(@types/node@24.6.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3) - lodash.isplainobject: 4.0.6 - lodash.merge: 4.6.2 - lodash.uniq: 4.5.0 + '@commitlint/config-validator': 21.2.0 + '@commitlint/execute-rule': 21.0.1 + '@commitlint/resolve-extends': 21.2.0 + '@commitlint/types': 21.2.0 + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + es-toolkit: 1.49.0 + is-plain-obj: 4.1.0 + picocolors: 1.1.1 transitivePeerDependencies: - '@types/node' - typescript - '@commitlint/message@20.0.0': {} + '@commitlint/message@21.2.0': {} - '@commitlint/parse@20.0.0': + '@commitlint/parse@21.2.0': dependencies: - '@commitlint/types': 20.0.0 - conventional-changelog-angular: 7.0.0 - conventional-commits-parser: 5.0.0 + '@commitlint/types': 21.2.0 + conventional-changelog-angular: 9.2.1 + conventional-commits-parser: 7.1.0 - '@commitlint/read@20.0.0': + '@commitlint/read@21.2.1(conventional-commits-parser@7.1.0)': dependencies: - '@commitlint/top-level': 20.0.0 - '@commitlint/types': 20.0.0 - git-raw-commits: 4.0.0 - minimist: 1.2.8 - tinyexec: 1.0.1 + '@commitlint/top-level': 21.2.0 + '@commitlint/types': 21.2.0 + '@conventional-changelog/git-client': 3.1.0(conventional-commits-parser@7.1.0) + tinyexec: 1.2.4 + transitivePeerDependencies: + - conventional-commits-filter + - conventional-commits-parser - '@commitlint/resolve-extends@20.1.0': + '@commitlint/resolve-extends@21.2.0': dependencies: - '@commitlint/config-validator': 20.0.0 - '@commitlint/types': 20.0.0 - global-directory: 4.0.1 - import-meta-resolve: 4.2.0 - lodash.mergewith: 4.6.2 + '@commitlint/config-validator': 21.2.0 + '@commitlint/types': 21.2.0 + es-toolkit: 1.49.0 + global-directory: 5.0.0 resolve-from: 5.0.0 - '@commitlint/rules@20.0.0': + '@commitlint/rules@21.2.0': dependencies: - '@commitlint/ensure': 20.0.0 - '@commitlint/message': 20.0.0 - '@commitlint/to-lines': 20.0.0 - '@commitlint/types': 20.0.0 + '@commitlint/ensure': 21.2.0 + '@commitlint/message': 21.2.0 + '@commitlint/to-lines': 21.0.1 + '@commitlint/types': 21.2.0 - '@commitlint/to-lines@20.0.0': {} + '@commitlint/to-lines@21.0.1': {} - '@commitlint/top-level@20.0.0': + '@commitlint/top-level@21.2.0': dependencies: - find-up: 7.0.0 + escalade: 3.2.0 - '@commitlint/types@20.0.0': + '@commitlint/types@21.2.0': dependencies: - '@types/conventional-commits-parser': 5.0.1 - chalk: 5.6.2 + conventional-commits-parser: 7.1.0 + picocolors: 1.1.1 + + '@conventional-changelog/git-client@3.1.0(conventional-commits-parser@7.1.0)': + dependencies: + '@simple-libs/child-process-utils': 2.0.0 + '@simple-libs/stream-utils': 2.0.0 + semver: 7.8.5 + optionalDependencies: + conventional-commits-parser: 7.1.0 + + '@conventional-changelog/template@1.2.1': {} '@devicefarmer/adbkit-logcat@2.1.3': {} @@ -6046,11 +6332,27 @@ snapshots: bluebird: 3.7.2 commander: 9.5.0 debug: 4.3.7 - node-forge: 1.3.1 + node-forge: 1.4.0 split: 1.0.1 transitivePeerDependencies: - supports-color + '@emnapi/core@1.11.1': + dependencies: + '@emnapi/wasi-threads': 1.2.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.11.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.2': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/is-prop-valid@1.3.1': dependencies: '@emotion/memoize': 0.9.0 @@ -6061,265 +6363,163 @@ snapshots: '@epic-web/invariant@1.0.0': {} - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.27.7': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/android-arm64@0.27.7': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/android-arm@0.27.7': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/android-x64@0.27.7': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/darwin-arm64@0.27.7': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/darwin-x64@0.27.7': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.27.7': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/freebsd-x64@0.27.7': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-arm64@0.27.7': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-arm@0.27.7': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-ia32@0.27.7': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.27.7': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.27.7': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-ppc64@0.27.7': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-riscv64@0.27.7': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-s390x@0.27.7': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-x64@0.27.7': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.27.7': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/netbsd-x64@0.27.7': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/openbsd-arm64@0.27.7': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/openbsd-x64@0.27.7': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/openharmony-arm64@0.27.7': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/sunos-x64@0.27.7': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/win32-arm64@0.27.7': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/win32-ia32@0.27.7': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/win32-x64@0.27.7': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0(jiti@2.6.1))': + '@eslint-community/eslint-utils@4.9.1(eslint@10.7.0(jiti@2.7.0))': dependencies: - eslint: 9.36.0(jiti@2.6.1) + eslint: 10.7.0(jiti@2.7.0) eslint-visitor-keys: 3.4.3 + optional: true - '@eslint-community/regexpp@4.12.1': {} - - '@eslint-react/ast@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 2.0.5 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - string-ts: 2.2.1 - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/core@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - birecord: 0.1.1 - ts-pattern: 5.8.0 - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/eff@2.0.5': {} - - '@eslint-react/eslint-plugin@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - eslint-plugin-react-debug: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-react-dom: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-react-hooks-extra: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-react-naming-convention: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-react-web-api: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint-plugin-react-x: 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@eslint-react/kit@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 2.0.5 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/shared@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - ts-pattern: 5.8.0 - zod: 4.1.11 - transitivePeerDependencies: - - eslint - - supports-color - - typescript - - '@eslint-react/var@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - ts-pattern: 5.8.0 - transitivePeerDependencies: - - eslint - - supports-color - - typescript + '@eslint-community/regexpp@4.12.2': + optional: true - '@eslint/config-array@0.21.0': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 2.1.6 + '@eslint/object-schema': 3.0.5 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color + optional: true - '@eslint/config-helpers@0.3.1': {} - - '@eslint/core@0.15.2': + '@eslint/config-helpers@0.6.0': dependencies: - '@types/json-schema': 7.0.15 + '@eslint/core': 1.2.1 + optional: true - '@eslint/eslintrc@3.3.1': + '@eslint/core@1.2.1': dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.36.0': {} + '@types/json-schema': 7.0.15 + optional: true - '@eslint/object-schema@2.1.6': {} + '@eslint/object-schema@3.0.5': + optional: true - '@eslint/plugin-kit@0.3.5': + '@eslint/plugin-kit@0.7.2': dependencies: - '@eslint/core': 0.15.2 + '@eslint/core': 1.2.1 levn: 0.4.1 + optional: true - '@floating-ui/core@1.7.3': + '@floating-ui/core@1.8.0': dependencies: - '@floating-ui/utils': 0.2.10 + '@floating-ui/utils': 0.2.12 - '@floating-ui/dom@1.7.4': + '@floating-ui/dom@1.8.0': dependencies: - '@floating-ui/core': 1.7.3 - '@floating-ui/utils': 0.2.10 + '@floating-ui/core': 1.8.0 + '@floating-ui/utils': 0.2.12 - '@floating-ui/react-dom@2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@floating-ui/react-dom@2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@floating-ui/dom': 1.7.4 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@floating-ui/dom': 1.8.0 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - '@floating-ui/utils@0.2.10': {} + '@floating-ui/utils@0.2.12': {} - '@hookform/resolvers@5.2.2(react-hook-form@7.63.0(react@19.2.0))': + '@hookform/resolvers@5.4.0(react-hook-form@7.81.0(react@19.2.7))': dependencies: '@standard-schema/utils': 0.3.0 - react-hook-form: 7.63.0(react@19.2.0) + react-hook-form: 7.81.0(react@19.2.7) - '@humanfs/core@0.19.1': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + optional: true - '@humanfs/node@0.16.7': + '@humanfs/node@0.16.8': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + optional: true - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/retry@0.4.3': {} - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 + '@humanfs/types@0.15.0': + optional: true - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 + '@humanwhocodes/module-importer@1.0.1': + optional: true - '@isaacs/fs-minipass@4.0.1': - dependencies: - minipass: 7.1.2 + '@humanwhocodes/retry@0.4.3': + optional: true '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -6340,91 +6540,195 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@lukeed/csprng@1.1.0': {} - - '@nodelib/fs.scandir@2.1.5': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.19.1 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 + optional: true - '@number-flow/react@0.5.10(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@number-flow/react@0.6.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: esm-env: 1.2.2 - number-flow: 0.5.8 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + number-flow: 0.6.1 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) '@octokit/auth-token@6.0.0': {} - '@octokit/core@7.0.5': + '@octokit/core@7.0.6': dependencies: '@octokit/auth-token': 6.0.0 - '@octokit/graphql': 9.0.2 - '@octokit/request': 10.0.5 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.11 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 before-after-hook: 4.0.0 universal-user-agent: 7.0.3 - '@octokit/endpoint@11.0.1': + '@octokit/endpoint@11.0.3': dependencies: - '@octokit/types': 15.0.0 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 - '@octokit/graphql@9.0.2': + '@octokit/graphql@9.0.3': dependencies: - '@octokit/request': 10.0.5 - '@octokit/types': 15.0.0 + '@octokit/request': 10.0.11 + '@octokit/types': 16.0.0 universal-user-agent: 7.0.3 - '@octokit/openapi-types@26.0.0': {} + '@octokit/openapi-types@27.0.0': {} - '@octokit/plugin-paginate-rest@13.2.0(@octokit/core@7.0.5)': + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 - '@octokit/plugin-retry@8.0.2(@octokit/core@7.0.5)': + '@octokit/plugin-retry@8.1.0(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 bottleneck: 2.19.5 - '@octokit/plugin-throttling@11.0.2(@octokit/core@7.0.5)': + '@octokit/plugin-throttling@11.0.3(@octokit/core@7.0.6)': dependencies: - '@octokit/core': 7.0.5 - '@octokit/types': 15.0.0 + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 bottleneck: 2.19.5 - '@octokit/request-error@7.0.1': + '@octokit/request-error@7.1.0': dependencies: - '@octokit/types': 15.0.0 + '@octokit/types': 16.0.0 - '@octokit/request@10.0.5': + '@octokit/request@10.0.11': dependencies: - '@octokit/endpoint': 11.0.1 - '@octokit/request-error': 7.0.1 - '@octokit/types': 15.0.0 - fast-content-type-parse: 3.0.0 + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + content-type: 2.0.0 + json-with-bigint: 3.5.10 universal-user-agent: 7.0.3 - '@octokit/types@15.0.0': + '@octokit/types@16.0.0': dependencies: - '@octokit/openapi-types': 26.0.0 + '@octokit/openapi-types': 27.0.0 - '@perfsee/jsonr@1.14.2': - dependencies: - tslib: 2.8.1 + '@oxc-project/types@0.139.0': {} - '@pkgr/core@0.2.9': {} + '@oxfmt/binding-android-arm-eabi@0.57.0': + optional: true + + '@oxfmt/binding-android-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-arm64@0.57.0': + optional: true + + '@oxfmt/binding-darwin-x64@0.57.0': + optional: true + + '@oxfmt/binding-freebsd-x64@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-arm64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-riscv64-musl@0.57.0': + optional: true + + '@oxfmt/binding-linux-s390x-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-gnu@0.57.0': + optional: true + + '@oxfmt/binding-linux-x64-musl@0.57.0': + optional: true + + '@oxfmt/binding-openharmony-arm64@0.57.0': + optional: true + + '@oxfmt/binding-win32-arm64-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-ia32-msvc@0.57.0': + optional: true + + '@oxfmt/binding-win32-x64-msvc@0.57.0': + optional: true + + '@oxlint/binding-android-arm-eabi@1.74.0': + optional: true + + '@oxlint/binding-android-arm64@1.74.0': + optional: true + + '@oxlint/binding-darwin-arm64@1.74.0': + optional: true + + '@oxlint/binding-darwin-x64@1.74.0': + optional: true + + '@oxlint/binding-freebsd-x64@1.74.0': + optional: true + + '@oxlint/binding-linux-arm-gnueabihf@1.74.0': + optional: true + + '@oxlint/binding-linux-arm-musleabihf@1.74.0': + optional: true + + '@oxlint/binding-linux-arm64-gnu@1.74.0': + optional: true + + '@oxlint/binding-linux-arm64-musl@1.74.0': + optional: true + + '@oxlint/binding-linux-ppc64-gnu@1.74.0': + optional: true + + '@oxlint/binding-linux-riscv64-gnu@1.74.0': + optional: true + + '@oxlint/binding-linux-riscv64-musl@1.74.0': + optional: true + + '@oxlint/binding-linux-s390x-gnu@1.74.0': + optional: true + + '@oxlint/binding-linux-x64-gnu@1.74.0': + optional: true + + '@oxlint/binding-linux-x64-musl@1.74.0': + optional: true + + '@oxlint/binding-openharmony-arm64@1.74.0': + optional: true + + '@oxlint/binding-win32-arm64-msvc@1.74.0': + optional: true + + '@oxlint/binding-win32-ia32-msvc@1.74.0': + optional: true + + '@oxlint/binding-win32-x64-msvc@1.74.0': + optional: true '@pnpm/config.env-replace@1.1.0': {} @@ -6432,408 +6736,459 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.3.1': + '@pnpm/npm-conf@3.0.3': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@radix-ui/number@1.1.1': {} + '@radix-ui/number@1.1.2': {} - '@radix-ui/primitive@1.1.3': {} + '@radix-ui/primitive@1.1.5': {} - '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-arrow@1.1.11(@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)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-primitive': 2.1.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) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-avatar@1.1.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-is-hydrated': 0.1.0(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-avatar@1.2.2(@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)': + dependencies: + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-checkbox@1.3.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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-collection@1.1.12(@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)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-context@1.1.2(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-direction@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-dismissable-layer@1.1.15(@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)': dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-dropdown-menu@2.1.16(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-menu': 2.1.16(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-dropdown-menu@2.1.20(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-menu': 2.1.20(@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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-focus-scope@1.1.12(@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)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-hover-card@1.1.15(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-hover-card@1.1.19(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@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) + '@radix-ui/react-popper': 1.3.3(@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) + '@radix-ui/react-portal': 1.1.13(@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) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-icons@1.3.2(react@19.2.0)': + '@radix-ui/react-icons@1.3.2(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 - '@radix-ui/react-id@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-label@2.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-label@2.1.11(@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)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-primitive': 2.1.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) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-menu@2.1.16(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-menu@2.1.20(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@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) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@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) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@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) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@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) + '@radix-ui/react-portal': 1.1.13(@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) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-roving-focus': 1.1.15(@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) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-remove-scroll: 2.7.1(@types/react@19.2.0)(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-popover@1.1.19(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@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) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-focus-scope': 1.1.12(@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) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-popper': 1.3.3(@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) + '@radix-ui/react-portal': 1.1.13(@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) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) aria-hidden: 1.2.6 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-remove-scroll: 2.7.1(@types/react@19.2.0)(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/rect': 1.1.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-popper@1.3.3(@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)': + dependencies: + '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-arrow': 1.1.11(@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) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-rect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/rect': 1.1.2 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-portal@1.1.13(@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)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-presence@1.1.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)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-primitive@2.1.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)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-radio-group@1.4.3(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-roving-focus': 1.1.15(@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) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-roving-focus@1.1.15(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@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) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) - - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-scroll-area@1.2.14(@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)': + dependencies: + '@radix-ui/number': 1.1.2 + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-presence': 1.1.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) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-slot@1.2.3(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 - - '@radix-ui/react-switch@1.2.6(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.0(@types/react@19.2.0))(@types/react@19.2.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + '@types/react': 19.2.17 + + '@radix-ui/react-switch@1.3.3(@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)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-primitive': 2.1.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) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-previous': 1.1.2(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-size': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 - '@types/react-dom': 19.2.0(@types/react@19.2.0) + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.0)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.7) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-is-hydrated@0.1.0(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-previous@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-rect@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - react: 19.2.0 + '@radix-ui/rect': 1.1.2 + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@radix-ui/react-use-size@1.1.2(@types/react@19.2.17)(react@19.2.7)': dependencies: - '@radix-ui/rect': 1.1.1 - react: 19.2.0 + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.7) + react: 19.2.7 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 + + '@radix-ui/rect@1.1.2': {} + + '@resreq/event-hub@1.7.2': {} + + '@rolldown/binding-android-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-arm64@1.1.5': + optional: true + + '@rolldown/binding-darwin-x64@1.1.5': + optional: true - '@radix-ui/react-use-size@1.1.1(@types/react@19.2.0)(react@19.2.0)': + '@rolldown/binding-freebsd-x64@1.1.5': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.1.5': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.1.5': + optional: true + + '@rolldown/binding-linux-x64-musl@1.1.5': + optional: true + + '@rolldown/binding-openharmony-arm64@1.1.5': + optional: true + + '@rolldown/binding-wasm32-wasi@1.1.5': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.0)(react@19.2.0) - react: 19.2.0 - optionalDependencies: - '@types/react': 19.2.0 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) + optional: true - '@radix-ui/rect@1.1.1': {} + '@rolldown/binding-win32-arm64-msvc@1.1.5': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.1.5': + optional: true - '@resreq/event-hub@1.6.0': {} + '@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@babel/core': 7.29.7 + picomatch: 4.0.5 + rolldown: 1.1.5 + optionalDependencies: + '@babel/runtime': 7.29.7 + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - '@rolldown/pluginutils@1.0.0-beta.38': {} + '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.3.0(rollup@4.52.3)': + '@rollup/pluginutils@5.4.0(rollup@4.52.3)': dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 estree-walker: 2.0.2 - picomatch: 4.0.3 + picomatch: 4.0.5 optionalDependencies: rollup: 4.52.3 @@ -6906,39 +7261,39 @@ snapshots: '@rtco/client@0.3.6': dependencies: '@rtco/peer': 0.3.6 - bufferutil: 4.0.9 - eventemitter3: 5.0.1 - nanoid: 5.1.6 - socket.io-client: 4.8.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) - utf-8-validate: 6.0.5 + bufferutil: 4.1.0 + eventemitter3: 5.0.4 + nanoid: 5.1.16 + socket.io-client: 4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6) + utf-8-validate: 6.0.6 transitivePeerDependencies: - supports-color '@rtco/peer@0.3.6': dependencies: - eventemitter3: 5.0.1 + eventemitter3: 5.0.4 '@sec-ant/readable-stream@0.4.1': {} - '@semantic-release/changelog@6.0.3(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/changelog@6.0.3(semantic-release@25.0.7(typescript@7.0.2))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 - fs-extra: 11.3.2 - lodash: 4.17.21 - semantic-release: 24.2.9(typescript@5.9.3) + fs-extra: 11.3.6 + lodash: 4.18.1 + semantic-release: 25.0.7(typescript@7.0.2) - '@semantic-release/commit-analyzer@13.0.1(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/commit-analyzer@13.0.1(semantic-release@25.0.7(typescript@7.0.2))': dependencies: - conventional-changelog-angular: 8.0.0 - conventional-changelog-writer: 8.2.0 + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.4.0 debug: 4.4.3 import-from-esm: 2.0.0 - lodash-es: 4.17.21 + lodash-es: 4.18.1 micromatch: 4.0.8 - semantic-release: 24.2.9(typescript@5.9.3) + semantic-release: 25.0.7(typescript@7.0.2) transitivePeerDependencies: - supports-color @@ -6946,145 +7301,157 @@ snapshots: '@semantic-release/error@4.0.0': {} - '@semantic-release/exec@7.1.0(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/exec@7.1.0(semantic-release@25.0.7(typescript@7.0.2))': dependencies: '@semantic-release/error': 4.0.0 aggregate-error: 3.1.0 debug: 4.4.3 - execa: 9.6.0 - lodash-es: 4.17.21 + execa: 9.6.1 + lodash-es: 4.18.1 parse-json: 8.3.0 - semantic-release: 24.2.9(typescript@5.9.3) + semantic-release: 25.0.7(typescript@7.0.2) transitivePeerDependencies: - supports-color - '@semantic-release/git@10.0.1(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/git@10.0.1(semantic-release@25.0.7(typescript@7.0.2))': dependencies: '@semantic-release/error': 3.0.0 aggregate-error: 3.1.0 debug: 4.4.3 dir-glob: 3.0.1 execa: 5.1.1 - lodash: 4.17.21 + lodash: 4.18.1 micromatch: 4.0.8 p-reduce: 2.1.0 - semantic-release: 24.2.9(typescript@5.9.3) + semantic-release: 25.0.7(typescript@7.0.2) transitivePeerDependencies: - supports-color - '@semantic-release/github@11.0.6(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/github@12.0.9(semantic-release@25.0.7(typescript@7.0.2))': dependencies: - '@octokit/core': 7.0.5 - '@octokit/plugin-paginate-rest': 13.2.0(@octokit/core@7.0.5) - '@octokit/plugin-retry': 8.0.2(@octokit/core@7.0.5) - '@octokit/plugin-throttling': 11.0.2(@octokit/core@7.0.5) + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-retry': 8.1.0(@octokit/core@7.0.6) + '@octokit/plugin-throttling': 11.0.3(@octokit/core@7.0.6) '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 debug: 4.4.3 dir-glob: 3.0.1 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 - issue-parser: 7.0.1 - lodash-es: 4.17.21 + http-proxy-agent: 9.1.0 + https-proxy-agent: 9.1.0 + issue-parser: 7.0.2 + lodash-es: 4.18.1 mime: 4.1.0 p-filter: 4.1.0 - semantic-release: 24.2.9(typescript@5.9.3) - tinyglobby: 0.2.15 + semantic-release: 25.0.7(typescript@7.0.2) + tinyglobby: 0.2.17 + undici: 7.28.0 url-join: 5.0.0 transitivePeerDependencies: + - kerberos - supports-color - '@semantic-release/npm@12.0.2(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/npm@13.1.5(semantic-release@25.0.7(typescript@7.0.2))': dependencies: + '@actions/core': 3.0.1 '@semantic-release/error': 4.0.0 aggregate-error: 5.0.0 - execa: 9.6.0 - fs-extra: 11.3.2 - lodash-es: 4.17.21 + env-ci: 11.2.0 + execa: 9.6.1 + fs-extra: 11.3.6 + lodash-es: 4.18.1 nerf-dart: 1.0.0 - normalize-url: 8.1.0 - npm: 10.9.4 + normalize-url: 9.0.1 + npm: 11.18.0 rc: 1.2.8 - read-pkg: 9.0.1 - registry-auth-token: 5.1.0 - semantic-release: 24.2.9(typescript@5.9.3) - semver: 7.7.2 - tempy: 3.1.0 + read-pkg: 10.1.0 + registry-auth-token: 5.1.1 + semantic-release: 25.0.7(typescript@7.0.2) + semver: 7.8.5 + tempy: 3.2.0 - '@semantic-release/release-notes-generator@14.1.0(semantic-release@24.2.9(typescript@5.9.3))': + '@semantic-release/release-notes-generator@14.1.1(semantic-release@25.0.7(typescript@7.0.2))': dependencies: - conventional-changelog-angular: 8.0.0 - conventional-changelog-writer: 8.2.0 + conventional-changelog-angular: 8.3.1 + conventional-changelog-writer: 8.4.0 conventional-commits-filter: 5.0.0 - conventional-commits-parser: 6.2.0 + conventional-commits-parser: 6.4.0 debug: 4.4.3 - get-stream: 7.0.1 import-from-esm: 2.0.0 - into-stream: 7.0.0 - lodash-es: 4.17.21 + lodash-es: 4.18.1 read-package-up: 11.0.0 - semantic-release: 24.2.9(typescript@5.9.3) + semantic-release: 25.0.7(typescript@7.0.2) transitivePeerDependencies: - supports-color + '@simple-libs/child-process-utils@2.0.0': + dependencies: + '@simple-libs/stream-utils': 2.0.0 + + '@simple-libs/stream-utils@1.2.0': {} + + '@simple-libs/stream-utils@2.0.0': {} + '@sindresorhus/is@4.6.0': {} '@sindresorhus/merge-streams@4.0.0': {} '@socket.io/component-emitter@3.1.2': {} + '@standard-schema/spec@1.1.0': {} + '@standard-schema/utils@0.3.0': {} - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.28.4)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.7 - '@svgr/babel-preset@8.1.0(@babel/core@7.28.4)': + '@svgr/babel-preset@8.1.0(@babel/core@7.29.7)': dependencies: - '@babel/core': 7.28.4 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.28.4) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.28.4) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.28.4) + '@babel/core': 7.29.7 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.7) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.7) - '@svgr/core@8.1.0(typescript@5.9.3)': + '@svgr/core@8.1.0(typescript@7.0.2)': dependencies: - '@babel/core': 7.28.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.9.3) + cosmiconfig: 8.3.6(typescript@7.0.2) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -7092,357 +7459,325 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.7 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.9.3))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@7.0.2))': dependencies: - '@babel/core': 7.28.4 - '@svgr/babel-preset': 8.1.0(@babel/core@7.28.4) - '@svgr/core': 8.1.0(typescript@5.9.3) + '@babel/core': 7.29.7 + '@svgr/babel-preset': 8.1.0(@babel/core@7.29.7) + '@svgr/core': 8.1.0(typescript@7.0.2) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - '@tailwindcss/node@4.1.14': + '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.18.3 - jiti: 2.6.1 - lightningcss: 1.30.1 - magic-string: 0.30.19 + enhanced-resolve: 5.24.2 + jiti: 2.7.0 + lightningcss: 1.32.0 + magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.1.14 + tailwindcss: 4.3.3 - '@tailwindcss/oxide-android-arm64@4.1.14': + '@tailwindcss/oxide-android-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-arm64@4.1.14': + '@tailwindcss/oxide-darwin-arm64@4.3.3': optional: true - '@tailwindcss/oxide-darwin-x64@4.1.14': + '@tailwindcss/oxide-darwin-x64@4.3.3': optional: true - '@tailwindcss/oxide-freebsd-x64@4.1.14': + '@tailwindcss/oxide-freebsd-x64@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.14': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.1.14': + '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.1.14': + '@tailwindcss/oxide-linux-arm64-musl@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.1.14': + '@tailwindcss/oxide-linux-x64-gnu@4.3.3': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.1.14': + '@tailwindcss/oxide-linux-x64-musl@4.3.3': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.1.14': + '@tailwindcss/oxide-wasm32-wasi@4.3.3': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.1.14': + '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.1.14': + '@tailwindcss/oxide-win32-x64-msvc@4.3.3': optional: true - '@tailwindcss/oxide@4.1.14': - dependencies: - detect-libc: 2.1.1 - tar: 7.5.1 + '@tailwindcss/oxide@4.3.3': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.1.14 - '@tailwindcss/oxide-darwin-arm64': 4.1.14 - '@tailwindcss/oxide-darwin-x64': 4.1.14 - '@tailwindcss/oxide-freebsd-x64': 4.1.14 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.14 - '@tailwindcss/oxide-linux-arm64-gnu': 4.1.14 - '@tailwindcss/oxide-linux-arm64-musl': 4.1.14 - '@tailwindcss/oxide-linux-x64-gnu': 4.1.14 - '@tailwindcss/oxide-linux-x64-musl': 4.1.14 - '@tailwindcss/oxide-wasm32-wasi': 4.1.14 - '@tailwindcss/oxide-win32-arm64-msvc': 4.1.14 - '@tailwindcss/oxide-win32-x64-msvc': 4.1.14 - - '@tailwindcss/postcss@4.1.14': + '@tailwindcss/oxide-android-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-arm64': 4.3.3 + '@tailwindcss/oxide-darwin-x64': 4.3.3 + '@tailwindcss/oxide-freebsd-x64': 4.3.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 + '@tailwindcss/oxide-linux-x64-musl': 4.3.3 + '@tailwindcss/oxide-wasm32-wasi': 4.3.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 + + '@tailwindcss/postcss@4.3.3': dependencies: '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.1.14 - '@tailwindcss/oxide': 4.1.14 - postcss: 8.5.6 - tailwindcss: 4.1.14 + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + postcss: 8.5.19 + tailwindcss: 4.3.3 - '@tailwindcss/typography@0.5.19(tailwindcss@4.1.14)': + '@tailwindcss/typography@0.5.20(tailwindcss@4.3.3)': dependencies: postcss-selector-parser: 6.0.10 - tailwindcss: 4.1.14 + tailwindcss: 4.3.3 - '@tailwindcss/vite@4.1.14(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1))': + '@tailwindcss/vite@4.3.3(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - '@tailwindcss/node': 4.1.14 - '@tailwindcss/oxide': 4.1.14 - tailwindcss: 4.1.14 - vite: 7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + '@tailwindcss/node': 4.3.3 + '@tailwindcss/oxide': 4.3.3 + tailwindcss: 4.3.3 + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - '@types/babel__core@7.20.5': + '@tybys/wasm-util@0.10.3': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 - '@types/babel__generator': 7.27.0 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.28.0 + tslib: 2.8.1 + optional: true - '@types/babel__generator@7.27.0': + '@types/chai@5.2.3': dependencies: - '@babel/types': 7.28.4 + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 - '@types/babel__template@7.4.4': + '@types/debug@4.1.13': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@types/ms': 2.1.0 - '@types/babel__traverse@7.28.0': - dependencies: - '@babel/types': 7.28.4 + '@types/deep-eql@4.0.2': {} - '@types/conventional-commits-parser@5.0.1': + '@types/esrecurse@4.3.1': + optional: true + + '@types/estree-jsx@1.0.5': dependencies: - '@types/node': 24.6.2 + '@types/estree': 1.0.9 + + '@types/estree@1.0.8': + optional: true + + '@types/estree@1.0.9': {} - '@types/debug@4.1.12': + '@types/filesystem@0.0.36': dependencies: - '@types/ms': 2.1.0 + '@types/filewriter': 0.0.33 - '@types/eslint-plugin-tailwindcss@3.17.0': + '@types/filewriter@0.0.33': {} + + '@types/har-format@1.2.16': {} + + '@types/hast@3.0.5': dependencies: - '@types/eslint': 9.6.1 + '@types/unist': 3.0.3 - '@types/eslint@9.6.1': + '@types/js-cookie@3.0.6': {} + + '@types/json-schema@7.0.15': + optional: true + + '@types/mdast@4.0.4': dependencies: - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 + '@types/unist': 3.0.3 - '@types/estree-jsx@1.0.5': + '@types/minimatch@3.0.5': {} + + '@types/ms@2.1.0': {} + + '@types/node@26.1.1': dependencies: - '@types/estree': 1.0.8 + undici-types: 8.3.0 - '@types/estree@1.0.8': {} + '@types/normalize-package-data@2.4.4': {} - '@types/filesystem@0.0.36': + '@types/react-dom@19.2.3(@types/react@19.2.17)': dependencies: - '@types/filewriter': 0.0.33 + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/webextension-polyfill@0.12.5': {} - '@types/filewriter@0.0.33': {} + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true - '@types/har-format@1.2.16': {} + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true - '@types/hast@3.0.4': - dependencies: - '@types/unist': 3.0.3 + '@typescript/typescript-darwin-x64@7.0.2': + optional: true - '@types/js-cookie@2.2.7': {} + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true - '@types/json-schema@7.0.15': {} + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 + '@typescript/typescript-linux-arm64@7.0.2': + optional: true - '@types/minimatch@3.0.5': {} + '@typescript/typescript-linux-arm@7.0.2': + optional: true - '@types/ms@2.1.0': {} + '@typescript/typescript-linux-loong64@7.0.2': + optional: true - '@types/node@24.6.2': - dependencies: - undici-types: 7.13.0 + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true - '@types/normalize-package-data@2.4.4': {} + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true - '@types/react-dom@19.2.0(@types/react@19.2.0)': - dependencies: - '@types/react': 19.2.0 + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true - '@types/react@19.2.0': - dependencies: - csstype: 3.1.3 + '@typescript/typescript-linux-s390x@7.0.2': + optional: true - '@types/trusted-types@2.0.7': + '@typescript/typescript-linux-x64@7.0.2': optional: true - '@types/unist@2.0.11': {} + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true - '@types/unist@3.0.3': {} + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true - '@types/webextension-polyfill@0.8.3': {} + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true - '@types/yauzl@2.10.3': - dependencies: - '@types/node': 24.6.2 + '@typescript/typescript-openbsd-x64@7.0.2': optional: true - '@typescript-eslint/eslint-plugin@8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 - eslint: 9.36.0(jiti@2.6.1) - graphemer: 1.4.0 - ignore: 7.0.5 - natural-compare: 1.4.0 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-sunos-x64@7.0.2': + optional: true - '@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.45.0 - debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-win32-arm64@7.0.2': + optional: true - '@typescript-eslint/project-service@8.45.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - debug: 4.4.3 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@typescript/typescript-win32-x64@7.0.2': + optional: true - '@typescript-eslint/scope-manager@8.45.0': - dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 + '@ungap/structured-clone@1.3.3': {} - '@typescript-eslint/tsconfig-utils@8.45.0(typescript@5.9.3)': + '@vitejs/plugin-react@6.0.3(@rolldown/plugin-babel@0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)))(babel-plugin-react-compiler@19.1.0-rc.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': dependencies: - typescript: 5.9.3 + '@rolldown/pluginutils': 1.0.1 + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + optionalDependencies: + '@rolldown/plugin-babel': 0.2.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(rolldown@1.1.5)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + babel-plugin-react-compiler: 19.1.0-rc.3 - '@typescript-eslint/type-utils@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': + '@vitest/expect@4.1.10': dependencies: - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - debug: 4.4.3 - eslint: 9.36.0(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@typescript-eslint/types@8.45.0': {} + '@vitest/mocker@4.1.10(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - '@typescript-eslint/typescript-estree@8.45.0(typescript@5.9.3)': + '@vitest/pretty-format@4.1.10': dependencies: - '@typescript-eslint/project-service': 8.45.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.45.0(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/visitor-keys': 8.45.0 - debug: 4.4.3 - fast-glob: 3.3.3 - is-glob: 4.0.3 - minimatch: 9.0.5 - semver: 7.7.2 - ts-api-utils: 2.1.0(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + tinyrainbow: 3.1.0 - '@typescript-eslint/utils@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3)': + '@vitest/runner@4.1.10': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color + '@vitest/utils': 4.1.10 + pathe: 2.0.3 - '@typescript-eslint/visitor-keys@8.45.0': + '@vitest/snapshot@4.1.10': dependencies: - '@typescript-eslint/types': 8.45.0 - eslint-visitor-keys: 4.2.1 + '@vitest/pretty-format': 4.1.10 + '@vitest/utils': 4.1.10 + magic-string: 0.30.21 + pathe: 2.0.3 - '@ungap/structured-clone@1.3.0': {} + '@vitest/spy@4.1.10': {} - '@vitejs/plugin-react@5.0.4(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1))': + '@vitest/utils@4.1.10': dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.4) - '@rolldown/pluginutils': 1.0.0-beta.38 - '@types/babel__core': 7.20.5 - react-refresh: 0.17.0 - vite: 7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) - transitivePeerDependencies: - - supports-color + '@vitest/pretty-format': 4.1.10 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 '@webcomponents/custom-elements@1.6.0': {} - '@webext-core/fake-browser@1.3.2': + '@webext-core/fake-browser@1.5.2': dependencies: + '@types/webextension-polyfill': 0.12.5 lodash.merge: 4.6.2 - '@webext-core/isolated-element@1.1.2': + '@webext-core/isolated-element@1.1.5': dependencies: is-potential-custom-element-name: 1.0.1 - '@webext-core/match-patterns@1.0.3': {} - - '@webext-core/messaging@2.3.0': - dependencies: - serialize-error: 11.0.3 - uid: 2.0.2 - webextension-polyfill: 0.10.0 + '@webext-core/match-patterns@1.1.0': {} - '@webext-core/proxy-service@1.2.1(@webext-core/messaging@2.3.0)(webextension-polyfill@0.12.0)': + '@wxt-dev/browser@0.1.43': dependencies: - '@webext-core/messaging': 2.3.0 - get-value: 3.0.1 - webextension-polyfill: 0.12.0 + '@types/filesystem': 0.0.36 + '@types/har-format': 1.2.16 - '@wxt-dev/browser@0.1.4': + '@wxt-dev/browser@0.2.2': dependencies: '@types/filesystem': 0.0.36 '@types/har-format': 1.2.16 - '@wxt-dev/storage@1.2.0': + '@wxt-dev/storage@1.2.8': dependencies: - '@wxt-dev/browser': 0.1.4 + '@wxt-dev/browser': 0.1.43 async-mutex: 0.5.0 dequal: 2.0.3 '@xobotyi/scrollbar-width@1.9.5': {} - JSONStream@1.3.5: - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 - - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.17.0): dependencies: - acorn: 8.15.0 + acorn: 8.17.0 + optional: true - acorn@8.15.0: {} + acorn@8.17.0: {} - adm-zip@0.5.16: {} + adm-zip@0.5.18: {} - agent-base@7.1.4: {} + agent-base@9.0.0: {} aggregate-error@3.1.0: dependencies: @@ -7454,21 +7789,22 @@ snapshots: clean-stack: 5.3.0 indent-string: 5.0.0 - aidly@1.36.0: + aidly@1.37.0: dependencies: small-queue: 1.1.2 - ajv@6.12.6: + ajv@6.15.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 + optional: true - ajv@8.17.1: + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 + fast-uri: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -7476,7 +7812,7 @@ snapshots: dependencies: string-width: 4.2.3 - ansi-escapes@7.1.1: + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -7499,10 +7835,12 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 argparse@2.0.1: {} + argue-cli@3.1.0: {} + argv-formatter@1.0.0: {} aria-hidden@1.2.6: @@ -7523,13 +7861,15 @@ snapshots: arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.2 es-errors: 1.3.0 get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + async-function@1.0.0: {} async-mutex@0.5.0: @@ -7540,19 +7880,18 @@ snapshots: atomic-sleep@1.0.0: {} - atomically@2.0.3: + atomically@2.1.1: dependencies: - stubborn-fs: 1.2.5 - when-exit: 2.1.4 + stubborn-fs: 2.0.0 + when-exit: 2.1.5 - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.5.4(postcss@8.5.19): dependencies: - browserslist: 4.26.3 - caniuse-lite: 1.0.30001746 - fraction.js: 4.3.7 - normalize-range: 0.1.2 + browserslist: 4.28.6 + caniuse-lite: 1.0.30001806 + fraction.js: 5.3.4 picocolors: 1.1.1 - postcss: 8.5.6 + postcss: 8.5.19 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: @@ -7561,29 +7900,21 @@ snapshots: babel-plugin-react-compiler@19.1.0-rc.3: dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.7 bail@2.0.2: {} balanced-match@1.0.2: {} - base64-js@1.5.1: {} + balanced-match@4.0.4: {} - baseline-browser-mapping@2.8.10: {} + baseline-browser-mapping@2.10.43: {} before-after-hook@4.0.0: {} - birecord@0.1.1: {} - - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.2 - bluebird@3.7.2: {} - boolbase@1.0.0: {} + boolbase@2.0.0: {} bottleneck@2.19.5: {} @@ -7598,37 +7929,32 @@ snapshots: widest-line: 5.0.0 wrap-ansi: 9.0.2 - brace-expansion@1.1.12: + brace-expansion@1.1.16: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@5.0.7: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.26.3: + browserslist@4.28.6: dependencies: - baseline-browser-mapping: 2.8.10 - caniuse-lite: 1.0.30001746 - electron-to-chromium: 1.5.228 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.392 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) - buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bufferutil@4.0.9: + bufferutil@4.1.0: dependencies: node-gyp-build: 4.8.4 @@ -7636,31 +7962,33 @@ snapshots: dependencies: run-applescript: 7.1.0 - c12@3.3.0(magicast@0.3.5): + c12@3.3.4(magicast@0.5.3): dependencies: - chokidar: 4.0.3 - confbox: 0.2.2 - defu: 6.1.4 - dotenv: 17.2.3 - exsolve: 1.0.7 - giget: 2.0.0 - jiti: 2.6.1 + chokidar: 5.0.0 + confbox: 0.2.4 + defu: 6.1.7 + dotenv: 17.4.2 + exsolve: 1.1.0 + giget: 3.3.0 + jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 - perfect-debounce: 2.0.0 - pkg-types: 2.3.0 - rc9: 2.1.2 + perfect-debounce: 2.1.0 + pkg-types: 2.3.1 + rc9: 3.0.1 optionalDependencies: - magicast: 0.3.5 + magicast: 0.5.3 cac@6.7.14: {} + cac@7.0.0: {} + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -7678,10 +8006,12 @@ snapshots: camelcase@8.0.0: {} - caniuse-lite@1.0.30001746: {} + caniuse-lite@1.0.30001806: {} ccount@2.0.1: {} + chai@6.2.2: {} + chalk@2.4.2: dependencies: ansi-styles: 3.2.1 @@ -7705,26 +8035,22 @@ snapshots: character-reference-invalid@2.0.1: {} - chokidar@4.0.3: + chokidar@5.0.0: dependencies: - readdirp: 4.1.2 - - chownr@3.0.0: {} + readdirp: 5.0.0 chrome-launcher@1.2.0: dependencies: - '@types/node': 24.6.2 + '@types/node': 26.1.1 escape-string-regexp: 4.0.0 is-wsl: 2.2.0 lighthouse-logger: 2.0.2 transitivePeerDependencies: - supports-color - ci-info@4.3.0: {} + ci-info@4.4.0: {} - citty@0.1.6: - dependencies: - consola: 3.4.2 + citty@0.2.2: {} class-variance-authority@0.7.1: dependencies: @@ -7738,10 +8064,6 @@ snapshots: cli-boxes@3.0.0: {} - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 @@ -7753,9 +8075,7 @@ snapshots: mz: 2.7.0 parse5: 5.1.1 parse5-htmlparser2-tree-adapter: 6.0.1 - yargs: 16.2.0 - - cli-spinners@2.9.2: {} + yargs: 16.2.2 cli-table3@0.6.5: dependencies: @@ -7763,15 +8083,10 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 - cli-truncate@4.0.0: + cli-truncate@5.2.0: dependencies: - slice-ansi: 5.0.0 - string-width: 7.2.0 - - cli-truncate@5.1.0: - dependencies: - slice-ansi: 7.1.2 - string-width: 8.1.0 + slice-ansi: 8.0.0 + string-width: 8.2.2 cliui@7.0.4: dependencies: @@ -7785,13 +8100,15 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - clone@1.0.4: {} + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 clsx@2.1.1: {} - cobe@0.6.5: - dependencies: - phenomenon: 1.6.0 + cobe@2.0.1: {} color-convert@1.9.3: dependencies: @@ -7805,14 +8122,10 @@ snapshots: color-name@1.1.4: {} - colorette@2.0.20: {} - - comctx@1.4.3: {} + comctx@1.7.5: {} comma-separated-tokens@2.0.3: {} - commander@14.0.1: {} - commander@2.9.0: dependencies: graceful-readlink: 1.0.1 @@ -7824,8 +8137,6 @@ snapshots: array-ify: 1.0.0 dot-prop: 5.3.0 - compare-versions@6.1.1: {} - concat-map@0.0.1: {} concat-stream@1.6.2: @@ -7837,7 +8148,7 @@ snapshots: confbox@0.1.8: {} - confbox@0.2.2: {} + confbox@0.2.4: {} config-chain@1.1.13: dependencies: @@ -7846,81 +8157,85 @@ snapshots: configstore@7.1.0: dependencies: - atomically: 2.0.3 + atomically: 2.1.1 dot-prop: 9.0.0 graceful-fs: 4.2.11 xdg-basedir: 5.1.0 consola@3.4.2: {} - conventional-changelog-angular@7.0.0: + content-type@2.0.0: {} + + conventional-changelog-angular@8.3.1: dependencies: compare-func: 2.0.0 - conventional-changelog-angular@8.0.0: + conventional-changelog-angular@9.2.1: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.2.1 - conventional-changelog-conventionalcommits@7.0.2: + conventional-changelog-conventionalcommits@10.2.1: dependencies: - compare-func: 2.0.0 + '@conventional-changelog/template': 1.2.1 - conventional-changelog-writer@8.2.0: + conventional-changelog-writer@8.4.0: dependencies: + '@simple-libs/stream-utils': 1.2.0 conventional-commits-filter: 5.0.0 - handlebars: 4.7.8 + handlebars: 4.7.9 meow: 13.2.0 - semver: 7.7.2 + semver: 7.8.5 conventional-commits-filter@5.0.0: {} - conventional-commits-parser@5.0.0: + conventional-commits-parser@6.4.0: dependencies: - JSONStream: 1.3.5 - is-text-path: 2.0.0 - meow: 12.1.1 - split2: 4.2.0 + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 - conventional-commits-parser@6.2.0: + conventional-commits-parser@7.1.0: dependencies: - meow: 13.2.0 + '@simple-libs/stream-utils': 2.0.0 + argue-cli: 3.1.0 convert-hrtime@5.0.0: {} convert-source-map@2.0.0: {} - cookie-es@1.2.2: {} + cookie-es@1.2.3: {} copy-to-clipboard@3.3.3: dependencies: toggle-selection: 1.0.6 + core-js@3.49.0: {} + core-util-is@1.0.3: {} - cosmiconfig-typescript-loader@6.1.0(@types/node@24.6.2)(cosmiconfig@9.0.0(typescript@5.9.3))(typescript@5.9.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): dependencies: - '@types/node': 24.6.2 - cosmiconfig: 9.0.0(typescript@5.9.3) + '@types/node': 26.1.1 + cosmiconfig: 9.0.2(typescript@7.0.2) jiti: 2.6.1 - typescript: 5.9.3 + typescript: 7.0.2 - cosmiconfig@8.3.6(typescript@5.9.3): + cosmiconfig@8.3.6(typescript@7.0.2): dependencies: import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.3.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.9.3 + typescript: 7.0.2 - cosmiconfig@9.0.0(typescript@5.9.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.3.0 parse-json: 5.2.0 optionalDependencies: - typescript: 5.9.3 + typescript: 7.0.2 cross-env@10.1.0: dependencies: @@ -7953,34 +8268,32 @@ snapshots: dependencies: hyphenate-style-name: 1.1.0 - css-select@5.2.2: + css-select@7.0.0: dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 5.0.3 - domutils: 3.2.2 - nth-check: 2.1.1 + boolbase: 2.0.0 + css-what: 8.0.0 + domhandler: 6.0.1 + domutils: 4.0.2 + nth-check: 3.0.1 css-tree@1.1.3: dependencies: mdn-data: 2.0.14 source-map: 0.6.1 - css-what@6.2.2: {} + css-what@8.0.0: {} cssesc@3.0.0: {} cssom@0.5.0: {} - csstype@3.1.3: {} + csstype@3.2.3: {} - danmu@0.18.1: + danmu@0.20.0: dependencies: - aidly: 1.36.0 + aidly: 1.37.0 hooks-plugin: 1.3.4 - dargs@8.1.0: {} - data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -7999,7 +8312,7 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.2 - date-fns@4.1.0: {} + date-fns@4.4.0: {} debounce@1.2.1: {} @@ -8011,24 +8324,21 @@ snapshots: dependencies: ms: 2.1.3 - decode-named-character-reference@1.2.0: + decode-named-character-reference@1.3.0: dependencies: character-entities: 2.0.2 deep-extend@0.6.0: {} - deep-is@0.1.4: {} + deep-is@0.1.4: + optional: true - default-browser-id@5.0.0: {} + default-browser-id@5.0.1: {} - default-browser@5.2.1: + default-browser@5.5.0: dependencies: bundle-name: 4.1.0 - default-browser-id: 5.0.0 - - defaults@1.0.4: - dependencies: - clone: 1.0.4 + default-browser-id: 5.0.1 define-data-property@1.1.4: dependencies: @@ -8046,13 +8356,13 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 - defu@6.1.4: {} + defu@6.1.7: {} dequal@2.0.3: {} destr@2.0.5: {} - detect-libc@2.1.1: {} + detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -8070,15 +8380,23 @@ snapshots: domhandler: 5.0.3 entities: 4.5.0 + dom-serializer@3.1.1: + dependencies: + domelementtype: 3.0.0 + domhandler: 6.0.1 + entities: 8.0.0 + domelementtype@2.3.0: {} + domelementtype@3.0.0: {} + domhandler@5.0.3: dependencies: domelementtype: 2.3.0 - dompurify@3.2.7: - optionalDependencies: - '@types/trusted-types': 2.0.7 + domhandler@6.0.1: + dependencies: + domelementtype: 3.0.0 domutils@3.2.2: dependencies: @@ -8086,6 +8404,12 @@ snapshots: domelementtype: 2.3.0 domhandler: 5.0.3 + domutils@4.0.2: + dependencies: + dom-serializer: 3.1.1 + domelementtype: 3.0.0 + domhandler: 6.0.1 + dot-case@3.0.4: dependencies: no-case: 3.0.4 @@ -8105,7 +8429,7 @@ snapshots: dotenv@16.6.1: {} - dotenv@17.2.3: {} + dotenv@17.4.2: {} dunder-proto@1.0.1: dependencies: @@ -8117,28 +8441,24 @@ snapshots: dependencies: readable-stream: 2.3.8 - eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 - electron-to-chromium@1.5.228: {} + electron-to-chromium@1.5.392: {} - emoji-regex@10.5.0: {} + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} - emoji-regex@9.2.2: {} - emojilib@2.4.0: {} - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - engine.io-client@6.6.3(bufferutil@4.0.9)(utf-8-validate@6.0.5): + engine.io-client@6.6.6(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 engine.io-parser: 5.2.3 - ws: 8.17.1(bufferutil@4.0.9)(utf-8-validate@6.0.5) + ws: 8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) xmlhttprequest-ssl: 2.1.2 transitivePeerDependencies: - bufferutil @@ -8147,14 +8467,16 @@ snapshots: engine.io-parser@5.2.3: {} - enhanced-resolve@5.18.3: + enhanced-resolve@5.24.2: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.3 entities@4.5.0: {} - entities@6.0.1: {} + entities@7.0.1: {} + + entities@8.0.0: {} env-ci@11.2.0: dependencies: @@ -8173,22 +8495,29 @@ snapshots: dependencies: stackframe: 1.3.4 - es-abstract@1.24.0: + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 get-intrinsic: 1.3.0 get-proto: 1.0.1 get-symbol-description: 1.1.0 @@ -8197,7 +8526,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -8215,28 +8544,28 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 string.prototype.trimstart: 1.0.8 typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.19 + which-typed-array: 1.1.22 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-module-lexer@1.7.0: {} + es-module-lexer@2.3.1: {} - es-object-atoms@1.1.1: + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -8245,44 +8574,49 @@ 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-to-primitive@1.3.0: + es-to-primitive@1.3.4: dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 is-callable: 1.2.7 is-date-object: 1.1.0 is-symbol: 1.1.1 + es-toolkit@1.49.0: {} + es6-error@4.1.1: {} - esbuild@0.25.10: + esbuild@0.27.7: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 escalade@3.2.0: {} @@ -8294,175 +8628,40 @@ snapshots: escape-string-regexp@5.0.0: {} - eslint-config-prettier@10.1.8(eslint@9.36.0(jiti@2.6.1)): - dependencies: - eslint: 9.36.0(jiti@2.6.1) - - eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.36.0(jiti@2.6.1)))(eslint@9.36.0(jiti@2.6.1))(prettier@3.6.2): - dependencies: - eslint: 9.36.0(jiti@2.6.1) - prettier: 3.6.2 - prettier-linter-helpers: 1.0.0 - synckit: 0.11.11 - optionalDependencies: - '@types/eslint': 9.6.1 - eslint-config-prettier: 10.1.8(eslint@9.36.0(jiti@2.6.1)) - - eslint-plugin-react-debug@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - string-ts: 2.2.1 - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-dom@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - compare-versions: 6.1.1 - eslint: 9.36.0(jiti@2.6.1) - string-ts: 2.2.1 - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-hooks-extra@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - string-ts: 2.2.1 - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-naming-convention@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - string-ts: 2.2.1 - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-web-api@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - string-ts: 2.2.1 - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-react-x@2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@eslint-react/ast': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/core': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/eff': 2.0.5 - '@eslint-react/kit': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/shared': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@eslint-react/var': 2.0.5(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.45.0 - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/types': 8.45.0 - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - compare-versions: 6.1.1 - eslint: 9.36.0(jiti@2.6.1) - is-immutable-type: 5.0.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - string-ts: 2.2.1 - ts-api-utils: 2.1.0(typescript@5.9.3) - ts-pattern: 5.8.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - eslint-plugin-tailwindcss@3.18.2(tailwindcss@4.1.14): - dependencies: - fast-glob: 3.3.3 - postcss: 8.5.6 - tailwindcss: 4.1.14 - - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.9 esrecurse: 4.3.0 estraverse: 5.3.0 + optional: true - eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@3.4.3: + optional: true - eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: + optional: true - eslint@9.36.0(jiti@2.6.1): + eslint@10.7.0(jiti@2.7.0): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0(jiti@2.6.1)) - '@eslint-community/regexpp': 4.12.1 - '@eslint/config-array': 0.21.0 - '@eslint/config-helpers': 0.3.1 - '@eslint/core': 0.15.2 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.36.0 - '@eslint/plugin-kit': 0.3.5 - '@humanfs/node': 0.16.7 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.6.0 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.2 + '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - chalk: 4.1.2 + '@types/estree': 1.0.9 + ajv: 6.15.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -8472,32 +8671,36 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 2.6.1 + jiti: 2.7.0 transitivePeerDependencies: - supports-color + optional: true esm-env@1.2.2: {} - espree@10.4.0: + espree@11.2.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 5.0.1 + optional: true - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 + optional: true esrecurse@4.3.0: dependencies: estraverse: 5.3.0 + optional: true - estraverse@5.3.0: {} + estraverse@5.3.0: + optional: true estree-util-is-identifier-name@3.0.0: {} @@ -8505,11 +8708,12 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 - esutils@2.0.3: {} + esutils@2.0.3: + optional: true - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} execa@5.1.1: dependencies: @@ -8535,7 +8739,7 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.6.0: + execa@9.6.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 cross-spawn: 7.0.6 @@ -8550,57 +8754,33 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 - exsolve@1.0.7: {} + expect-type@1.4.0: {} - extend@3.0.2: {} + exsolve@1.1.0: {} - extract-zip@2.0.1: - dependencies: - debug: 4.4.3 - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color + extend@3.0.2: {} - fast-content-type-parse@3.0.0: {} + fake-indexeddb@6.2.5: {} fast-deep-equal@3.1.3: {} - fast-diff@1.3.0: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} + fast-json-stable-stringify@2.1.0: + optional: true - fast-levenshtein@2.0.6: {} + fast-levenshtein@2.0.6: + optional: true fast-redact@3.5.0: {} fast-shallow-equal@1.0.0: {} - fast-uri@3.1.0: {} + fast-uri@3.1.3: {} fastest-stable-stringify@2.0.2: {} - fastq@1.19.1: - dependencies: - reusify: 1.1.0 - - fd-slicer@1.1.0: - dependencies: - pend: 1.2.0 - - fdir@6.5.0(picomatch@4.0.3): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.3 + picomatch: 4.0.5 figures@2.0.0: dependencies: @@ -8613,8 +8793,9 @@ snapshots: file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 + optional: true - filesize@11.0.13: {} + filesize@11.0.22: {} fill-range@7.1.1: dependencies: @@ -8630,65 +8811,54 @@ snapshots: dependencies: locate-path: 6.0.0 path-exists: 4.0.0 - - find-up@7.0.0: - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - unicorn-magic: 0.1.0 + optional: true find-versions@6.0.0: dependencies: semver-regex: 4.0.5 - super-regex: 1.0.0 + super-regex: 1.1.0 firefox-profile@4.7.0: dependencies: - adm-zip: 0.5.16 - fs-extra: 11.3.2 + adm-zip: 0.5.18 + fs-extra: 11.3.6 ini: 4.1.3 minimist: 1.2.8 xml2js: 0.6.2 flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.4.2 keyv: 4.5.4 + optional: true - flatted@3.3.3: {} + flatted@3.4.2: + optional: true for-each@0.3.5: dependencies: is-callable: 1.2.7 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 + form-data-encoder@4.1.0: {} formdata-node@6.0.3: {} - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} - framer-motion@12.23.22(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + framer-motion@12.42.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - motion-dom: 12.23.21 - motion-utils: 12.23.6 + motion-dom: 12.42.2 + motion-utils: 12.39.0 tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.3.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - - from2@2.3.0: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - fs-extra@11.3.2: + fs-extra@11.3.6: dependencies: graceful-fs: 4.2.11 - jsonfile: 6.2.0 + jsonfile: 6.2.1 universalify: 2.0.1 fsevents@2.3.3: @@ -8698,14 +8868,17 @@ snapshots: function-timeout@1.0.2: {} - function.prototype.name@1.1.8: + function.prototype.name@1.2.0: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 - define-properties: 1.2.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 functions-have-names: 1.2.3 - hasown: 2.0.2 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 is-callable: 1.2.7 + is-document.all: 1.0.0 functions-have-names@1.2.3: {} @@ -8724,19 +8897,19 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.6.0: {} get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-nonce@1.0.1: {} @@ -8746,16 +8919,10 @@ snapshots: get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@5.2.0: - dependencies: - pump: 3.0.3 + es-object-atoms: 1.1.2 get-stream@6.0.1: {} - get-stream@7.0.1: {} - get-stream@8.0.1: {} get-stream@9.0.1: @@ -8769,18 +8936,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-value@3.0.1: - dependencies: - isobject: 3.0.1 - - giget@2.0.0: - dependencies: - citty: 0.1.6 - consola: 3.4.2 - defu: 6.1.4 - node-fetch-native: 1.6.7 - nypm: 0.6.2 - pathe: 2.0.3 + giget@3.3.0: {} git-log-parser@1.2.1: dependencies: @@ -8791,38 +8947,26 @@ snapshots: through2: 2.0.5 traverse: 0.6.8 - git-raw-commits@4.0.0: - dependencies: - dargs: 8.1.0 - meow: 12.1.1 - split2: 4.2.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 + optional: true glob-to-regexp@0.4.1: {} - glob@11.0.3: + glob@13.0.6: dependencies: - foreground-child: 3.3.1 - jackspeak: 4.1.1 - minimatch: 10.0.3 - minipass: 7.1.2 - package-json-from-dist: 1.0.1 - path-scurry: 2.0.0 + minimatch: 10.2.5 + minipass: 7.1.3 + path-scurry: 2.0.2 global-directory@4.0.1: dependencies: ini: 4.1.1 - globals@14.0.0: {} - - globals@16.4.0: {} + global-directory@5.0.0: + dependencies: + ini: 6.0.0 globalthis@1.0.4: dependencies: @@ -8837,23 +8981,21 @@ snapshots: graceful-readlink@1.0.1: {} - graphemer@1.4.0: {} - growly@1.3.0: {} - h3@1.15.4: + h3@1.15.11: dependencies: - cookie-es: 1.2.2 + cookie-es: 1.2.3 crossws: 0.3.5 - defu: 6.1.4 + defu: 6.1.7 destr: 2.0.5 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.3 + node-mock-http: 1.0.4 radix3: 1.1.2 - ufo: 1.6.1 + ufo: 1.6.4 uncrypto: 0.1.3 - handlebars@4.7.8: + handlebars@4.7.9: dependencies: minimist: 1.2.8 neo-async: 2.6.2 @@ -8882,16 +9024,16 @@ snapshots: dependencies: has-symbols: 1.1.0 - hash-it@6.0.0: {} + hash-it@7.0.3: {} - hasown@2.0.2: + hasown@2.0.4: dependencies: function-bind: 1.1.2 hast-util-to-jsx-runtime@2.3.6: dependencies: - '@types/estree': 1.0.8 - '@types/hast': 3.0.4 + '@types/estree': 1.0.9 + '@types/hast': 3.0.5 '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 devlop: 1.1.0 @@ -8900,9 +9042,9 @@ snapshots: mdast-util-mdx-expression: 2.0.1 mdast-util-mdx-jsx: 3.2.0 mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.1.0 + property-information: 7.2.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.21 unist-util-position: 5.0.0 vfile-message: 4.0.3 transitivePeerDependencies: @@ -8910,17 +9052,17 @@ snapshots: hast-util-whitespace@3.0.0: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 highlight.js@10.7.3: {} hook-std@4.0.0: {} - hookable@5.5.3: {} + hookable@6.1.1: {} hooks-plugin@1.3.4: dependencies: - aidly: 1.36.0 + aidly: 1.37.0 hosted-git-info@2.8.9: {} @@ -8928,33 +9070,37 @@ snapshots: dependencies: lru-cache: 10.4.3 - hosted-git-info@8.1.0: + hosted-git-info@9.0.3: dependencies: - lru-cache: 10.4.3 + lru-cache: 11.5.2 html-escaper@3.0.3: {} html-url-attributes@3.0.1: {} - htmlparser2@10.0.0: + htmlparser2@10.1.0: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 domutils: 3.2.2 - entities: 6.0.1 + entities: 7.0.1 - http-proxy-agent@7.0.2: + http-proxy-agent@9.1.0: dependencies: - agent-base: 7.1.4 + agent-base: 9.0.0 debug: 4.4.3 + proxy-agent-negotiate: 1.1.0 transitivePeerDependencies: + - kerberos - supports-color - https-proxy-agent@7.0.6: + https-proxy-agent@9.1.0: dependencies: - agent-base: 7.1.4 + agent-base: 9.0.0 debug: 4.4.3 + proxy-agent-negotiate: 1.1.0 transitivePeerDependencies: + - kerberos - supports-color human-signals@2.1.0: {} @@ -8967,15 +9113,14 @@ snapshots: hyphenate-style-name@1.1.0: {} - idb-keyval@6.2.2: {} + idb-keyval@6.3.0: {} - ieee754@1.2.1: {} + idb@8.0.3: {} - ignore@5.3.2: {} - - ignore@7.0.5: {} + ignore@5.3.2: + optional: true - imgcap@1.0.2: {} + imgcap@1.1.0: {} immediate@3.0.6: {} @@ -8993,7 +9138,8 @@ snapshots: import-meta-resolve@4.2.0: {} - imurmurhash@0.1.4: {} + imurmurhash@0.1.4: + optional: true indent-string@4.0.0: {} @@ -9009,7 +9155,9 @@ snapshots: ini@4.1.3: {} - inline-style-parser@0.2.4: {} + ini@6.0.0: {} + + inline-style-parser@0.2.7: {} inline-style-prefixer@7.0.1: dependencies: @@ -9018,13 +9166,8 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 - - into-stream@7.0.0: - dependencies: - from2: 2.3.0 - p-is-promise: 3.0.0 + hasown: 2.0.4 + side-channel: 1.1.1 iron-webcrypto@1.2.1: {} @@ -9041,7 +9184,7 @@ snapshots: is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -9066,9 +9209,9 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: - hasown: 2.0.2 + hasown: 2.0.4 is-data-view@1.0.2: dependencies: @@ -9087,7 +9230,12 @@ snapshots: is-docker@3.0.0: {} - is-extglob@2.1.1: {} + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: + optional: true is-finalizationregistry@1.1.1: dependencies: @@ -9095,11 +9243,9 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@4.0.0: {} - is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.6.0 is-generator-function@1.1.2: dependencies: @@ -9112,21 +9258,14 @@ snapshots: is-glob@4.0.3: dependencies: is-extglob: 2.1.1 + optional: true is-hexadecimal@2.0.1: {} - is-immutable-type@5.0.1(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@typescript-eslint/type-utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - ts-api-utils: 2.1.0(typescript@5.9.3) - ts-declaration-location: 1.0.7(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - is-in-ci@1.0.0: {} + is-in-ssh@1.0.0: {} + is-inside-container@1.0.0: dependencies: is-docker: 3.0.0 @@ -9136,8 +9275,6 @@ snapshots: global-directory: 4.0.1 is-path-inside: 4.0.0 - is-interactive@2.0.0: {} - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -9172,7 +9309,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.4 is-relative@0.1.3: {} @@ -9199,15 +9336,9 @@ snapshots: has-symbols: 1.1.0 safe-regex-test: 1.1.0 - is-text-path@2.0.0: - dependencies: - text-extensions: 2.4.0 - is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.19 - - is-unicode-supported@1.3.0: {} + which-typed-array: 1.1.22 is-unicode-supported@2.1.0: {} @@ -9226,7 +9357,7 @@ snapshots: dependencies: is-docker: 2.2.1 - is-wsl@3.1.0: + is-wsl@3.1.1: dependencies: is-inside-container: 1.0.0 @@ -9240,7 +9371,7 @@ snapshots: isobject@3.0.1: {} - issue-parser@7.0.1: + issue-parser@7.0.2: dependencies: lodash.capitalize: 4.2.1 lodash.escaperegexp: 4.1.2 @@ -9248,27 +9379,26 @@ snapshots: lodash.isstring: 4.0.1 lodash.uniqby: 4.7.0 - jackspeak@4.1.1: - dependencies: - '@isaacs/cliui': 8.0.2 - java-properties@1.0.2: {} jiti@2.6.1: {} - js-cookie@2.2.1: {} + jiti@2.7.0: {} + + js-cookie@3.0.8: {} js-tokens@4.0.0: {} js-tokens@9.0.1: {} - js-yaml@4.1.0: + js-yaml@4.3.0: dependencies: argparse: 2.0.1 jsesc@3.1.0: {} - json-buffer@3.0.1: {} + json-buffer@3.0.1: + optional: true json-parse-better-errors@1.0.2: {} @@ -9276,21 +9406,36 @@ snapshots: json-parse-even-better-errors@3.0.2: {} - json-schema-traverse@0.4.1: {} + json-schema-traverse@0.4.1: + optional: true json-schema-traverse@1.0.0: {} - json-stable-stringify-without-jsonify@1.0.1: {} + json-stable-stringify-without-jsonify@1.0.1: + optional: true + + json-with-bigint@3.5.10: {} json5@2.2.3: {} - jsonfile@6.2.0: + jsonfile@6.2.1: dependencies: universalify: 2.0.1 optionalDependencies: graceful-fs: 4.2.11 - jsonparse@1.3.1: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.8.5 jszip@3.10.1: dependencies: @@ -9299,13 +9444,25 @@ snapshots: readable-stream: 2.3.8 setimmediate: 1.0.5 + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + optional: true kleur@3.0.3: {} - ky@1.11.0: {} + ky@1.14.3: {} latest-version@9.0.0: dependencies: @@ -9315,6 +9472,7 @@ snapshots: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + optional: true lie@3.3.0: dependencies: @@ -9327,90 +9485,83 @@ snapshots: transitivePeerDependencies: - supports-color - lightningcss-darwin-arm64@1.30.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.30.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-freebsd-x64@1.30.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.30.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.30.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.30.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.30.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-musl@1.30.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.30.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.30.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss@1.30.1: + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: dependencies: - detect-libc: 2.1.1 + detect-libc: 2.1.2 optionalDependencies: - lightningcss-darwin-arm64: 1.30.1 - lightningcss-darwin-x64: 1.30.1 - lightningcss-freebsd-x64: 1.30.1 - lightningcss-linux-arm-gnueabihf: 1.30.1 - lightningcss-linux-arm64-gnu: 1.30.1 - lightningcss-linux-arm64-musl: 1.30.1 - lightningcss-linux-x64-gnu: 1.30.1 - lightningcss-linux-x64-musl: 1.30.1 - lightningcss-win32-arm64-msvc: 1.30.1 - lightningcss-win32-x64-msvc: 1.30.1 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 lines-and-columns@1.2.4: {} lines-and-columns@2.0.4: {} - linkedom@0.18.12: + linkedom@0.18.13: dependencies: - css-select: 5.2.2 + css-select: 7.0.0 cssom: 0.5.0 html-escaper: 3.0.3 - htmlparser2: 10.0.0 + htmlparser2: 10.1.0 uhyphen: 0.2.0 - lint-staged@16.2.3: + lint-staged@17.0.8: dependencies: - commander: 14.0.1 - listr2: 9.0.4 - micromatch: 4.0.8 - nano-spawn: 1.0.3 - pidtree: 0.6.0 + listr2: 10.2.2 + picomatch: 4.0.5 string-argv: 0.3.2 - yaml: 2.8.1 - - listr2@8.3.3: - dependencies: - cli-truncate: 4.0.0 - colorette: 2.0.20 - eventemitter3: 5.0.1 - log-update: 6.1.0 - rfdc: 1.4.1 - wrap-ansi: 9.0.2 + tinyexec: 1.2.4 + optionalDependencies: + yaml: 2.9.0 - listr2@9.0.4: + listr2@10.2.2: dependencies: - cli-truncate: 5.1.0 - colorette: 2.0.20 - eventemitter3: 5.0.1 + cli-truncate: 5.2.0 + eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 9.0.2 + wrap-ansi: 10.0.0 load-json-file@4.0.0: dependencies: @@ -9419,10 +9570,10 @@ snapshots: pify: 3.0.0 strip-bom: 3.0.0 - local-pkg@1.1.2: + local-pkg@1.2.1: dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 + mlly: 1.8.2 + pkg-types: 2.3.1 quansync: 0.2.11 locate-path@2.0.0: @@ -9433,57 +9584,40 @@ snapshots: locate-path@6.0.0: dependencies: p-locate: 5.0.0 + optional: true - locate-path@7.2.0: - dependencies: - p-locate: 6.0.0 - - lodash-es@4.17.21: {} - - lodash.camelcase@4.3.0: {} + lodash-es@4.18.1: {} lodash.capitalize@4.2.1: {} lodash.escaperegexp@4.1.2: {} - lodash.isplainobject@4.0.6: {} + lodash.includes@4.3.0: {} - lodash.isstring@4.0.1: {} + lodash.isboolean@3.0.3: {} - lodash.kebabcase@4.1.1: {} + lodash.isinteger@4.0.4: {} - lodash.merge@4.6.2: {} + lodash.isnumber@3.0.3: {} - lodash.mergewith@4.6.2: {} + lodash.isplainobject@4.0.6: {} - lodash.snakecase@4.1.1: {} + lodash.isstring@4.0.1: {} - lodash.startcase@4.4.0: {} + lodash.merge@4.6.2: {} - lodash.uniq@4.5.0: {} + lodash.once@4.1.1: {} lodash.uniqby@4.7.0: {} - lodash.upperfirst@4.3.1: {} - - lodash@4.17.21: {} - - log-symbols@5.1.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 + lodash@4.18.1: {} log-update@6.1.0: dependencies: - ansi-escapes: 7.1.1 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi: 9.0.2 longest-streak@3.1.0: {} @@ -9494,35 +9628,41 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.2.2: {} + lru-cache@11.5.2: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lucide-react@0.544.0(react@19.2.0): + lucide-react@1.25.0(react@19.2.7): dependencies: - react: 19.2.0 + react: 19.2.7 - magic-string@0.30.19: + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.3: dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 source-map-js: 1.2.1 + make-asynchronous@1.1.0: + dependencies: + p-event: 6.0.1 + type-fest: 4.41.0 + web-worker: 1.5.0 + make-error@1.3.6: {} - many-keys-map@2.0.1: {} + many-keys-map@3.0.3: {} markdown-table@3.0.4: {} marked-terminal@7.3.0(marked@15.0.12): dependencies: - ansi-escapes: 7.1.1 + ansi-escapes: 7.3.0 ansi-regex: 6.2.2 chalk: 5.6.2 cli-highlight: 2.1.11 @@ -9541,14 +9681,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 - mdast-util-from-markdown@2.0.2: + mdast-util-from-markdown@2.0.3: dependencies: '@types/mdast': 4.0.4 '@types/unist': 3.0.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 mdast-util-to-string: 4.0.0 micromark: 4.0.2 @@ -9573,7 +9713,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: @@ -9582,7 +9722,7 @@ snapshots: mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -9592,7 +9732,7 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -9601,14 +9741,14 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.1.0: dependencies: - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.1.0 mdast-util-gfm-strikethrough: 2.0.0 @@ -9621,10 +9761,10 @@ snapshots: mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -9632,12 +9772,12 @@ snapshots: mdast-util-mdx-jsx@3.2.0: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 parse-entities: 4.0.2 stringify-entities: 4.0.4 @@ -9649,10 +9789,10 @@ snapshots: mdast-util-mdxjs-esm@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -9665,18 +9805,18 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - mdast-util-to-hast@13.2.0: + mdast-util-to-hast@13.2.1: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.0 + '@ungap/structured-clone': 1.3.3 devlop: 1.1.0 micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 mdast-util-to-markdown@2.1.2: @@ -9688,7 +9828,7 @@ snapshots: mdast-util-to-string: 4.0.0 micromark-util-classify-character: 2.0.1 micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 zwitch: 2.0.4 mdast-util-to-string@4.0.0: @@ -9699,17 +9839,13 @@ snapshots: memorystream@0.3.1: {} - meow@12.1.1: {} - meow@13.2.0: {} merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromark-core-commonmark@2.0.3: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-factory-destination: 2.0.1 micromark-factory-label: 2.0.1 @@ -9842,7 +9978,7 @@ snapshots: micromark-util-decode-string@2.0.1: dependencies: - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 micromark-util-character: 2.1.1 micromark-util-decode-numeric-character-reference: 2.0.2 micromark-util-symbol: 2.0.1 @@ -9878,9 +10014,9 @@ snapshots: micromark@4.0.2: dependencies: - '@types/debug': 4.1.12 + '@types/debug': 4.1.13 debug: 4.4.3 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 micromark-factory-space: 2.0.1 @@ -9901,7 +10037,7 @@ snapshots: micromatch@4.0.8: dependencies: braces: 3.0.3 - picomatch: 2.3.1 + picomatch: 2.3.2 mime@4.1.0: {} @@ -9911,47 +10047,39 @@ snapshots: mimic-function@5.0.1: {} - minimatch@10.0.3: + minimatch@10.2.5: dependencies: - '@isaacs/brace-expansion': 5.0.0 + brace-expansion: 5.0.7 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 + brace-expansion: 1.1.16 minimist@1.2.8: {} - minipass@7.1.2: {} - - minizlib@3.1.0: - dependencies: - minipass: 7.1.2 + minipass@7.1.3: {} - mlly@1.8.0: + mlly@1.8.2: dependencies: - acorn: 8.15.0 + acorn: 8.17.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.4 - motion-dom@12.23.21: + motion-dom@12.42.2: dependencies: - motion-utils: 12.23.6 + motion-utils: 12.39.0 - motion-utils@12.23.6: {} + motion-utils@12.39.0: {} - motion@12.23.22(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + motion@12.42.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - framer-motion: 12.23.22(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + framer-motion: 12.42.2(@emotion/is-prop-valid@1.3.1)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) tslib: 2.8.1 optionalDependencies: '@emotion/is-prop-valid': 1.3.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) ms@2.1.3: {} @@ -9960,7 +10088,7 @@ snapshots: '@types/minimatch': 3.0.5 array-differ: 4.0.0 array-union: 3.0.1 - minimatch: 3.1.2 + minimatch: 3.1.5 mz@2.7.0: dependencies: @@ -9968,28 +10096,33 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 - nano-css@5.6.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + nano-css@5.6.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: '@jridgewell/sourcemap-codec': 1.5.5 css-tree: 1.1.3 - csstype: 3.1.3 + csstype: 3.2.3 fastest-stable-stringify: 2.0.2 inline-style-prefixer: 7.0.1 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) rtl-css-js: 1.16.1 stacktrace-js: 2.0.2 - stylis: 4.3.6 + stylis: 4.4.0 - nano-spawn@1.0.3: {} + nano-spawn@2.1.0: {} - nanoevents@6.0.2: {} + nanoid@3.3.16: {} - nanoid@3.3.11: {} + nanoid@5.1.16: {} - nanoid@5.1.6: {} + nanoid@6.0.0: {} - natural-compare@1.4.0: {} + nanospinner@1.2.2: + dependencies: + picocolors: 1.1.1 + + natural-compare@1.4.0: + optional: true neo-async@2.6.2: {} @@ -10011,41 +10144,45 @@ snapshots: node-fetch-native@1.6.7: {} - node-forge@1.3.1: {} + node-forge@1.4.0: {} node-gyp-build@4.8.4: {} - node-mock-http@1.0.3: {} + node-mock-http@1.0.4: {} node-notifier@10.0.1: dependencies: growly: 1.3.0 is-wsl: 2.2.0 - semver: 7.7.2 + semver: 7.8.5 shellwords: 0.1.1 uuid: 8.3.2 which: 2.0.2 - node-releases@2.0.21: {} + node-releases@2.0.51: {} normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.10 + resolve: 1.22.12 semver: 5.7.2 validate-npm-package-license: 3.0.4 normalize-package-data@6.0.2: dependencies: hosted-git-info: 7.0.2 - semver: 7.7.2 + semver: 7.8.5 validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} + normalize-package-data@8.0.0: + dependencies: + hosted-git-info: 9.0.3 + semver: 7.8.5 + validate-npm-package-license: 3.0.4 - normalize-range@0.1.2: {} + normalize-path@3.0.0: {} - normalize-url@8.1.0: {} + normalize-url@9.0.1: {} npm-run-all@4.1.5: dependencies: @@ -10053,10 +10190,10 @@ snapshots: chalk: 2.4.2 cross-spawn: 6.0.6 memorystream: 0.3.1 - minimatch: 3.1.2 + minimatch: 3.1.5 pidtree: 0.3.1 read-pkg: 3.0.0 - shell-quote: 1.8.3 + shell-quote: 1.10.0 string.prototype.padend: 3.1.6 npm-run-path@4.0.1: @@ -10072,23 +10209,21 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 - npm@10.9.4: {} + npm@11.18.0: {} - nth-check@2.1.1: + nth-check@3.0.1: dependencies: - boolbase: 1.0.0 + boolbase: 2.0.0 - number-flow@0.5.8: + number-flow@0.6.1: dependencies: esm-env: 1.2.2 - nypm@0.6.2: + nypm@0.6.8: dependencies: - citty: 0.1.6 - consola: 3.4.2 + citty: 0.2.2 pathe: 2.0.3 - pkg-types: 2.3.0 - tinyexec: 1.0.1 + tinyexec: 1.2.4 object-assign@4.1.1: {} @@ -10098,27 +10233,25 @@ snapshots: object.assign@4.1.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 - ofetch@1.4.1: + obug@2.1.4: {} + + ofetch@1.5.1: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 - ufo: 1.6.1 + ufo: 1.6.4 ohash@2.0.11: {} on-exit-leak-free@2.1.2: {} - once@1.4.0: - dependencies: - wrappy: 1.0.2 - onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -10131,12 +10264,14 @@ snapshots: dependencies: mimic-function: 5.0.1 - open@10.2.0: + open@11.0.0: dependencies: - default-browser: 5.2.1 + default-browser: 5.5.0 define-lazy-prop: 3.0.0 + is-in-ssh: 1.0.0 is-inside-container: 1.0.0 - wsl-utils: 0.1.0 + powershell-utils: 0.1.0 + wsl-utils: 0.3.1 open@8.4.2: dependencies: @@ -10152,30 +10287,7 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 word-wrap: 1.2.5 - - ora@6.3.1: - dependencies: - chalk: 5.6.2 - cli-cursor: 4.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 1.3.0 - log-symbols: 5.1.0 - stdin-discarder: 0.1.0 - strip-ansi: 7.1.2 - wcwidth: 1.0.1 - - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.2 + optional: true os-shim@0.1.3: {} @@ -10185,13 +10297,61 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + oxfmt@0.57.0: + dependencies: + tinypool: 2.1.0 + optionalDependencies: + '@oxfmt/binding-android-arm-eabi': 0.57.0 + '@oxfmt/binding-android-arm64': 0.57.0 + '@oxfmt/binding-darwin-arm64': 0.57.0 + '@oxfmt/binding-darwin-x64': 0.57.0 + '@oxfmt/binding-freebsd-x64': 0.57.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 + '@oxfmt/binding-linux-arm64-gnu': 0.57.0 + '@oxfmt/binding-linux-arm64-musl': 0.57.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 + '@oxfmt/binding-linux-riscv64-musl': 0.57.0 + '@oxfmt/binding-linux-s390x-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-gnu': 0.57.0 + '@oxfmt/binding-linux-x64-musl': 0.57.0 + '@oxfmt/binding-openharmony-arm64': 0.57.0 + '@oxfmt/binding-win32-arm64-msvc': 0.57.0 + '@oxfmt/binding-win32-ia32-msvc': 0.57.0 + '@oxfmt/binding-win32-x64-msvc': 0.57.0 + + oxlint@1.74.0: + optionalDependencies: + '@oxlint/binding-android-arm-eabi': 1.74.0 + '@oxlint/binding-android-arm64': 1.74.0 + '@oxlint/binding-darwin-arm64': 1.74.0 + '@oxlint/binding-darwin-x64': 1.74.0 + '@oxlint/binding-freebsd-x64': 1.74.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.74.0 + '@oxlint/binding-linux-arm-musleabihf': 1.74.0 + '@oxlint/binding-linux-arm64-gnu': 1.74.0 + '@oxlint/binding-linux-arm64-musl': 1.74.0 + '@oxlint/binding-linux-ppc64-gnu': 1.74.0 + '@oxlint/binding-linux-riscv64-gnu': 1.74.0 + '@oxlint/binding-linux-riscv64-musl': 1.74.0 + '@oxlint/binding-linux-s390x-gnu': 1.74.0 + '@oxlint/binding-linux-x64-gnu': 1.74.0 + '@oxlint/binding-linux-x64-musl': 1.74.0 + '@oxlint/binding-openharmony-arm64': 1.74.0 + '@oxlint/binding-win32-arm64-msvc': 1.74.0 + '@oxlint/binding-win32-ia32-msvc': 1.74.0 + '@oxlint/binding-win32-x64-msvc': 1.74.0 + p-each-series@3.0.0: {} - p-filter@4.1.0: + p-event@6.0.1: dependencies: - p-map: 7.0.3 + p-timeout: 6.1.4 - p-is-promise@3.0.0: {} + p-filter@4.1.0: + dependencies: + p-map: 7.0.5 p-limit@1.3.0: dependencies: @@ -10200,10 +10360,7 @@ snapshots: p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 - - p-limit@4.0.0: - dependencies: - yocto-queue: 1.2.1 + optional: true p-locate@2.0.0: dependencies: @@ -10212,27 +10369,26 @@ snapshots: p-locate@5.0.0: dependencies: p-limit: 3.1.0 + optional: true - p-locate@6.0.0: - dependencies: - p-limit: 4.0.0 - - p-map@7.0.3: {} + p-map@7.0.5: {} p-reduce@2.1.0: {} p-reduce@3.0.0: {} + p-timeout@6.1.4: {} + p-try@1.0.0: {} package-json-from-dist@1.0.1: {} package-json@10.0.1: dependencies: - ky: 1.11.0 - registry-auth-token: 5.1.0 + ky: 1.14.3 + registry-auth-token: 5.1.1 registry-url: 6.0.1 - semver: 7.7.2 + semver: 7.8.5 pako@1.0.11: {} @@ -10245,7 +10401,7 @@ snapshots: '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.2.0 + decode-named-character-reference: 1.3.0 is-alphanumerical: 2.0.1 is-decimal: 2.0.1 is-hexadecimal: 2.0.1 @@ -10257,14 +10413,14 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse-json@7.1.1: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.7 error-ex: 1.3.4 json-parse-even-better-errors: 3.0.2 lines-and-columns: 2.0.4 @@ -10272,7 +10428,7 @@ snapshots: parse-json@8.3.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.7 index-to-position: 1.2.0 type-fest: 4.41.0 @@ -10288,9 +10444,8 @@ snapshots: path-exists@3.0.0: {} - path-exists@4.0.0: {} - - path-exists@5.0.0: {} + path-exists@4.0.0: + optional: true path-key@2.0.1: {} @@ -10300,10 +10455,10 @@ snapshots: path-parse@1.0.7: {} - path-scurry@2.0.0: + path-scurry@2.0.2: dependencies: - lru-cache: 11.2.2 - minipass: 7.1.2 + lru-cache: 11.5.2 + minipass: 7.1.3 path-type@3.0.0: dependencies: @@ -10313,29 +10468,23 @@ snapshots: pathe@2.0.3: {} - pend@1.2.0: {} - - perfect-debounce@2.0.0: {} - - phenomenon@1.6.0: {} + perfect-debounce@2.1.0: {} picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@2.3.2: {} - picomatch@4.0.3: {} + picomatch@4.0.5: {} pidtree@0.3.1: {} - pidtree@0.6.0: {} - pify@3.0.0: {} pino-abstract-transport@2.0.0: dependencies: split2: 4.2.0 - pino-std-serializers@7.0.0: {} + pino-std-serializers@7.1.0: {} pino@9.7.0: dependencies: @@ -10343,13 +10492,13 @@ snapshots: fast-redact: 3.5.0 on-exit-leak-free: 2.1.2 pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.0.0 + pino-std-serializers: 7.1.0 process-warning: 5.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.0 - thread-stream: 3.1.0 + sonic-boom: 4.2.1 + thread-stream: 3.2.0 pkg-conf@2.1.0: dependencies: @@ -10359,18 +10508,26 @@ snapshots: pkg-types@1.3.1: dependencies: confbox: 0.1.8 - mlly: 1.8.0 + mlly: 1.8.2 pathe: 2.0.3 - pkg-types@2.3.0: + pkg-types@2.3.1: dependencies: - confbox: 0.2.2 - exsolve: 1.0.7 + confbox: 0.2.4 + exsolve: 1.1.0 pathe: 2.0.3 possible-typed-array-names@1.1.0: {} - postcss-rem-to-responsive-pixel@6.1.0: {} + postcss-plugin-shared@1.1.5(postcss@8.5.19): + dependencies: + defu: 6.1.7 + postcss: 8.5.19 + + postcss-rem-to-responsive-pixel@7.0.4(postcss@8.5.19): + dependencies: + postcss: 8.5.19 + postcss-plugin-shared: 1.1.5(postcss@8.5.19) postcss-selector-parser@6.0.10: dependencies: @@ -10379,19 +10536,16 @@ snapshots: postcss-value-parser@4.2.0: {} - postcss@8.5.6: + postcss@8.5.19: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.16 picocolors: 1.1.1 source-map-js: 1.2.1 - prelude-ls@1.2.1: {} - - prettier-linter-helpers@1.0.0: - dependencies: - fast-diff: 1.3.0 + powershell-utils@0.1.0: {} - prettier@3.6.2: {} + prelude-ls@1.2.1: + optional: true pretty-ms@9.3.0: dependencies: @@ -10410,36 +10564,26 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - property-information@7.1.0: {} + property-information@7.2.0: {} proto-list@1.2.4: {} - publish-browser-extension@3.0.2: + proxy-agent-negotiate@1.1.0: {} + + publish-browser-extension@4.0.5: dependencies: cac: 6.7.14 - cli-highlight: 2.1.11 consola: 3.4.2 - dotenv: 16.6.1 - extract-zip: 2.0.1 + dotenv: 17.4.2 + form-data-encoder: 4.1.0 formdata-node: 6.0.3 - listr2: 8.3.3 - lodash.camelcase: 4.3.0 - lodash.kebabcase: 4.1.1 - lodash.snakecase: 4.1.1 - ofetch: 1.4.1 - open: 10.2.0 - ora: 6.3.1 - prompts: 2.4.2 - zod: 3.25.76 - transitivePeerDependencies: - - supports-color + jsonwebtoken: 9.0.3 + listr2: 10.2.2 + ofetch: 1.5.1 + zod: 4.4.3 - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - punycode@2.3.1: {} + punycode@2.3.1: + optional: true pupa@3.3.0: dependencies: @@ -10447,15 +10591,13 @@ snapshots: quansync@0.2.11: {} - queue-microtask@1.2.3: {} - quick-format-unescaped@4.0.4: {} radix3@1.1.2: {} - rc9@2.1.2: + rc9@3.0.1: dependencies: - defu: 6.1.4 + defu: 6.1.7 destr: 2.0.5 rc@1.2.8: @@ -10465,79 +10607,77 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dom@19.2.0(react@19.2.0): + react-dom@19.2.7(react@19.2.7): dependencies: - react: 19.2.0 + react: 19.2.7 scheduler: 0.27.0 - react-hook-form@7.63.0(react@19.2.0): + react-hook-form@7.81.0(react@19.2.7): dependencies: - react: 19.2.0 + react: 19.2.7 - react-markdown@10.1.0(@types/react@19.2.0)(react@19.2.0): + react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - '@types/react': 19.2.0 + '@types/react': 19.2.17 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.0 - react: 19.2.0 + mdast-util-to-hast: 13.2.1 + react: 19.2.7 remark-parse: 11.0.0 remark-rehype: 11.1.2 unified: 11.0.5 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 vfile: 6.0.3 transitivePeerDependencies: - supports-color - react-refresh@0.17.0: {} - - react-remove-scroll-bar@2.3.8(@types/react@19.2.0)(react@19.2.0): + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): dependencies: - react: 19.2.0 - react-style-singleton: 2.2.3(@types/react@19.2.0)(react@19.2.0) + react: 19.2.7 + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - react-remove-scroll@2.7.1(@types/react@19.2.0)(react@19.2.0): + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): dependencies: - react: 19.2.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.0)(react@19.2.0) - react-style-singleton: 2.2.3(@types/react@19.2.0)(react@19.2.0) + react: 19.2.7 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.0)(react@19.2.0) - use-sidecar: 1.1.3(@types/react@19.2.0)(react@19.2.0) + use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7) + use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7) optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - react-style-singleton@2.2.3(@types/react@19.2.0)(react@19.2.0): + react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): dependencies: get-nonce: 1.0.1 - react: 19.2.0 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - react-universal-interface@0.6.2(react@19.2.0)(tslib@2.8.1): + react-universal-interface@0.6.2(react@19.2.7)(tslib@2.8.1): dependencies: - react: 19.2.0 + react: 19.2.7 tslib: 2.8.1 - react-use@17.6.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-use@17.6.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - '@types/js-cookie': 2.2.7 + '@types/js-cookie': 3.0.6 '@xobotyi/scrollbar-width': 1.9.5 copy-to-clipboard: 3.3.3 fast-deep-equal: 3.1.3 fast-shallow-equal: 1.0.0 - js-cookie: 2.2.1 - nano-css: 5.6.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - react-universal-interface: 0.6.2(react@19.2.0)(tslib@2.8.1) + js-cookie: 3.0.8 + nano-css: 5.6.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) + react-universal-interface: 0.6.2(react@19.2.7)(tslib@2.8.1) resize-observer-polyfill: 1.5.1 screenfull: 5.2.0 set-harmonic-interval: 1.0.1 @@ -10545,12 +10685,12 @@ snapshots: ts-easing: 0.2.0 tslib: 2.8.1 - react-virtuoso@4.14.1(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-virtuoso@4.18.11(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) - react@19.2.0: {} + react@19.2.7: {} read-package-up@11.0.0: dependencies: @@ -10558,6 +10698,20 @@ snapshots: read-pkg: 9.0.1 type-fest: 4.41.0 + read-package-up@12.0.0: + dependencies: + find-up-simple: 1.0.1 + read-pkg: 10.1.0 + type-fest: 5.8.0 + + read-pkg@10.1.0: + dependencies: + '@types/normalize-package-data': 2.4.4 + normalize-package-data: 8.0.0 + parse-json: 8.3.0 + type-fest: 5.8.0 + unicorn-magic: 0.4.0 + read-pkg@3.0.0: dependencies: load-json-file: 4.0.0 @@ -10582,39 +10736,33 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@4.1.2: {} + readdirp@5.0.0: {} real-require@0.2.0: {} reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.0 + es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 gopd: 1.2.0 set-function-name: 2.0.2 - registry-auth-token@5.1.0: + registry-auth-token@5.1.1: dependencies: - '@pnpm/npm-conf': 2.3.1 + '@pnpm/npm-conf': 3.0.3 registry-url@6.0.1: dependencies: @@ -10640,7 +10788,7 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.2 + mdast-util-from-markdown: 2.0.3 micromark-util-types: 2.0.2 unified: 11.0.5 transitivePeerDependencies: @@ -10648,9 +10796,9 @@ snapshots: remark-rehype@11.1.2: dependencies: - '@types/hast': 3.0.4 + '@types/hast': 3.0.5 '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 unified: 11.0.5 vfile: 6.0.3 @@ -10671,14 +10819,14 @@ snapshots: remesh-debugger-helper: 4.1.0(remesh@4.2.2(rxjs@7.8.2)) tslib: 2.8.1 - remesh-react@4.1.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(remesh@4.2.2(rxjs@7.8.2))(rxjs@7.8.2): + remesh-react@4.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(remesh@4.2.2(rxjs@7.8.2))(rxjs@7.8.2): dependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) remesh: 4.2.2(rxjs@7.8.2) rxjs: 7.8.2 tslib: 2.8.1 - use-sync-external-store: 1.6.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.7) remesh@4.2.2(rxjs@7.8.2): dependencies: @@ -10697,31 +10845,46 @@ snapshots: resolve-from@5.0.0: {} - resolve@1.22.10: + resolve@1.22.12: dependencies: - is-core-module: 2.16.1 + es-errors: 1.3.0 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - restore-cursor@5.1.0: dependencies: onetime: 7.0.0 signal-exit: 4.1.0 - reusify@1.1.0: {} - rfdc@1.4.1: {} - rimraf@6.0.1: + rimraf@6.1.3: dependencies: - glob: 11.0.3 + glob: 13.0.6 package-json-from-dist: 1.0.1 + rolldown@1.1.5: + dependencies: + '@oxc-project/types': 0.139.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.1.5 + '@rolldown/binding-darwin-arm64': 1.1.5 + '@rolldown/binding-darwin-x64': 1.1.5 + '@rolldown/binding-freebsd-x64': 1.1.5 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.5 + '@rolldown/binding-linux-arm64-gnu': 1.1.5 + '@rolldown/binding-linux-arm64-musl': 1.1.5 + '@rolldown/binding-linux-ppc64-gnu': 1.1.5 + '@rolldown/binding-linux-s390x-gnu': 1.1.5 + '@rolldown/binding-linux-x64-gnu': 1.1.5 + '@rolldown/binding-linux-x64-musl': 1.1.5 + '@rolldown/binding-openharmony-arm64': 1.1.5 + '@rolldown/binding-wasm32-wasi': 1.1.5 + '@rolldown/binding-win32-arm64-msvc': 1.1.5 + '@rolldown/binding-win32-x64-msvc': 1.1.5 + rollup@4.52.3: dependencies: '@types/estree': 1.0.8 @@ -10749,24 +10912,21 @@ snapshots: '@rollup/rollup-win32-x64-gnu': 4.52.3 '@rollup/rollup-win32-x64-msvc': 4.52.3 fsevents: 2.3.3 + optional: true rtl-css-js@1.16.1: dependencies: - '@babel/runtime': 7.28.4 + '@babel/runtime': 7.29.7 run-applescript@7.1.0: {} - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - rxjs@7.8.2: dependencies: tslib: 2.8.1 - safe-array-concat@1.1.3: + safe-array-concat@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 has-symbols: 1.1.0 @@ -10789,7 +10949,7 @@ snapshots: safe-stable-stringify@2.5.0: {} - sax@1.4.1: {} + sax@1.6.0: {} scheduler@0.27.0: {} @@ -10797,60 +10957,48 @@ snapshots: scule@1.3.0: {} - semantic-release@24.2.9(typescript@5.9.3): + semantic-release@25.0.7(typescript@7.0.2): dependencies: - '@semantic-release/commit-analyzer': 13.0.1(semantic-release@24.2.9(typescript@5.9.3)) + '@semantic-release/commit-analyzer': 13.0.1(semantic-release@25.0.7(typescript@7.0.2)) '@semantic-release/error': 4.0.0 - '@semantic-release/github': 11.0.6(semantic-release@24.2.9(typescript@5.9.3)) - '@semantic-release/npm': 12.0.2(semantic-release@24.2.9(typescript@5.9.3)) - '@semantic-release/release-notes-generator': 14.1.0(semantic-release@24.2.9(typescript@5.9.3)) + '@semantic-release/github': 12.0.9(semantic-release@25.0.7(typescript@7.0.2)) + '@semantic-release/npm': 13.1.5(semantic-release@25.0.7(typescript@7.0.2)) + '@semantic-release/release-notes-generator': 14.1.1(semantic-release@25.0.7(typescript@7.0.2)) aggregate-error: 5.0.0 - cosmiconfig: 9.0.0(typescript@5.9.3) + cosmiconfig: 9.0.2(typescript@7.0.2) debug: 4.4.3 env-ci: 11.2.0 - execa: 9.6.0 + execa: 9.6.1 figures: 6.1.0 find-versions: 6.0.0 get-stream: 6.0.1 git-log-parser: 1.2.1 hook-std: 4.0.0 - hosted-git-info: 8.1.0 + hosted-git-info: 9.0.3 import-from-esm: 2.0.0 - lodash-es: 4.17.21 + lodash-es: 4.18.1 marked: 15.0.12 marked-terminal: 7.3.0(marked@15.0.12) micromatch: 4.0.8 p-each-series: 3.0.0 p-reduce: 3.0.0 - read-package-up: 11.0.0 + read-package-up: 12.0.0 resolve-from: 5.0.0 - semver: 7.7.2 - semver-diff: 5.0.0 + semver: 7.8.5 signale: 1.4.0 - yargs: 17.7.2 + yargs: 18.0.0 transitivePeerDependencies: + - kerberos - supports-color - typescript - semver-diff@5.0.0: - dependencies: - semver: 7.7.2 - semver-regex@4.0.5: {} semver@5.7.2: {} semver@6.3.1: {} - semver@7.7.2: {} - - serialize-error@11.0.3: - dependencies: - type-fest: 2.19.0 - - serialize-error@9.1.1: - dependencies: - type-fest: 2.19.0 + semver@7.8.5: {} set-function-length@1.2.2: dependencies: @@ -10874,7 +11022,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 set-value@4.1.0: dependencies: @@ -10897,13 +11045,13 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.7.3: {} + shell-quote@1.10.0: {} - shell-quote@1.8.3: {} + shell-quote@1.7.3: {} shellwords@0.1.1: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -10923,14 +11071,16 @@ snapshots: object-inspect: 1.13.4 side-channel-map: 1.0.1 - side-channel@1.1.0: + side-channel@1.1.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -10947,12 +11097,12 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 - slice-ansi@5.0.0: + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 - is-fullwidth-code-point: 4.0.0 + is-fullwidth-code-point: 5.1.0 - slice-ansi@7.1.2: + slice-ansi@8.0.0: dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 @@ -10964,32 +11114,32 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 - socket.io-client@4.8.1(bufferutil@4.0.9)(utf-8-validate@6.0.5): + socket.io-client@4.8.3(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 - engine.io-client: 6.6.3(bufferutil@4.0.9)(utf-8-validate@6.0.5) - socket.io-parser: 4.2.4 + debug: 4.4.3 + engine.io-client: 6.6.6(bufferutil@4.1.0)(utf-8-validate@6.0.6) + socket.io-parser: 4.2.7 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - socket.io-parser@4.2.4: + socket.io-parser@4.2.7: dependencies: '@socket.io/component-emitter': 3.1.2 - debug: 4.3.7 + debug: 4.4.3 transitivePeerDependencies: - supports-color - sonic-boom@4.2.0: + sonic-boom@4.2.1: dependencies: atomic-sleep: 1.0.0 - sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + sonner@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): dependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 19.2.7 + react-dom: 19.2.7(react@19.2.7) source-map-js@1.2.1: {} @@ -11016,16 +11166,16 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 + spdx-license-ids: 3.0.23 - spdx-license-ids@3.0.22: {} + spdx-license-ids@3.0.23: {} split2@1.0.0: dependencies: @@ -11041,6 +11191,8 @@ snapshots: dependencies: stackframe: 1.3.4 + stackback@0.0.2: {} + stackframe@1.3.4: {} stacktrace-gps@3.1.2: @@ -11054,11 +11206,7 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 - stdin-discarder@0.1.0: - dependencies: - bl: 5.1.0 - - stdin-discarder@0.2.2: {} + std-env@4.2.0: {} stop-iteration-iterator@1.1.0: dependencies: @@ -11072,69 +11220,58 @@ snapshots: string-argv@0.3.2: {} - string-ts@2.2.1: {} - string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - string-width@7.2.0: dependencies: - emoji-regex: 10.5.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 - string-width@8.1.0: + string-width@8.2.2: dependencies: - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 string.prototype.padend@3.1.6: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 - string.prototype.trim@1.2.10: + string.prototype.trim@1.2.11: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.0 - es-object-atoms: 1.1.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 - string.prototype.trimend@1.0.9: + string.prototype.trimend@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 @@ -11144,7 +11281,7 @@ snapshots: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -11160,29 +11297,32 @@ snapshots: strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} - strip-json-comments@5.0.2: {} strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 - stubborn-fs@1.2.5: {} + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} - style-to-js@1.1.17: + style-to-js@1.1.21: dependencies: - style-to-object: 1.0.9 + style-to-object: 1.0.14 - style-to-object@1.0.9: + style-to-object@1.0.14: dependencies: - inline-style-parser: 0.2.4 + inline-style-parser: 0.2.7 - stylis@4.3.6: {} + stylis@4.4.0: {} - super-regex@1.0.0: + super-regex@1.1.0: dependencies: function-timeout: 1.0.2 + make-asynchronous: 1.1.0 time-span: 5.1.0 supports-color@5.5.0: @@ -11202,37 +11342,23 @@ snapshots: svg-parser@2.0.4: {} - synckit@0.11.11: - dependencies: - '@pkgr/core': 0.2.9 - tagged-tag@1.0.0: {} - tailwind-merge@3.3.1: {} - - tailwindcss@4.1.14: {} + tailwind-merge@3.6.0: {} - tapable@2.3.0: {} + tailwindcss@4.3.3: {} - tar@7.5.1: - dependencies: - '@isaacs/fs-minipass': 4.0.1 - chownr: 3.0.0 - minipass: 7.1.2 - minizlib: 3.1.0 - yallist: 5.0.0 + tapable@2.3.3: {} temp-dir@3.0.0: {} - tempy@3.1.0: + tempy@3.2.0: dependencies: is-stream: 3.0.0 temp-dir: 3.0.0 type-fest: 2.19.0 unique-string: 3.0.0 - text-extensions@2.4.0: {} - thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -11241,7 +11367,7 @@ snapshots: dependencies: any-promise: 1.3.0 - thread-stream@3.1.0: + thread-stream@3.2.0: dependencies: real-require: 0.2.0 @@ -11258,14 +11384,18 @@ snapshots: dependencies: convert-hrtime: 5.0.0 - tiny-uid@1.1.2: {} + tinybench@2.9.0: {} - tinyexec@1.0.1: {} + tinyexec@1.2.4: {} - tinyglobby@0.2.15: + tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + tinypool@2.1.0: {} + + tinyrainbow@3.1.0: {} tmp@0.2.5: {} @@ -11281,26 +11411,18 @@ snapshots: trough@2.2.0: {} - ts-api-utils@2.1.0(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - - ts-declaration-location@1.0.7(typescript@5.9.3): - dependencies: - picomatch: 4.0.3 - typescript: 5.9.3 - ts-easing@0.2.0: {} - ts-pattern@5.8.0: {} - tslib@2.8.1: {} + tunnel@0.0.6: {} + tw-animate-css@1.4.0: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + optional: true type-fest@1.4.0: {} @@ -11310,7 +11432,7 @@ snapshots: type-fest@4.41.0: {} - type-fest@5.0.1: + type-fest@5.8.0: dependencies: tagged-tag: 1.0.0 @@ -11322,7 +11444,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -11331,16 +11453,16 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.7: + typed-array-length@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 @@ -11349,30 +11471,36 @@ snapshots: typedarray@0.0.6: {} - typescript-eslint@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.45.0(@typescript-eslint/parser@8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/parser': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.45.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.45.0(eslint@9.36.0(jiti@2.6.1))(typescript@5.9.3) - eslint: 9.36.0(jiti@2.6.1) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - typescript@5.9.3: {} - - ufo@1.6.1: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + ufo@1.6.4: {} uglify-js@3.19.3: optional: true uhyphen@0.2.0: {} - uid@2.0.2: - dependencies: - '@lukeed/csprng': 1.1.0 - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -11382,7 +11510,11 @@ snapshots: uncrypto@0.1.3: {} - undici-types@7.13.0: {} + undici-types@8.3.0: {} + + undici@6.27.0: {} + + undici@7.28.0: {} unicode-emoji-modifier-base@1.0.0: {} @@ -11390,6 +11522,8 @@ snapshots: unicorn-magic@0.3.0: {} + unicorn-magic@0.4.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -11400,28 +11534,39 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unimport@5.4.1: + unimport@6.3.0(esbuild@0.27.7)(rolldown@1.1.5)(rollup@4.52.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: - acorn: 8.15.0 + acorn: 8.17.0 escape-string-regexp: 5.0.0 estree-walker: 3.0.3 - local-pkg: 1.1.2 - magic-string: 0.30.19 - mlly: 1.8.0 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 pathe: 2.0.3 - picomatch: 4.0.3 - pkg-types: 2.3.0 + picomatch: 4.0.5 + pkg-types: 2.3.1 scule: 1.3.0 strip-literal: 3.1.0 - tinyglobby: 0.2.15 - unplugin: 2.3.10 - unplugin-utils: 0.3.0 + tinyglobby: 0.2.17 + unplugin: 3.3.0(esbuild@0.27.7)(rolldown@1.1.5)(rollup@4.52.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + unplugin-utils: 0.3.2 + optionalDependencies: + rolldown: 1.1.5 + transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' + - bun-types-no-globals + - esbuild + - rollup + - unloader + - vite + - webpack unique-string@3.0.0: dependencies: crypto-random-string: 4.0.0 - unist-util-is@6.0.0: + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -11433,49 +11578,53 @@ snapshots: dependencies: '@types/unist': 3.0.3 - unist-util-visit-parents@6.0.1: + unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - unist-util-visit@5.0.0: + unist-util-visit@5.1.0: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 universal-user-agent@7.0.3: {} universalify@2.0.1: {} - unplugin-utils@0.3.0: + unplugin-utils@0.3.2: dependencies: pathe: 2.0.3 - picomatch: 4.0.3 + picomatch: 4.0.5 - unplugin@2.3.10: + unplugin@3.3.0(esbuild@0.27.7)(rolldown@1.1.5)(rollup@4.52.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: '@jridgewell/remapping': 2.3.5 - acorn: 8.15.0 - picomatch: 4.0.3 + picomatch: 4.0.5 webpack-virtual-modules: 0.6.2 + optionalDependencies: + esbuild: 0.27.7 + rolldown: 1.1.5 + rollup: 4.52.3 + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) - unstorage@1.17.1(idb-keyval@6.2.2): + unstorage@1.17.5(idb-keyval@6.3.0): dependencies: anymatch: 3.1.3 - chokidar: 4.0.3 + chokidar: 5.0.0 destr: 2.0.5 - h3: 1.15.4 - lru-cache: 10.4.3 + h3: 1.15.11 + lru-cache: 11.5.2 node-fetch-native: 1.6.7 - ofetch: 1.4.1 - ufo: 1.6.1 + ofetch: 1.5.1 + ufo: 1.6.4 optionalDependencies: - idb-keyval: 6.2.2 + idb-keyval: 6.3.0 - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.2.3(browserslist@4.28.6): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.6 escalade: 3.2.0 picocolors: 1.1.1 @@ -11489,35 +11638,36 @@ snapshots: is-npm: 6.1.0 latest-version: 9.0.0 pupa: 3.3.0 - semver: 7.7.2 + semver: 7.8.5 xdg-basedir: 5.1.0 uri-js@4.4.1: dependencies: punycode: 2.3.1 + optional: true url-join@5.0.0: {} - use-callback-ref@1.3.3(@types/react@19.2.0)(react@19.2.0): + use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): dependencies: - react: 19.2.0 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - use-sidecar@1.1.3(@types/react@19.2.0)(react@19.2.0): + use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): dependencies: detect-node-es: 1.1.0 - react: 19.2.0 + react: 19.2.7 tslib: 2.8.1 optionalDependencies: - '@types/react': 19.2.0 + '@types/react': 19.2.17 - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@19.2.7): dependencies: - react: 19.2.0 + react: 19.2.7 - utf-8-validate@6.0.5: + utf-8-validate@6.0.6: dependencies: node-gyp-build: 4.8.4 @@ -11525,9 +11675,9 @@ snapshots: uuid@8.3.2: {} - valibot@1.1.0(typescript@5.9.3): + valibot@1.4.2(typescript@7.0.2): optionalDependencies: - typescript: 5.9.3 + typescript: 7.0.2 validate-npm-package-license@3.0.4: dependencies: @@ -11544,62 +11694,84 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-node@3.2.4(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1): + vite-node@6.0.0(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 + cac: 7.0.0 + es-module-lexer: 2.3.1 + obug: 2.1.4 pathe: 2.0.3 - vite: 7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' + - '@vitejs/devtools' + - esbuild - jiti - less - - lightningcss - sass - sass-embedded - stylus - sugarss - - supports-color - terser - tsx - yaml - vite-plugin-svgr@4.5.0(rollup@4.52.3)(typescript@5.9.3)(vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1)): + vite-plugin-svgr@5.2.0(rollup@4.52.3)(typescript@7.0.2)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.3) - '@svgr/core': 8.1.0(typescript@5.9.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.9.3)) - vite: 7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + '@rollup/pluginutils': 5.4.0(rollup@4.52.3) + '@svgr/core': 8.1.0(typescript@7.0.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@7.0.2)) + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) transitivePeerDependencies: - rollup - supports-color - typescript - vite@7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1): + vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0): dependencies: - esbuild: 0.25.10 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.52.3 - tinyglobby: 0.2.15 + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.19 + rolldown: 1.1.5 + tinyglobby: 0.2.17 optionalDependencies: - '@types/node': 24.6.2 + '@types/node': 26.1.1 + esbuild: 0.27.7 fsevents: 2.3.3 - jiti: 2.6.1 - lightningcss: 1.30.1 - yaml: 2.8.1 + jiti: 2.7.0 + yaml: 2.9.0 + + vitest@4.1.10(@types/node@26.1.1)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.10 + '@vitest/mocker': 4.1.10(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.10 + '@vitest/runner': 4.1.10 + '@vitest/snapshot': 4.1.10 + '@vitest/spy': 4.1.10 + '@vitest/utils': 4.1.10 + es-module-lexer: 2.3.1 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.5 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.2.4 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.0 + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 26.1.1 + transitivePeerDependencies: + - msw watchpack@2.4.4: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - web-ext-run@0.2.4: dependencies: '@babel/runtime': 7.28.2 @@ -11625,23 +11797,11 @@ snapshots: transitivePeerDependencies: - supports-color - webext-bridge@6.0.1: - dependencies: - '@types/webextension-polyfill': 0.8.3 - nanoevents: 6.0.2 - serialize-error: 9.1.1 - tiny-uid: 1.1.2 - webextension-polyfill: 0.9.0 - - webextension-polyfill@0.10.0: {} - - webextension-polyfill@0.12.0: {} - - webextension-polyfill@0.9.0: {} + web-worker@1.5.0: {} webpack-virtual-modules@0.6.2: {} - when-exit@2.1.4: {} + when-exit@2.1.5: {} when@3.7.7: {} @@ -11656,7 +11816,7 @@ snapshots: which-builtin-type@1.2.1: dependencies: call-bound: 1.0.4 - function.prototype.name: 1.1.8 + function.prototype.name: 1.2.0 has-tostringtag: 1.0.2 is-async-function: 2.1.1 is-date-object: 1.1.0 @@ -11667,7 +11827,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.19 + which-typed-array: 1.1.22 which-collection@1.0.2: dependencies: @@ -11676,10 +11836,10 @@ snapshots: is-weakmap: 2.0.2 is-weakset: 2.0.4 - which-typed-array@1.1.19: + which-typed-array@1.1.22: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -11699,98 +11859,107 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + widest-line@5.0.0: dependencies: string-width: 7.2.0 winreg@0.0.12: {} - word-wrap@1.2.5: {} + word-wrap@1.2.5: + optional: true wordwrap@1.0.0: {} + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.2 + strip-ansi: 7.2.0 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 - wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.2 - - wrappy@1.0.2: {} + strip-ansi: 7.2.0 - ws@8.17.1(bufferutil@4.0.9)(utf-8-validate@6.0.5): + ws@8.21.1(bufferutil@4.1.0)(utf-8-validate@6.0.6): optionalDependencies: - bufferutil: 4.0.9 - utf-8-validate: 6.0.5 + bufferutil: 4.1.0 + utf-8-validate: 6.0.6 - wsl-utils@0.1.0: + wsl-utils@0.3.1: dependencies: - is-wsl: 3.1.0 + is-wsl: 3.1.1 + powershell-utils: 0.1.0 - wxt@0.20.11(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(rollup@4.52.3)(yaml@2.8.1): + wxt@0.20.27(@types/node@26.1.1)(eslint@10.7.0(jiti@2.7.0))(jiti@2.7.0)(rolldown@1.1.5)(rollup@4.52.3)(yaml@2.9.0): dependencies: - '@1natsu/wait-element': 4.1.2 + '@1natsu/wait-element': 4.2.0 '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.52.3) - '@webext-core/fake-browser': 1.3.2 - '@webext-core/isolated-element': 1.1.2 - '@webext-core/match-patterns': 1.0.3 - '@wxt-dev/browser': 0.1.4 - '@wxt-dev/storage': 1.2.0 + '@webext-core/fake-browser': 1.5.2 + '@webext-core/isolated-element': 1.1.5 + '@webext-core/match-patterns': 1.1.0 + '@wxt-dev/browser': 0.2.2 + '@wxt-dev/storage': 1.2.8 async-mutex: 0.5.0 - c12: 3.3.0(magicast@0.3.5) - cac: 6.7.14 - chokidar: 4.0.3 - ci-info: 4.3.0 + c12: 3.3.4(magicast@0.5.3) + cac: 7.0.0 + chokidar: 5.0.0 + ci-info: 4.4.0 consola: 3.4.2 - defu: 6.1.4 - dotenv: 17.2.3 + defu: 6.1.7 dotenv-expand: 12.0.3 - esbuild: 0.25.10 - fast-glob: 3.3.3 - filesize: 11.0.13 - fs-extra: 11.3.2 + esbuild: 0.27.7 + filesize: 11.0.22 get-port-please: 3.2.0 - giget: 2.0.0 - hookable: 5.5.3 + giget: 3.3.0 + hookable: 6.1.1 import-meta-resolve: 4.2.0 - is-wsl: 3.1.0 + is-wsl: 3.1.1 json5: 2.2.3 jszip: 3.10.1 - linkedom: 0.18.12 - magicast: 0.3.5 - minimatch: 10.0.3 - nano-spawn: 1.0.3 + linkedom: 0.18.13 + magicast: 0.5.3 + nano-spawn: 2.1.0 + nanospinner: 1.2.2 normalize-path: 3.0.0 - nypm: 0.6.2 + nypm: 0.6.8 ohash: 2.0.11 - open: 10.2.0 - ora: 8.2.0 - perfect-debounce: 2.0.0 - picocolors: 1.1.1 + open: 11.0.0 + perfect-debounce: 2.1.0 + picomatch: 4.0.5 prompts: 2.4.2 - publish-browser-extension: 3.0.2 + publish-browser-extension: 4.0.5 scule: 1.3.0 - unimport: 5.4.1 - vite: 7.1.8(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) - vite-node: 3.2.4(@types/node@24.6.2)(jiti@2.6.1)(lightningcss@1.30.1)(yaml@2.8.1) + tinyglobby: 0.2.17 + unimport: 6.3.0(esbuild@0.27.7)(rolldown@1.1.5)(rollup@4.52.3)(vite@8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0)) + vite: 8.1.5(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) + vite-node: 6.0.0(@types/node@26.1.1)(esbuild@0.27.7)(jiti@2.7.0)(yaml@2.9.0) web-ext-run: 0.2.4 + optionalDependencies: + eslint: 10.7.0(jiti@2.7.0) transitivePeerDependencies: + - '@farmfe/core' + - '@rspack/core' - '@types/node' + - '@vitejs/devtools' + - bun-types-no-globals - canvas - jiti - less - - lightningcss + - oxc-parser + - rolldown - rollup - sass - sass-embedded @@ -11799,13 +11968,15 @@ snapshots: - supports-color - terser - tsx + - unloader + - webpack - yaml xdg-basedir@5.1.0: {} xml2js@0.6.2: dependencies: - sax: 1.4.1 + sax: 1.6.0 xmlbuilder: 11.0.1 xmlbuilder@11.0.1: {} @@ -11818,15 +11989,16 @@ snapshots: yallist@3.1.1: {} - yallist@5.0.0: {} - - yaml@2.8.1: {} + yaml@2.9.0: + optional: true yargs-parser@20.2.9: {} yargs-parser@21.1.1: {} - yargs@16.2.0: + yargs-parser@22.0.0: {} + + yargs@16.2.2: dependencies: cliui: 7.0.4 escalade: 3.2.0 @@ -11836,7 +12008,7 @@ snapshots: y18n: 5.0.8 yargs-parser: 20.2.9 - yargs@17.7.2: + yargs@17.7.3: dependencies: cliui: 8.0.1 escalade: 3.2.0 @@ -11846,14 +12018,17 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 - yauzl@2.10.0: + yargs@18.0.0: dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - - yocto-queue@0.1.0: {} + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 - yocto-queue@1.2.1: {} + yocto-queue@0.1.0: + optional: true yoctocolors@2.1.2: {} @@ -11862,8 +12037,6 @@ snapshots: async: 3.2.6 jszip: 3.10.1 - zod@3.25.76: {} - - zod@4.1.11: {} + zod@4.4.3: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..062429f0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +allowBuilds: + bufferutil: true + core-js: false + esbuild: true + spawn-sync: true + utf-8-validate: true + wxt: true diff --git a/skills-lock.json b/skills-lock.json new file mode 100644 index 00000000..2ad05736 --- /dev/null +++ b/skills-lock.json @@ -0,0 +1,11 @@ +{ + "version": 1, + "skills": { + "archify": { + "source": "tt-a1i/archify", + "sourceType": "github", + "skillPath": "archify/SKILL.md", + "computedHash": "4cec0aaade3dde0f63dff0824ef435d25ed68c54c996e6a637fb4054002f21af" + } + } +} diff --git a/src/app/background/index.ts b/src/app/background/index.ts index f327934a..5af2ae90 100644 --- a/src/app/background/index.ts +++ b/src/app/background/index.ts @@ -1,23 +1,40 @@ import { browser, defineBackground } from '#imports' -import { ProvideAdapter } from '@/service/adapter/runtimeMessage' +import { ProvideAdapter } from '@/service/adapter/runtime' import { defineProxy } from 'comctx' import { AppAction } from '@/service/AppAction' +import { defineAppActionProxy, defineNotificationProxy } from '@/service/Contract' import { Notification } from '@/service/Notification' +import { COORDINATOR_NAMESPACE } from '@/runtime/Contract' +import type { RuntimeCoordinator } from '@/runtime/Contract' +import { + ensureHost, + registerPage, + relayOffscreenMessages, + restore, + unregisterPage, + watchOffscreenClosed +} from '@/runtime/Background' export default defineBackground({ type: 'module', main() { - const [provideNotification] = defineProxy(() => new Notification(), { - namespace: browser.runtime.id - }) - const [provideAppAction] = defineProxy(() => new AppAction(), { - namespace: browser.runtime.id - }) + const [provideNotification] = defineNotificationProxy(() => new Notification(), browser.runtime.id) + const [provideAppAction] = defineAppActionProxy(() => new AppAction(), browser.runtime.id) provideNotification(new ProvideAdapter()) const appAction = provideAppAction(new ProvideAdapter()) + // Sole host coordinator: pages request the shared Runtime host here. + const [provideCoordinator] = defineProxy<() => RuntimeCoordinator>( + () => ({ ensureHost, registerPage, unregisterPage }), + { namespace: `${COORDINATOR_NAMESPACE}:${browser.runtime.id}` } + ) + provideCoordinator(new ProvideAdapter()) + if (!import.meta.env.FIREFOX) relayOffscreenMessages() + watchOffscreenClosed() + void restore() + browser.action.onClicked.addListener(() => appAction.openOptionsPage()) } }) diff --git a/src/app/content/App.test.ts b/src/app/content/App.test.ts new file mode 100644 index 00000000..2d80bf34 --- /dev/null +++ b/src/app/content/App.test.ts @@ -0,0 +1,61 @@ +import { existsSync, readFileSync } from 'node:fs' +import { describe, expect, it } from 'vitest' + +const source = (path: string) => readFileSync(new URL(path, import.meta.url), 'utf8') + +describe('generic AppMain Toast ownership', () => { + it('renders one direct Toaster with the original parameters and Motion behavior', () => { + const appSource = source('./App.tsx') + const appMainSource = source('./views/app-main/index.tsx') + const styles = source('../../assets/styles/tailwind.css') + const panelStart = appSource.indexOf('') + const panelEnd = appSource.indexOf('') + const toaster = appSource.indexOf(' { + const appSource = source('./App.tsx') + const bootstrapSource = source('./index.tsx') + const readinessSource = source('../../domain/Readiness.ts') + + expect(appSource).not.toMatch(/runtimeHostPhase|ReadinessDomain|WebChat connecting|WebChat unavailable| { + const presenterSource = source('./components/toast-presentation.tsx') + const feedbackSource = source('../../domain/AppFeedback.ts') + const toastSource = source('../../domain/Toast.ts') + + expect(existsSync(new URL('./components/reconnect-toast.tsx', import.meta.url))).toBe(false) + expect(existsSync(new URL('./components/reconnect-toast.test.ts', import.meta.url))).toBe(false) + expect(presenterSource).not.toMatch(/ChatRoom|Readiness|Runtime|Network|AppStatus|panel|reconnect/i) + expect(presenterSource).toContain("from '@/domain/ToastPresentation'") + expect(feedbackSource).toContain("from '@/domain/ChatRoom'") + expect(feedbackSource).toContain("from '@/domain/Readiness'") + expect(feedbackSource).toContain("from '@/domain/ToastPresentation'") + expect(feedbackSource).toContain('minimumVisibleMs: 300') + expect(toastSource).not.toMatch(/Descriptor|Presentation|SurfaceMounted|Acknowledge/) + }) +}) diff --git a/src/app/content/App.tsx b/src/app/content/App.tsx index d9a647a7..a0a793e9 100644 --- a/src/app/content/App.tsx +++ b/src/app/content/App.tsx @@ -9,7 +9,7 @@ import ChatRoomDomain from '@/domain/ChatRoom' import UserInfoDomain from '@/domain/UserInfo' import Setup from '@/app/content/views/setup' import MessageListDomain from '@/domain/MessageList' -import { useEffect, useLayoutEffect, useRef } from 'react' +import { useEffect, useRef } from 'react' import { Toaster } from 'sonner' import DanmakuContainer from './components/danmaku-container' @@ -17,6 +17,7 @@ import DanmakuDomain from '@/domain/Danmaku' import AppStatusDomain from '@/domain/AppStatus' import { checkDarkMode, cn } from '@/utils' import WorldRoomDomain from '@/domain/WorldRoom' +import { useToastPresentation } from './components/toast-presentation' /** * Fix requestAnimationFrame error in jest @@ -42,45 +43,27 @@ export default function App() { const appStatusLoadIsFinished = useRemeshQuery(appStatusDomain.query.StatusLoadIsFinishedQuery()) const chatRoomJoinIsFinished = useRemeshQuery(chatRoomDomain.query.JoinIsFinishedQuery()) const worldRoomJoinIsFinished = useRemeshQuery(worldRoomDomain.query.JoinIsFinishedQuery()) + const toasterRef = useToastPresentation() const userInfo = useRemeshQuery(userInfoDomain.query.UserInfoQuery()) const notUserInfo = userInfoLoadFinished && !userInfoSetFinished - const joinRoom = () => { - send(chatRoomDomain.command.JoinRoomCommand()) - send(worldRoomDomain.command.JoinRoomCommand()) - } - - const leaveRoom = () => { - chatRoomJoinIsFinished && send(chatRoomDomain.command.LeaveRoomCommand()) - worldRoomJoinIsFinished && send(worldRoomDomain.command.LeaveRoomCommand()) - } - useEffect(() => { - if (messageListLoadFinished) { - if (userInfoSetFinished) { - joinRoom() - } else { - // Clear simulated data when refreshing on the setup page - send(messageListDomain.command.ClearListCommand()) - } - } - return () => leaveRoom() - }, [userInfoSetFinished, messageListLoadFinished]) + if (messageListLoadFinished && userInfoSetFinished) send(chatRoomDomain.command.JoinRoomCommand()) + }, [userInfoSetFinished, messageListLoadFinished, send, chatRoomDomain.command]) useEffect(() => { - danmakuIsEnabled && send(danmakuDomain.command.MountCommand(danmakuContainerRef.current!)) - return () => { - danmakuIsEnabled && send(danmakuDomain.command.UnmountCommand()) + if (chatRoomJoinIsFinished && !worldRoomJoinIsFinished) { + send(worldRoomDomain.command.JoinRoomCommand()) } - }, [danmakuIsEnabled]) + }, [chatRoomJoinIsFinished, worldRoomJoinIsFinished, send, worldRoomDomain.command]) - useLayoutEffect(() => { - window.addEventListener('beforeunload', leaveRoom) + useEffect(() => { + if (danmakuIsEnabled) send(danmakuDomain.command.MountCommand(danmakuContainerRef.current!)) return () => { - window.removeEventListener('beforeunload', leaveRoom) + if (danmakuIsEnabled) send(danmakuDomain.command.UnmountCommand()) } - }, []) + }, [danmakuIsEnabled, send, danmakuDomain.command]) const themeMode = userInfo?.themeMode === 'system' @@ -101,6 +84,7 @@ export default function App() {