diff --git a/.github/workflows/studio.yml b/.github/workflows/studio.yml index 4fc2a59e2e..f89b9c1377 100644 --- a/.github/workflows/studio.yml +++ b/.github/workflows/studio.yml @@ -61,3 +61,30 @@ jobs: echo "Proprietary SPDX headers are not allowed in studio/" >&2 exit 1 fi + + # Browser smoke over the real proxy tier against the fixture daemon. + # Separate job so a browser-infra flake never masks the checks above. + e2e: + name: Playwright (fixture daemon) + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version-file: studio/.nvmrc + cache: npm + cache-dependency-path: studio/package-lock.json + + - name: Install + working-directory: studio + run: | + npm ci --ignore-scripts + npx playwright install --with-deps chromium + + - name: Build and test + working-directory: studio + run: npm run test:e2e diff --git a/docs/adr/0289-studio-server-backed-chats.md b/docs/adr/0289-studio-server-backed-chats.md new file mode 100644 index 0000000000..13564757aa --- /dev/null +++ b/docs/adr/0289-studio-server-backed-chats.md @@ -0,0 +1,96 @@ +# ADR 0289 — Studio's chat list is the daemon's session store + +- Status: Accepted +- Date: 2026-08-18 +- Scope: `studio/` — where a chat's existence, title, and transcript live, and which + client actions are server calls + +> History: re-lands the unmerged draft from PR #615 (numbered 0227 there before +> that id was taken on main), adapted to the Atrium workspace UI. + +## Context + +The prototype Studio descended from kept its chat list in browser state — first +a `localStorage` key, then mock fixtures mirrored in module scope. `mecated` +persisted the same sessions the whole time: the daemon already exposed +`GET /v1/sessions`, `GET /v1/sessions/{id}/transcript`, +`POST /v1/sessions/{id}/rename`, and `POST /v1/sessions/{id}/delete` — and the +client called none of them. + +Every consequence followed from that one gap: + +- A second browser, or a second machine, saw nothing. The daemon held the work; + the client that opened it held the only record of what the work was called. +- Renaming a chat renamed a local label that no other client would ever see. +- Deleting a chat dropped the client's copy and left the session in the store + forever — "delete" quietly meant "hide". +- A reload during a run orphaned it. `mecated` keeps running after the page + that started it goes away, but the client had no way back to the result: the + run streams off the `POST /prompt` response body, and that body is gone. +- The client also minted its own session ids and mapped them lazily onto daemon + sessions, so the daemon's record and the sidebar disagreed about what even + existed. + +The persistence was never missing. It was unused. + +## Decision + +The daemon's session store is the record of which chats exist, what each is +called, and what was said in each. Studio reads and writes that record, and +**the sidebar id IS the daemon session id** — there is no client-side session +mapping. + +- **The chat list is the session inventory.** Studio walks `GET /v1/sessions` + when the daemon becomes reachable and on a slow poll, and merges the result + into its list. A row is removed only when a COMPLETE walk proves it gone — + a partial walk updates what it saw and never deletes. Rows whose one + not-a-chat reason is `inspect_only_kind` (subagents, team members, scheduled + fires) are filtered by the decoder, not by id-prefix guessing. +- **Rename is `POST …/rename`.** The local update is optimistic and rolls back + if the daemon refuses. Studio adopts the title the daemon actually stored, + which is clamped, rather than the one it asked for. +- **Delete is `POST …/delete`.** A refusal keeps the row: the chat still + exists, and hiding it locally is the exact failure this ADR is about. Only a + 404 — the daemon saying it has no such session — removes a row without a + witnessed walk. +- **Opening a chat reads `GET …/transcript`.** The authoritative message-level + snapshot, which also covers scheduler-tick fires whose conversation never + reached the durable event log, and works identically in external mode. A + transcript the daemon cannot prove whole says so in the UI. +- **Eligibility is the daemon's, not Studio's.** Each inventory row carries + `capabilities` plus a closed machine-readable reason per disabled action. + Studio renders the reason; it does not re-derive who may rename or delete + what, so tightening the rule server-side needs no client change. +- **A new chat is a draft.** No daemon session is created until the first + prompt is sent; the mint happens then, and the route adopts the daemon's id. + Empty sessions never accumulate in the store from idle "new chat" clicks. + +## Consequences + +A chat renamed or deleted on one machine is renamed or deleted on every +machine, and survives clearing the browser. Sessions can finally be removed +from the store through the UI. A reload during a run no longer loses it: the +inventory row shows the session still running on the daemon, and the +transcript is read back when it ends. + +The costs, stated plainly: + +- **The list reorders on rename.** A rename advances the daemon's stored + mtime, and that mtime is the only ordering every client can agree on. +- **A rehydrated transcript is not identical to the live one.** The transcript + endpoint returns the model's conversation, which includes the harness's own + synthetic continuations as user-role messages with no provenance to + distinguish them. Fixing this needs a provenance field on the daemon's + `ConversationMessage`, not string-sniffing in the client. +- **Reattaching to a LIVE run rides the durable session watch.** A tab that + did not start the run follows it via `GET /v1/sessions/{id}/watch` + ([ADR 0250](./0250-durable-cursors-and-watch.md)), landing later in this + series. The honest residual: the driving tab's own `POST …/prompt` stream + still cancels the run on disconnect. +- **Polling.** The list reconciles on an interval rather than a push, so a + change made elsewhere appears within seconds, not instantly. + +## See also + +- [ADR 0287](./0288-studio-atrium-module.md) — the Studio module and its + daemon-only posture diff --git a/docs/adr/README.md b/docs/adr/README.md index 799f443967..b9c2f2ea4f 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -217,6 +217,7 @@ Documentation/citation conventions are in [`docs/design/README.md`](../design/RE - [0088 — Explicit daemon.yaml (listener topology config)](./0088-daemon-config-file.md) - [0222 — mecatui: ctrl+t routes by ask type; full-screen ask-args view](./0222-mecatui-ask-args-view.md) - [0288 — Studio: the Atrium workspace as mecatl's daemon-only web client](./0288-studio-atrium-module.md) +- [0289 — Studio's chat list is the daemon's session store](./0289-studio-server-backed-chats.md) - [0247 — mecatui generated status lines](./0247-mecatui-status-line.md) - [0280 — Automatic light theme selection in mecatui](./0280-mecatui-light-theme-autodetect.md) diff --git a/docs/design/PRODUCTION-READINESS.md b/docs/design/PRODUCTION-READINESS.md index 54ea7cc381..24fd4f7ccd 100644 --- a/docs/design/PRODUCTION-READINESS.md +++ b/docs/design/PRODUCTION-READINESS.md @@ -46,7 +46,7 @@ record; current behaviour is in the linked [architecture](../architecture.md) do | mecak8s (storage-free k8s-native agent) | ✅ shipped (MVP) · ✅ OPT-IN `/metrics` loopback scrape + OTLP push (ADR 0098) · ✅ verified external Redis TLS/ACL with transactional projected-file reload + last-valid generations (ADR 0240) · ✅ Helm 0.3.0 secure real-provider in-pod TLS+OIDC or edge-terminated TLS+OIDC (ClusterIP h2c), nullable spend ceilings, and pod scheduling controls · ⛔ CRD/Operator · ⛔ HPA (custom-metrics on active-runs) · ⛔ managed Redis provisioning (ElastiCache/MemoryStore — endpoint only) · ⛔ fix `mecated`'s unbounded `GracefulStop` (pre-existing, follow-up) | [mecak8s.md](../adr/0048-mecak8s.md) · [0098](../adr/0098-headless-telemetry.md) · [0240](../adr/0240-mecak8s-credential-reload-and-chart-security.md) · [0278](../adr/0278-mecak8s-edge-terminated-tls.md) · [MECAK8S-PLAN.md](./MECAK8S-PLAN.md) | [overview](../architecture.md) | | ACP adapter (editor stdio surface) | ✅ Phase 1+2 + bounded Phase 3 + multimodal shipped · ⛔ Phase 3 long-tail (rule persistence, grep-over-buffers, fs/* on resume) | [0001-acp-adapter.md](../adr/0001-acp-adapter.md) | [api surface](../architecture/api-surface.md) | | Conversation fork (peer session from a history snapshot) | ✅ shipped · ✅ effort override (mid-conversation effort switch, keeps the transcript — [0068](../adr/0068-effort-change-via-fork.md)) · ⛔ cross-provider/model fork (v2: replay-blob stripping) · ⛔ workspace-branching fork · ⛔ fork-from-event-log-at-arbitrary-point · ⛔ fork lineage (`forked_from` label) | [0065-conversation-fork.md](../adr/0065-conversation-fork.md) | [overview](../architecture.md) | -| Studio (web client) | 🚧 landing as a stacked PR series: ✅ module foundation (vendored Atrium UI kit, root chrome, npm/Biome/knip/vitest toolchain, CI checks incl. the license-header gate) · ⛔ server tier (proxy + managed-mode controller) · ⛔ protocol seam + harness · ⛔ the five surfaces (Chats · Scheduled · Skills · Memory · Settings) · ⛔ browser e2e | [0288](../adr/0288-studio-atrium-module.md) | [overview](../architecture.md) | +| Studio (web client) | 🚧 landing as a stacked PR series: ✅ module foundation (vendored Atrium UI kit, toolchain, CI gates) · ✅ server tier (trusted proxy + managed-mode controller core, hermetic suite) · ✅ protocol seam + harness transport · ✅ workspace shell + runtime status · ✅ Chats core + hermetic browser e2e (fixture daemon) · ⛔ Scheduled · Skills · Memory · Settings surfaces · ⛔ advanced chat tiers (attachments, steer/queue, threads, re-attach, modes, mobile) | [0288](../adr/0288-studio-atrium-module.md) · [0289](../adr/0289-studio-server-backed-chats.md) | [overview](../architecture.md) | | _Historical / retired_ | — | [ARCHITECTURE.md](../adr/0004-v1-architecture.md) · [STEP-CHAIN.md](../adr/0006-v1-step-chain.md) · [TWELVE-PATTERNS-AUDIT.md](../adr/0007-twelve-patterns-audit.md) · [REPOMAP-TREE-SITTER.md](../adr/0029-repomap-tree-sitter.md) | — | ## Security diff --git a/llms.txt b/llms.txt index e8bcc514c8..e34967f782 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # mecatl -> A markdown documentation corpus of 281 document(s) across 12 component(s), with 2794 heading(s) and 2688 resolved reference(s). Entries are ordered by importance (most-connected first). 19 document(s) excluded from rendering by emitExclude (still in the corpus: link-checked and ranked). Compactness 0.85, docs ~2.2 clicks apart. +> A markdown documentation corpus of 282 document(s) across 12 component(s), with 2799 heading(s) and 2692 resolved reference(s). Entries are ordered by importance (most-connected first). 19 document(s) excluded from rendering by emitExclude (still in the corpus: link-checked and ranked). Compactness 0.85, docs ~2.2 clicks apart. ## Documentation @@ -27,8 +27,8 @@ ## Optional +- [ADR 0250 — Durable cursors and the session watch transport](docs/adr/0250-durable-cursors-and-watch.md): ADR 0250 — Durable cursors and the session watch transport (linked from: docs/acceptance/README.md, docs/acceptance/sdk-server-enablers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0248-sdk-compatibility-and-error-contract.md, docs/adr/0249-durable-run-identity.md, docs/adr/0253-sdk-mocking-testkit.md, docs/adr/0279-typescript-sdk-architecture.md, docs/adr/0288-studio-atrium-module.md, docs/adr/0289-studio-server-backed-chats.md, docs/adr/README.md, docs/architecture.md, docs/design/MECAK8S-PLAN.md, docs/design/principles.md, docs/development-process.md, docs/usage/grpc-api.md, docs/usage/http-sse-api.md, engine/CHANGELOG.md) - [ADR 0215 — OpenAI subscription with a manual access token](docs/adr/0215-openai-subscription-manual-token.md): ADR 0215 — OpenAI subscription with a manual access token (linked from: docs/README.md, docs/acceptance/README.md, docs/acceptance/operator-defined-llm-providers.md, docs/acceptance/spine-convergence.md, docs/adr/0238-operator-defined-llm-providers.md, docs/adr/README.md, docs/architecture.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/usage/troubleshooting.md) -- [ADR 0250 — Durable cursors and the session watch transport](docs/adr/0250-durable-cursors-and-watch.md): ADR 0250 — Durable cursors and the session watch transport (linked from: docs/acceptance/README.md, docs/acceptance/sdk-server-enablers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0248-sdk-compatibility-and-error-contract.md, docs/adr/0249-durable-run-identity.md, docs/adr/0253-sdk-mocking-testkit.md, docs/adr/0279-typescript-sdk-architecture.md, docs/adr/0288-studio-atrium-module.md, docs/adr/README.md, docs/architecture.md, docs/design/MECAK8S-PLAN.md, docs/design/principles.md, docs/development-process.md, docs/usage/grpc-api.md, docs/usage/http-sse-api.md, engine/CHANGELOG.md) - [ADR 0277 — Remote mecatui OIDC client authentication](docs/adr/0277-remote-mecatui-oidc.md): ADR 0277 — Remote mecatui OIDC client authentication (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0274-remote-mecatui-logout-budget.md, docs/adr/0275-bounded-scoped-https-keepalive-oidc.md, docs/adr/0284-optional-system-trust-remote-oidc.md, docs/adr/0286-issuer-transport-split.md, docs/adr/0287-target-aware-mecatui-tls.md, docs/adr/README.md, docs/architecture.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0001 — Agent Client Protocol (ACP) adapter](docs/adr/0001-acp-adapter.md): ADR 0001 — Agent Client Protocol (ACP) adapter (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0078-mcp-typed-tool-results.md, docs/adr/README.md, docs/architecture.md, docs/architecture/api-surface.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0023 — Workspace Trust](docs/adr/0023-workspace-trust.md): ADR 0023 — Workspace Trust (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0021-guardrails.md, docs/adr/0022-allow-all-posture.md, docs/adr/0032-worktree-binding.md, docs/adr/0033-dirty-aware-readonly-fork.md, docs/adr/0092-no-project-trust-pin.md, docs/adr/0094-opt-in-project-ingestion.md, docs/adr/0095-root-aware-project-trust.md, docs/adr/0202-diagnostic-only-posture-reporting.md, docs/adr/README.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) @@ -49,42 +49,43 @@ - [ADR 0221 — Read-only environment credential source and OAuth refresh posture](docs/adr/0221-read-only-credential-source.md): ADR 0221 — Read-only environment credential source and OAuth refresh posture (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0009 — Tiered memory: tier-0 index and BM25 search](docs/adr/0009-tiered-memory.md): ADR 0009 — Tiered memory: tier-0 index and BM25 search (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0008-memory-on-by-default.md, docs/adr/0010-semantic-memory-recall.md, docs/adr/0011-soul-and-user-model.md, docs/adr/0012-compaction.md, docs/adr/0043-ephemeral-turn0-instruction-fragments.md, docs/adr/0227-dream-consolidation-safety-boundary.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0065 — Conversation fork: peer session from a history snapshot](docs/adr/0065-conversation-fork.md): ADR 0065 — Conversation fork: peer session from a history snapshot (linked from: docs/acceptance/README.md, docs/acceptance/session-continuity-ux.md, docs/acceptance/spine-convergence.md, docs/adr/0068-effort-change-via-fork.md, docs/adr/0071-seamless-model-switch.md, docs/adr/0217-session-discovery-continuation.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0206 — Ship reusable OIDC caller identity as an opt-in module](docs/adr/0206-oidc-authn-module.md): ADR 0206 — Ship reusable OIDC caller identity as an opt-in module (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0235-scoped-private-https-oidc-transport.md, docs/adr/0236-private-https-oidc-issuer.md, docs/adr/0240-mecak8s-credential-reload-and-chart-security.md, docs/adr/0270-activity-gated-remote-oidc-refresh.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0012 — Conversation compaction](docs/adr/0012-compaction.md): ADR 0012 — Conversation compaction (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0011-soul-and-user-model.md, docs/adr/0027-cloud-native.md, docs/adr/0043-ephemeral-turn0-instruction-fragments.md, docs/adr/0276-full-request-and-manual-compaction.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0018 — Performance observability](docs/adr/0018-perf-observability.md): ADR 0018 — Performance observability (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0019-perf-tracking.md, docs/adr/0020-diagnostics.md, docs/adr/0045-explicit-bucket-latency-histograms.md, docs/adr/0098-headless-telemetry.md, docs/adr/0254-session-debugger-admin-transport.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/perf-measurement-survey.md) - [ADR 0201 — Background Bash commands](docs/adr/0201-background-bash.md): ADR 0201 — Background Bash commands (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0281-managed-temporary-command-leases.md, docs/adr/0282-managed-workspace-scratch-cache.md, docs/adr/0283-managed-delegation-fork-lifecycle.md, docs/adr/README.md, docs/architecture/ports.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0278 — mecak8s edge-terminated TLS](docs/adr/0278-mecak8s-edge-terminated-tls.md): ADR 0278 — mecak8s edge-terminated TLS (linked from: deploy/README.md, docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0240-mecak8s-credential-reload-and-chart-security.md, docs/adr/README.md, docs/architecture/deployment-and-hardening.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecak8s.md) -- [ADR 0257 — Session debugger incarnation and disclosure hardening](docs/adr/0257-session-debugger-hardening.md): ADR 0257 — Session debugger incarnation and disclosure hardening (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0258-cryptographic-session-incarnations.md, docs/adr/README.md, docs/architecture.md, docs/architecture/extensibility.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0206 — Ship reusable OIDC caller identity as an opt-in module](docs/adr/0206-oidc-authn-module.md): ADR 0206 — Ship reusable OIDC caller identity as an opt-in module (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0235-scoped-private-https-oidc-transport.md, docs/adr/0236-private-https-oidc-issuer.md, docs/adr/0240-mecak8s-credential-reload-and-chart-security.md, docs/adr/0270-activity-gated-remote-oidc-refresh.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0053 — Flip guardrails default mode from advisory to block](docs/adr/0053-guardrails-default-block.md): ADR 0053 — Flip guardrails default mode from advisory to block (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0060-guardrails-bash-default.md, docs/adr/0061-guardrails-human-override.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/usage/guardrails.md) +- [ADR 0257 — Session debugger incarnation and disclosure hardening](docs/adr/0257-session-debugger-hardening.md): ADR 0257 — Session debugger incarnation and disclosure hardening (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0258-cryptographic-session-incarnations.md, docs/adr/README.md, docs/architecture.md, docs/architecture/extensibility.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/principles.md, docs/development-process.md) - [ADR 0049 — Remove the guardrails per-session checker call-count cap](docs/adr/0049-guardrails-remove-maxchecks.md): ADR 0049 — Remove the guardrails per-session checker call-count cap (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0021-guardrails.md, docs/adr/0050-guardrails-remove-maxcontentbytes.md, docs/adr/0051-guardrails-advisory-tui-visibility.md, docs/adr/0052-guardrails-checker-down-toggle.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0032 — First-class worktree binding for a session](docs/adr/0032-worktree-binding.md): ADR 0032 — First-class worktree binding for a session (linked from: docs/acceptance/README.md, docs/acceptance/listener-scoped-workspace-authority.md, docs/acceptance/spine-convergence.md, docs/adr/0033-dirty-aware-readonly-fork.md, docs/adr/0237-listener-scoped-workspace-authority.md, docs/adr/README.md, docs/architecture/parallelism.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0051 — Surface advisory guardrail findings to the TUI](docs/adr/0051-guardrails-advisory-tui-visibility.md): ADR 0051 — Surface advisory guardrail findings to the TUI (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0021-guardrails.md, docs/adr/0053-guardrails-default-block.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/usage/guardrails.md) - [ADR 0285 — Predictable actionable mecatui session handles](docs/adr/0285-predictable-mecatui-session-handles.md): ADR 0285 — Predictable actionable mecatui session handles (linked from: docs/acceptance/README.md, docs/acceptance/predictable-session-handles.md, docs/acceptance/spine-convergence.md, docs/adr/0217-session-discovery-continuation.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0055 — Reasoning-effort knob](docs/adr/0055-reasoning-effort.md): ADR 0055 — Reasoning-effort knob (linked from: docs/acceptance/README.md, docs/acceptance/schedule-tool.md, docs/acceptance/spine-convergence.md, docs/adr/0064-toolhive-llm-gateway-provider.md, docs/adr/0067-openai-chat-completions-adapter.md, docs/adr/0068-effort-change-via-fork.md, docs/adr/0100-provider-prompt-caching.md, docs/adr/README.md, docs/design/principles.md, docs/development-process.md, docs/usage/guardrails.md, docs/usage/mecak8s.md, docs/usage/mecated.md, engine/CHANGELOG.md) - [ADR 0031 — The operator-gated semantic subagent model router](docs/adr/0031-subagent-model-router.md): ADR 0031 — The operator-gated semantic subagent model router (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0030-model-selection-heuristics.md, docs/adr/0034-team-parallel-model-routing.md, docs/adr/0035-per-delegation-model-surface.md, docs/adr/0042-taxonomy-gated-model-router.md, docs/adr/0066-route-unpinned-and-writable-delegations.md, docs/adr/0083-routing-reason-on-delegation-start.md, docs/adr/0242-route-unpinned-writable-named-specialists.md, docs/adr/README.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecated.md, docs/usage/model-routing.md) -- [ADR 0209 — Attribute schedule origins through the run context](docs/adr/0209-schedule-origin-run-context.md): ADR 0209 — Attribute schedule origins through the run context (linked from: docs/acceptance/README.md, docs/acceptance/schedule-shared-catalog.md, docs/acceptance/spine-convergence.md, docs/adr/0075-fire-result-delivery.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0019 — Long-term performance and resource regression tracking](docs/adr/0019-perf-tracking.md): ADR 0019 — Long-term performance and resource regression tracking (linked from: .github/workflows/README.md, docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0018-perf-observability.md, docs/adr/0020-diagnostics.md, docs/adr/0045-explicit-bucket-latency-histograms.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0033 — Dirty-aware read-only fork (uncommitted-state overlay)](docs/adr/0033-dirty-aware-readonly-fork.md): ADR 0033 — Dirty-aware read-only fork (uncommitted-state overlay) (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0065-conversation-fork.md, docs/adr/README.md, docs/architecture/parallelism.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0209 — Attribute schedule origins through the run context](docs/adr/0209-schedule-origin-run-context.md): ADR 0209 — Attribute schedule origins through the run context (linked from: docs/acceptance/README.md, docs/acceptance/schedule-shared-catalog.md, docs/acceptance/spine-convergence.md, docs/adr/0075-fire-result-delivery.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0020 — Diagnostics, audit, and the global-slog ban](docs/adr/0020-diagnostics.md): ADR 0020 — Diagnostics, audit, and the global-slog ban (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0018-perf-observability.md, docs/adr/0019-perf-tracking.md, docs/adr/0087-mecatui-staged-transport-migration.md, docs/adr/0104-session-family-physical-naming.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0010 — Semantic memory recall: BM25 shipped, semantic deferred](docs/adr/0010-semantic-memory-recall.md): ADR 0010 — Semantic memory recall: BM25 shipped, semantic deferred (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0008-memory-on-by-default.md, docs/adr/0009-tiered-memory.md, docs/adr/0011-soul-and-user-model.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0062 — Out-of-band approve-once for guardrail blocks](docs/adr/0062-guardrails-approve-once.md): ADR 0062 — Out-of-band approve-once for guardrail blocks (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0061-guardrails-human-override.md, docs/adr/0069-plan-approval-gate.md, docs/adr/0080-guardrail-routed-escape-checking.md, docs/adr/README.md, docs/architecture/agent-loop.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) -- [ADR 0030 — Layered model-selection heuristics](docs/adr/0030-model-selection-heuristics.md): ADR 0030 — Layered model-selection heuristics (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0031-subagent-model-router.md, docs/adr/0032-worktree-binding.md, docs/adr/0034-team-parallel-model-routing.md, docs/adr/0046-guardrails-slot-enable.md, docs/adr/0066-route-unpinned-and-writable-delegations.md, docs/adr/0069-plan-approval-gate.md, docs/adr/README.md, docs/architecture/api-surface.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecated.md) - [ADR 0007 — Twelve agentic-harness patterns: pluggability audit](docs/adr/0007-twelve-patterns-audit.md): ADR 0007 — Twelve agentic-harness patterns: pluggability audit (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0024-system-prompt-research.md, docs/adr/0069-plan-approval-gate.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0062 — Out-of-band approve-once for guardrail blocks](docs/adr/0062-guardrails-approve-once.md): ADR 0062 — Out-of-band approve-once for guardrail blocks (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0061-guardrails-human-override.md, docs/adr/0069-plan-approval-gate.md, docs/adr/0080-guardrail-routed-escape-checking.md, docs/adr/README.md, docs/architecture/agent-loop.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0025 — mecatui UX Discoverability](docs/adr/0025-ux-discoverability.md): ADR 0025 — mecatui UX Discoverability (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0026-clipboard-image-paste.md, docs/adr/0276-full-request-and-manual-compaction.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/tui.md) +- [ADR 0030 — Layered model-selection heuristics](docs/adr/0030-model-selection-heuristics.md): ADR 0030 — Layered model-selection heuristics (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0031-subagent-model-router.md, docs/adr/0032-worktree-binding.md, docs/adr/0034-team-parallel-model-routing.md, docs/adr/0046-guardrails-slot-enable.md, docs/adr/0066-route-unpinned-and-writable-delegations.md, docs/adr/0069-plan-approval-gate.md, docs/adr/README.md, docs/architecture/api-surface.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecated.md) - [ADR 0107 — Live operator profiles and reversible memory lifecycle](docs/adr/0107-operator-profile-memory-lifecycle.md): ADR 0107 — Live operator profiles and reversible memory lifecycle (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0109-staged-learning-proposals.md, docs/adr/0227-dream-consolidation-safety-boundary.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0052 — Global guardrails checker-down posture toggle](docs/adr/0052-guardrails-checker-down-toggle.md): ADR 0052 — Global guardrails checker-down posture toggle (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/usage/guardrails.md) - [ADR 0008 — Memory on by default in the embedded mecatui server](docs/adr/0008-memory-on-by-default.md): ADR 0008 — Memory on by default in the embedded mecatui server (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0009-tiered-memory.md, docs/adr/0010-semantic-memory-recall.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0093 — Ship the real LLM provider adapters as opt-in Go modules under provider/](docs/adr/0093-provider-modules.md): ADR 0093 — Ship the real LLM provider adapters as opt-in Go modules under provider/ (linked from: docs/acceptance/README.md, docs/acceptance/operator-defined-llm-providers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0100-provider-prompt-caching.md, docs/adr/0216-provider-session-correlation-header.md, docs/adr/0238-operator-defined-llm-providers.md, docs/adr/0279-typescript-sdk-architecture.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0013 — Agent definitions (Tier 1)](docs/adr/0013-agent-definitions.md): ADR 0013 — Agent definitions (Tier 1) (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0014-agent-teams.md, docs/adr/0015-background-subagents.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat](docs/adr/0075-fire-result-delivery.md): ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat (linked from: docs/acceptance/README.md, docs/acceptance/fire-result-delivery.md, docs/acceptance/schedule-shared-catalog.md, docs/acceptance/spine-convergence.md, docs/adr/0076-schedule-shared-catalog.md, docs/adr/0096-live-feed-reconnect.md, docs/adr/0097-scheduled-fire-inflight-state.md, docs/adr/0209-schedule-origin-run-context.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0026 — Clipboard Image Paste](docs/adr/0026-clipboard-image-paste.md): ADR 0026 — Clipboard Image Paste (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0025-ux-discoverability.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md, docs/tui.md) +- [ADR 0093 — Ship the real LLM provider adapters as opt-in Go modules under provider/](docs/adr/0093-provider-modules.md): ADR 0093 — Ship the real LLM provider adapters as opt-in Go modules under provider/ (linked from: docs/acceptance/README.md, docs/acceptance/operator-defined-llm-providers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0100-provider-prompt-caching.md, docs/adr/0216-provider-session-correlation-header.md, docs/adr/0238-operator-defined-llm-providers.md, docs/adr/0279-typescript-sdk-architecture.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat](docs/adr/0075-fire-result-delivery.md): ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat (linked from: docs/acceptance/README.md, docs/acceptance/fire-result-delivery.md, docs/acceptance/schedule-shared-catalog.md, docs/acceptance/spine-convergence.md, docs/adr/0076-schedule-shared-catalog.md, docs/adr/0096-live-feed-reconnect.md, docs/adr/0097-scheduled-fire-inflight-state.md, docs/adr/0209-schedule-origin-run-context.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0110 — Evaluated, versioned agent-owned skills](docs/adr/0110-evaluated-agent-owned-skills.md): ADR 0110 — Evaluated, versioned agent-owned skills (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0111-hardened-agent-owned-skill-publication.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0054 — Reasoning rebalance of the default-tone prompt](docs/adr/0054-reasoning-rebalance-default-prompt.md): ADR 0054 — Reasoning rebalance of the default-tone prompt (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0086-remove-output-economy-control.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0214 — Execution-environment persistence and reattachment](docs/adr/0214-environment-persistence.md): ADR 0214 — Execution-environment persistence and reattachment (linked from: docs/acceptance/README.md, docs/acceptance/authority-evaluator-port.md, docs/acceptance/spine-convergence.md, docs/adr/0211-execution-environment-runtime-seam.md, docs/adr/0234-authority-evaluator-port.md, docs/adr/README.md, docs/architecture.md, docs/architecture/ports.md, docs/design/principles.md, docs/development-process.md, engine/COMPATIBILITY.md) +- [ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client](docs/adr/0288-studio-atrium-module.md): ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0289-studio-server-backed-chats.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0029 — Repo-map Tree-sitter Binding](docs/adr/0029-repomap-tree-sitter.md): ADR 0029 — Repo-map Tree-sitter Binding (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0068 — Change reasoning effort via conversation fork (keep the transcript)](docs/adr/0068-effort-change-via-fork.md): ADR 0068 — Change reasoning effort via conversation fork (keep the transcript) (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) - [ADR 0098 — Telemetry for the headless binaries (mecatequi, mecak8s)](docs/adr/0098-headless-telemetry.md): ADR 0098 — Telemetry for the headless binaries (mecatequi, mecak8s) (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) -- [ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client](docs/adr/0288-studio-atrium-module.md): ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0289 — Studio's chat list is the daemon's session store](docs/adr/0289-studio-server-backed-chats.md): ADR 0289 — Studio's chat list is the daemon's session store (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/README.md, docs/design/PRODUCTION-READINESS.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0214 — Execution-environment persistence and reattachment](docs/adr/0214-environment-persistence.md): ADR 0214 — Execution-environment persistence and reattachment (linked from: docs/acceptance/README.md, docs/acceptance/authority-evaluator-port.md, docs/acceptance/spine-convergence.md, docs/adr/0211-execution-environment-runtime-seam.md, docs/adr/0234-authority-evaluator-port.md, docs/adr/README.md, docs/architecture.md, docs/architecture/ports.md, docs/design/principles.md, docs/development-process.md, engine/COMPATIBILITY.md) - [ADR 0042 — Taxonomy-gated subagent model router (enable by config, not a flag)](docs/adr/0042-taxonomy-gated-model-router.md): ADR 0042 — Taxonomy-gated subagent model router (enable by config, not a flag) (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0031-subagent-model-router.md, docs/adr/0046-guardrails-slot-enable.md, docs/adr/0066-route-unpinned-and-writable-delegations.md, docs/adr/README.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, docs/usage/guardrails.md, docs/usage/mecated.md, docs/usage/model-routing.md) - [ADR 0249 — Durable run identity: a host-minted run_id](docs/adr/0249-durable-run-identity.md): ADR 0249 — Durable run identity: a host-minted run_id (linked from: docs/acceptance/README.md, docs/acceptance/sdk-server-enablers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0248-sdk-compatibility-and-error-contract.md, docs/adr/0250-durable-cursors-and-watch.md, docs/adr/0252-http-steer-endpoint.md, docs/adr/0253-sdk-mocking-testkit.md, docs/adr/0279-typescript-sdk-architecture.md, docs/adr/README.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0211 — Execution-environment runtime seam](docs/adr/0211-execution-environment-runtime-seam.md): ADR 0211 — Execution-environment runtime seam (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0208-execution-environment.md, docs/adr/0214-environment-persistence.md, docs/adr/0281-managed-temporary-command-leases.md, docs/adr/README.md, docs/architecture.md, docs/architecture/ports.md, docs/design/principles.md, docs/development-process.md) @@ -97,8 +98,8 @@ - [ADR 0066 — Route unpinned agent-defs and writable explorers; explicit inherit is the pin](docs/adr/0066-route-unpinned-and-writable-delegations.md): ADR 0066 — Route unpinned agent-defs and writable explorers; explicit inherit is the pin (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0031-subagent-model-router.md, docs/adr/0077-direct-write-subagent.md, docs/adr/0242-route-unpinned-writable-named-specialists.md, docs/adr/README.md, docs/architecture/providers.md, docs/design/IMPLEMENTATION-NOTES.md, docs/design/principles.md, docs/development-process.md) - [ADR 0034 — Extend the model router to team members and Parallel branches](docs/adr/0034-team-parallel-model-routing.md): ADR 0034 — Extend the model router to team members and Parallel branches (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0035-per-delegation-model-surface.md, docs/adr/0042-taxonomy-gated-model-router.md, docs/adr/0083-routing-reason-on-delegation-start.md, docs/adr/README.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecated.md) - [ADR 0095 — Root-aware project trust](docs/adr/0095-root-aware-project-trust.md): ADR 0095 — Root-aware project trust (linked from: docs/acceptance/README.md, docs/acceptance/listener-scoped-workspace-authority.md, docs/acceptance/spine-convergence.md, docs/adr/0092-no-project-trust-pin.md, docs/adr/0094-opt-in-project-ingestion.md, docs/adr/0202-diagnostic-only-posture-reporting.md, docs/adr/0237-listener-scoped-workspace-authority.md, docs/adr/README.md, docs/design/principles.md, docs/development-process.md, docs/usage/mecak8s.md, docs/usage/mecated.md, docs/usage/mecatequi-ci.md, docs/usage/workspace-trust.md) -- [ADR 0213 — Enforce caller ownership at remote driver boundaries](docs/adr/0213-driver-caller-ownership.md): ADR 0213 — Enforce caller ownership at remote driver boundaries (linked from: docs/acceptance/README.md, docs/acceptance/caller-separation.md, docs/acceptance/spine-convergence.md, docs/adr/0212-caller-ownership-enforcement.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0089 — CLI clean break: one canonical spelling per action](docs/adr/0089-cli-clean-break-grammar.md): ADR 0089 — CLI clean break: one canonical spelling per action (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0086-remove-output-economy-control.md, docs/adr/0087-mecatui-staged-transport-migration.md, docs/adr/0103-mecatui-seed-prompt.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) +- [ADR 0213 — Enforce caller ownership at remote driver boundaries](docs/adr/0213-driver-caller-ownership.md): ADR 0213 — Enforce caller ownership at remote driver boundaries (linked from: docs/acceptance/README.md, docs/acceptance/caller-separation.md, docs/acceptance/spine-convergence.md, docs/adr/0212-caller-ownership-enforcement.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0242 — Route unpinned writable named specialists](docs/adr/0242-route-unpinned-writable-named-specialists.md): ADR 0242 — Route unpinned writable named specialists (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0058-writable-named-specialist-subagent.md, docs/adr/0066-route-unpinned-and-writable-delegations.md, docs/adr/README.md, docs/architecture/providers.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) - [ADR 0111 — Hardened publication and recovery for agent-owned skills](docs/adr/0111-hardened-agent-owned-skill-publication.md): ADR 0111 — Hardened publication and recovery for agent-owned skills (linked from: docs/acceptance/README.md, docs/acceptance/spine-convergence.md, docs/adr/0110-evaluated-agent-owned-skills.md, docs/adr/0224-validated-automatic-skill-activation.md, docs/adr/README.md, docs/architecture.md, docs/design/principles.md, docs/development-process.md) - [ADR 0044 — Host-supplied askID discriminator (cross-process-reconstructable askID)](docs/adr/0044-host-supplied-askid-discriminator.md): ADR 0044 — Host-supplied askID discriminator (cross-process-reconstructable askID) (linked from: docs/acceptance/README.md, docs/acceptance/sdk-server-enablers.md, docs/acceptance/sdk-typescript-core.md, docs/acceptance/spine-convergence.md, docs/adr/0249-durable-run-identity.md, docs/adr/README.md, docs/design/principles.md, docs/development-process.md, engine/CHANGELOG.md) @@ -237,8 +238,8 @@ - [Memory — cross-session recall & consolidation](docs/architecture/memory.md): Memory — cross-session recall & consolidation (linked from: README.md, docs/READING.md, docs/adr/0106-optional-learning-seam.md, docs/adr/0107-operator-profile-memory-lifecycle.md, docs/adr/0109-staged-learning-proposals.md, docs/adr/0227-dream-consolidation-safety-boundary.md, docs/adr/0228-manual-dream-review.md, docs/architecture.md, docs/architecture/agent-loop.md, docs/architecture/context-and-compaction.md, docs/design/PRODUCTION-READINESS.md) - [Hooks & guardrails](docs/architecture/hooks-and-guardrails.md): Hooks & guardrails (linked from: docs/READING.md, docs/adr/0051-guardrails-advisory-tui-visibility.md, docs/architecture.md, docs/architecture/agent-loop.md, docs/architecture/api-surface.md, docs/architecture/deployment-and-hardening.md, docs/design/PRODUCTION-READINESS.md) - [Parallelism — fork-join](docs/architecture/parallelism.md): Parallelism — fork-join (linked from: docs/READING.md, docs/adr/0283-managed-delegation-fork-lifecycle.md, docs/architecture.md, docs/architecture/agent-loop.md, docs/architecture/api-surface.md, docs/architecture/subagents-and-teams.md, docs/design/PRODUCTION-READINESS.md) -- [Engine compatibility policy](engine/COMPATIBILITY.md): Engine compatibility policy (linked from: AGENTS.md, README.md, docs/READING.md, docs/README.md, docs/acceptance/delegation-observability-convergence.md, docs/acceptance/sdk-server-enablers.md, docs/adr/0037-engine-stability-contract.md, docs/adr/0038-event-sourced-rehydration.md, docs/adr/0110-evaluated-agent-owned-skills.md, docs/adr/0204-caller-identity-threading.md, docs/adr/0249-durable-run-identity.md, docs/adr/0258-cryptographic-session-incarnations.md, docs/architecture.md, docs/architecture/api-surface.md, docs/architecture/extensibility.md, engine/CHANGELOG.md, engine/api/README.md) - [Deployment & server hardening](docs/architecture/deployment-and-hardening.md): Deployment & server hardening (linked from: docs/READING.md, docs/adr/0237-listener-scoped-workspace-authority.md, docs/adr/0278-mecak8s-edge-terminated-tls.md, docs/architecture.md, docs/architecture/api-surface.md, docs/architecture/hooks-and-guardrails.md, docs/architecture/observability.md, docs/architecture/parallelism.md, docs/design/PRODUCTION-READINESS.md) +- [Engine compatibility policy](engine/COMPATIBILITY.md): Engine compatibility policy (linked from: AGENTS.md, README.md, docs/READING.md, docs/README.md, docs/acceptance/delegation-observability-convergence.md, docs/acceptance/sdk-server-enablers.md, docs/adr/0037-engine-stability-contract.md, docs/adr/0038-event-sourced-rehydration.md, docs/adr/0110-evaluated-agent-owned-skills.md, docs/adr/0204-caller-identity-threading.md, docs/adr/0249-durable-run-identity.md, docs/adr/0258-cryptographic-session-incarnations.md, docs/architecture.md, docs/architecture/api-surface.md, docs/architecture/extensibility.md, engine/CHANGELOG.md, engine/api/README.md) - [Extensibility — MCP, tools & progressive disclosure](docs/architecture/extensibility.md): Extensibility — MCP, tools & progressive disclosure (linked from: README.md, docs/READING.md, docs/adr/0056-mcp-client-reconnect.md, docs/adr/0107-operator-profile-memory-lifecycle.md, docs/adr/0108-on-demand-logical-skill-assets.md, docs/adr/0112-mcp-oauth-loopback-runtime.md, docs/adr/0113-operator-mcp-auth-profiles.md, docs/adr/0219-mcp-oauth-sdk-profile.md, docs/adr/0220-mcp-oauth-controller.md, docs/adr/0223-mcp-sdk-transport-error-semantics.md, docs/adr/0281-managed-temporary-command-leases.md, docs/adr/0283-managed-delegation-fork-lifecycle.md, docs/agent-fabric-protocol.md, docs/architecture.md, docs/architecture/agent-loop.md, docs/architecture/api-surface.md) - [mecak8s — MVP Implementation Plan](docs/design/MECAK8S-PLAN.md): mecak8s — MVP Implementation Plan (linked from: docs/adr/0048-mecak8s.md, docs/design/PRODUCTION-READINESS.md) - [The domain model](docs/architecture/domain-model.md): The domain model (linked from: docs/READING.md, docs/acceptance/session-continuity-ux.md, docs/architecture.md, docs/architecture/ports.md, docs/architecture/subagents-and-teams.md) @@ -323,8 +324,8 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 26. [ADR 0003 — Consolidate design records as ADRs](docs/adr/0003-consolidate-design-records-as-adrs.md) 27. [ADR 0015 — Background subagents and per-child cancel](docs/adr/0015-background-subagents.md) 28. [ADR 0018 — Performance observability](docs/adr/0018-perf-observability.md) -29. [Providers — OpenAI adapter & multi-provider](docs/architecture/providers.md) -30. [mecatl](README.md) +29. [mecatl](README.md) +30. [Providers — OpenAI adapter & multi-provider](docs/architecture/providers.md) 31. [3. Running the server (mecated)](docs/usage/mecated.md) 32. [ADR 0005 — Driver seams: ports, the gRPC driver protocol, and conformance](docs/adr/0005-driver-seams.md) 33. [Extensibility — MCP, tools & progressive disclosure](docs/architecture/extensibility.md) @@ -338,8 +339,8 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 41. [ADR 0043 — Ephemeral turn-0 instruction fragments](docs/adr/0043-ephemeral-turn0-instruction-fragments.md) 42. [Observability, persistence & reliability](docs/architecture/observability.md) 43. [Platform principles](docs/design/principles.md) -44. [Surface migration — the general template (issue #555 Phase 2)](docs/design/surface-migration-plan.md) -45. [ADR 0205 — Bound cached JWKS staleness](docs/adr/0205-bounded-jwks-staleness.md) +44. [ADR 0205 — Bound cached JWKS staleness](docs/adr/0205-bounded-jwks-staleness.md) +45. [Surface migration — the general template (issue #555 Phase 2)](docs/design/surface-migration-plan.md) 46. [ADR 0024 — System-Prompt Research and Enhancement](docs/adr/0024-system-prompt-research.md) 47. [Changelog — github.com/stacklok/mecatl/engine](engine/CHANGELOG.md) 48. [Deployment & server hardening](docs/architecture/deployment-and-hardening.md) @@ -348,25 +349,25 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 51. [ADR 0017 — OpenAI Responses API adapter](docs/adr/0017-openai-responses-api.md) 52. [Memory — cross-session recall & consolidation](docs/architecture/memory.md) 53. [ADR 0025 — mecatui UX Discoverability](docs/adr/0025-ux-discoverability.md) -54. [ADR 0020 — Diagnostics, audit, and the global-slog ban](docs/adr/0020-diagnostics.md) -55. [ADR 0026 — Clipboard Image Paste](docs/adr/0026-clipboard-image-paste.md) -56. [The ports (engine/port)](docs/architecture/ports.md) -57. [The API surface](docs/architecture/api-surface.md) -58. [Subagents & teams](docs/architecture/subagents-and-teams.md) -59. [ADR 0214 — Execution-environment persistence and reattachment](docs/adr/0214-environment-persistence.md) -60. [ADR 0013 — Agent definitions (Tier 1)](docs/adr/0013-agent-definitions.md) -61. [ADR 0108 — Read skill assets on demand by logical name](docs/adr/0108-on-demand-logical-skill-assets.md) -62. [Context management & the compaction cascade](docs/architecture/context-and-compaction.md) -63. [ADR 0086 — Remove the output-economy control surface](docs/adr/0086-remove-output-economy-control.md) -64. [ADR 0078 — MCP typed tool results](docs/adr/0078-mcp-typed-tool-results.md) -65. [Cloud-Native Harness Kit — definition](docs/cloud-native-harness-kit.md) -66. [ADR 0250 — Durable cursors and the session watch transport](docs/adr/0250-durable-cursors-and-watch.md) +54. [ADR 0250 — Durable cursors and the session watch transport](docs/adr/0250-durable-cursors-and-watch.md) +55. [ADR 0020 — Diagnostics, audit, and the global-slog ban](docs/adr/0020-diagnostics.md) +56. [ADR 0026 — Clipboard Image Paste](docs/adr/0026-clipboard-image-paste.md) +57. [The ports (engine/port)](docs/architecture/ports.md) +58. [The API surface](docs/architecture/api-surface.md) +59. [Subagents & teams](docs/architecture/subagents-and-teams.md) +60. [ADR 0214 — Execution-environment persistence and reattachment](docs/adr/0214-environment-persistence.md) +61. [ADR 0013 — Agent definitions (Tier 1)](docs/adr/0013-agent-definitions.md) +62. [ADR 0108 — Read skill assets on demand by logical name](docs/adr/0108-on-demand-logical-skill-assets.md) +63. [Context management & the compaction cascade](docs/architecture/context-and-compaction.md) +64. [ADR 0086 — Remove the output-economy control surface](docs/adr/0086-remove-output-economy-control.md) +65. [ADR 0078 — MCP typed tool results](docs/adr/0078-mcp-typed-tool-results.md) +66. [Cloud-Native Harness Kit — definition](docs/cloud-native-harness-kit.md) 67. [ADR 0059 — Scheduled tasks: durable registry, tick loop, leader-lease, claim-before-fire at-most-once](docs/adr/0059-scheduled-tasks.md) -68. [ADR 0254 — Dedicated session debugger and per-instance admin transport](docs/adr/0254-session-debugger-admin-transport.md) -69. [ADR 0095 — Root-aware project trust](docs/adr/0095-root-aware-project-trust.md) +68. [ADR 0095 — Root-aware project trust](docs/adr/0095-root-aware-project-trust.md) +69. [ADR 0254 — Dedicated session debugger and per-instance admin transport](docs/adr/0254-session-debugger-admin-transport.md) 70. [Contributing to mecatl](CONTRIBUTING.md) -71. [ADR 0049 — Remove the guardrails per-session checker call-count cap](docs/adr/0049-guardrails-remove-maxchecks.md) -72. [11. Configuration](docs/usage/configuration.md) +71. [11. Configuration](docs/usage/configuration.md) +72. [ADR 0049 — Remove the guardrails per-session checker call-count cap](docs/adr/0049-guardrails-remove-maxchecks.md) 73. [ADR 0030 — Layered model-selection heuristics](docs/adr/0030-model-selection-heuristics.md) 74. [ADR 0256 — Target-bound related evidence and approval-gated reporting](docs/adr/0256-session-debugger-evidence-and-reporting.md) 75. [ADR 0010 — Semantic memory recall: BM25 shipped, semantic deferred](docs/adr/0010-semantic-memory-recall.md) @@ -385,18 +386,18 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 88. [ADR 0206 — Ship reusable OIDC caller identity as an opt-in module](docs/adr/0206-oidc-authn-module.md) 89. [9. The gRPC API](docs/usage/grpc-api.md) 90. [ADR 0006 — v1 Implementation step-chain](docs/adr/0006-v1-step-chain.md) -91. [Parallelism — fork-join](docs/architecture/parallelism.md) -92. [ADR 0237 — Listener-scoped workspace authority](docs/adr/0237-listener-scoped-workspace-authority.md) +91. [ADR 0237 — Listener-scoped workspace authority](docs/adr/0237-listener-scoped-workspace-authority.md) +92. [Parallelism — fork-join](docs/architecture/parallelism.md) 93. [ADR 0077 — Direct-write writable Subagent (no fork, no merge-back)](docs/adr/0077-direct-write-subagent.md) 94. [ADR 0211 — Execution-environment runtime seam](docs/adr/0211-execution-environment-runtime-seam.md) 95. [ADR 0208 — Execution environments and version-aware file mutation](docs/adr/0208-execution-environment.md) 96. [ADR 0257 — Session debugger incarnation and disclosure hardening](docs/adr/0257-session-debugger-hardening.md) 97. [Agent identity: mecatl as its own SPIFFE trust domain](docs/agent-identity-model.md) 98. [File-based permission config (.mecatl/settings.yaml)](docs/usage/permissions-config.md) -99. [ADR 0007 — Twelve agentic-harness patterns: pluggability audit](docs/adr/0007-twelve-patterns-audit.md) -100. [6. Workspace trust & posture](docs/usage/workspace-trust.md) -101. [ADR 0008 — Memory on by default in the embedded mecatui server](docs/adr/0008-memory-on-by-default.md) -102. [ADR 0071 — Seamless model switch: always keep the conversation](docs/adr/0071-seamless-model-switch.md) +99. [6. Workspace trust & posture](docs/usage/workspace-trust.md) +100. [ADR 0007 — Twelve agentic-harness patterns: pluggability audit](docs/adr/0007-twelve-patterns-audit.md) +101. [ADR 0071 — Seamless model switch: always keep the conversation](docs/adr/0071-seamless-model-switch.md) +102. [ADR 0008 — Memory on by default in the embedded mecatui server](docs/adr/0008-memory-on-by-default.md) 103. [ADR 0062 — Out-of-band approve-once for guardrail blocks](docs/adr/0062-guardrails-approve-once.md) 104. [ADR 0212 — Enforce caller ownership at every application access path](docs/adr/0212-caller-ownership-enforcement.md) 105. [ADR 0093 — Ship the real LLM provider adapters as opt-in Go modules under provider/](docs/adr/0093-provider-modules.md) @@ -406,12 +407,12 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 109. [7. Skills, soul, user model](docs/usage/skills-soul-usermodel.md) 110. [ADR 0055 — Reasoning-effort knob](docs/adr/0055-reasoning-effort.md) 111. [ADR 0060 — Add Bash to the default guardrail rule set with a read-only pre-filter](docs/adr/0060-guardrails-bash-default.md) -112. [ADR 0042 — Taxonomy-gated subagent model router (enable by config, not a flag)](docs/adr/0042-taxonomy-gated-model-router.md) -113. [ADR 0001 — Agent Client Protocol (ACP) adapter](docs/adr/0001-acp-adapter.md) -114. [ADR 0248 — SDK compatibility discovery and the typed error contract](docs/adr/0248-sdk-compatibility-and-error-contract.md) -115. [ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat](docs/adr/0075-fire-result-delivery.md) -116. [ADR 0056 — MCP client: reconnect on connection drop](docs/adr/0056-mcp-client-reconnect.md) -117. [ADR 0249 — Durable run identity: a host-minted run_id](docs/adr/0249-durable-run-identity.md) +112. [ADR 0248 — SDK compatibility discovery and the typed error contract](docs/adr/0248-sdk-compatibility-and-error-contract.md) +113. [ADR 0249 — Durable run identity: a host-minted run_id](docs/adr/0249-durable-run-identity.md) +114. [ADR 0042 — Taxonomy-gated subagent model router (enable by config, not a flag)](docs/adr/0042-taxonomy-gated-model-router.md) +115. [ADR 0001 — Agent Client Protocol (ACP) adapter](docs/adr/0001-acp-adapter.md) +116. [ADR 0075 — Fire-result delivery: a scheduled fire reports back into the originating chat](docs/adr/0075-fire-result-delivery.md) +117. [ADR 0056 — MCP client: reconnect on connection drop](docs/adr/0056-mcp-client-reconnect.md) 118. [The domain model](docs/architecture/domain-model.md) 119. [ADR 0032 — First-class worktree binding for a session](docs/adr/0032-worktree-binding.md) 120. [10. The HTTP / SSE API](docs/usage/http-sse-api.md) @@ -420,8 +421,8 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 123. [ADR 0235 — Scoped private HTTPS OIDC transport](docs/adr/0235-scoped-private-https-oidc-transport.md) 124. [ADR 0215 — OpenAI subscription with a manual access token](docs/adr/0215-openai-subscription-manual-token.md) 125. [ADR 0082 — Factory MCP wiring for the one-shot mains](docs/adr/0082-factory-mcp-wiring.md) -126. [ADR 0046 — Guardrails slot enables (configure = enable)](docs/adr/0046-guardrails-slot-enable.md) -127. [ADR 0111 — Hardened publication and recovery for agent-owned skills](docs/adr/0111-hardened-agent-owned-skill-publication.md) +126. [ADR 0111 — Hardened publication and recovery for agent-owned skills](docs/adr/0111-hardened-agent-owned-skill-publication.md) +127. [ADR 0046 — Guardrails slot enables (configure = enable)](docs/adr/0046-guardrails-slot-enable.md) 128. [ADR 0110 — Evaluated, versioned agent-owned skills](docs/adr/0110-evaluated-agent-owned-skills.md) 129. [ADR 0100 — Provider-side conversation prompt caching (Anthropic, OpenAI, OpenRouter, openaichat)](docs/adr/0100-provider-prompt-caching.md) 130. [5. Per-slot models (models:, ADR 0030)](docs/usage/model-routing.md) @@ -432,31 +433,31 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 135. [ADR 0045 — Explicit-bucket latency histograms (zero-config quantiles on /metrics)](docs/adr/0045-explicit-bucket-latency-histograms.md) 136. [ADR 0054 — Reasoning rebalance of the default-tone prompt](docs/adr/0054-reasoning-rebalance-default-prompt.md) 137. [13. Hooks](docs/usage/hooks.md) -138. [ADR 0258 — Cryptographic session and lineage incarnations](docs/adr/0258-cryptographic-session-incarnations.md) -139. [8. mecak8s (Kubernetes-native agent)](docs/usage/mecak8s.md) +138. [8. mecak8s (Kubernetes-native agent)](docs/usage/mecak8s.md) +139. [ADR 0258 — Cryptographic session and lineage incarnations](docs/adr/0258-cryptographic-session-incarnations.md) 140. [ADR 0240 — mecak8s credential reload and chart security](docs/adr/0240-mecak8s-credential-reload-and-chart-security.md) 141. [ADR 0236 — Private HTTPS OIDC issuer admission](docs/adr/0236-private-https-oidc-issuer.md) -142. [ADR 0053 — Flip guardrails default mode from advisory to block](docs/adr/0053-guardrails-default-block.md) -143. [ADR 0232 — Steer-while-running: inject a user message into an in-flight run](docs/adr/0232-steer-while-running.md) -144. [ADR 0070 — Model-visible affordance gate](docs/adr/0070-model-visible-affordance-gate.md) -145. [ADR 0034 — Extend the model router to team members and Parallel branches](docs/adr/0034-team-parallel-model-routing.md) +142. [SDK server enablers — acceptance plan](docs/acceptance/sdk-server-enablers.md) +143. [ADR 0053 — Flip guardrails default mode from advisory to block](docs/adr/0053-guardrails-default-block.md) +144. [ADR 0232 — Steer-while-running: inject a user message into an in-flight run](docs/adr/0232-steer-while-running.md) +145. [ADR 0070 — Model-visible affordance gate](docs/adr/0070-model-visible-affordance-gate.md) 146. [ADR 0081 — RulesSource port for .claude/rules discovery](docs/adr/0081-rules-source-port.md) -147. [ADR 0241 — Canonical untrusted-content fences live in governance](docs/adr/0241-governance-fence-ownership.md) -148. [1. Prerequisites & install](docs/usage/install.md) -149. [ADR 0219 — Qualify the official MCP SDK authorization-code profile](docs/adr/0219-mcp-oauth-sdk-profile.md) -150. [ADR 0106 — Optional completed-trajectory learning seam](docs/adr/0106-optional-learning-seam.md) -151. [Code of Conduct](CODE_OF_CONDUCT.md) -152. [Security Policy](SECURITY.md) -153. [SDK server enablers — acceptance plan](docs/acceptance/sdk-server-enablers.md) -154. [ADR 0234 — Derived delegation authority behind an evaluator port](docs/adr/0234-authority-evaluator-port.md) -155. [ADR 0274 — Remote logout provider budget](docs/adr/0274-remote-mecatui-logout-budget.md) +147. [ADR 0034 — Extend the model router to team members and Parallel branches](docs/adr/0034-team-parallel-model-routing.md) +148. [ADR 0241 — Canonical untrusted-content fences live in governance](docs/adr/0241-governance-fence-ownership.md) +149. [1. Prerequisites & install](docs/usage/install.md) +150. [ADR 0219 — Qualify the official MCP SDK authorization-code profile](docs/adr/0219-mcp-oauth-sdk-profile.md) +151. [ADR 0106 — Optional completed-trajectory learning seam](docs/adr/0106-optional-learning-seam.md) +152. [Code of Conduct](CODE_OF_CONDUCT.md) +153. [Security Policy](SECURITY.md) +154. [ADR 0274 — Remote logout provider budget](docs/adr/0274-remote-mecatui-logout-budget.md) +155. [ADR 0234 — Derived delegation authority behind an evaluator port](docs/adr/0234-authority-evaluator-port.md) 156. [ADR 0201 — Background Bash commands](docs/adr/0201-background-bash.md) 157. [ADR 0107 — Live operator profiles and reversible memory lifecycle](docs/adr/0107-operator-profile-memory-lifecycle.md) 158. [15. Running mecatequi from GitHub Actions](docs/usage/mecatequi-ci.md) 159. [ADR 0225 — Operator settings validation command](docs/adr/0225-operator-settings-validation.md) 160. [ADR 0065 — Conversation fork: peer session from a history snapshot](docs/adr/0065-conversation-fork.md) -161. [ADR 0061 — Human one-shot guardrail override (/guardrail-allow)](docs/adr/0061-guardrails-human-override.md) -162. [ADR 0279 — TypeScript SDK architecture: Connect-ES transport, protobuf-es codegen, in-repo pnpm project](docs/adr/0279-typescript-sdk-architecture.md) +161. [ADR 0279 — TypeScript SDK architecture: Connect-ES transport, protobuf-es codegen, in-repo pnpm project](docs/adr/0279-typescript-sdk-architecture.md) +162. [ADR 0061 — Human one-shot guardrail override (/guardrail-allow)](docs/adr/0061-guardrails-human-override.md) 163. [ADR 0242 — Route unpinned writable named specialists](docs/adr/0242-route-unpinned-writable-named-specialists.md) 164. [ADR 0105 — Built-in bounded WebFetch](docs/adr/0105-built-in-webfetch.md) 165. [ADR 0047 — Absolute path resolution inside the workspace root](docs/adr/0047-absolute-path-resolution.md) @@ -469,9 +470,9 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 172. [ADR 0104 — Session families get a bounded, injective, non-reversible physical name](docs/adr/0104-session-family-physical-naming.md) 173. [ADR 0220 — Adapter-local MCP OAuth controller](docs/adr/0220-mcp-oauth-controller.md) 174. [ADR 0064 — Auto-detect the ToolHive LLM gateway proxy as a native provider](docs/adr/0064-toolhive-llm-gateway-provider.md) -175. [ADR 0202 — Diagnostic-only posture reporting](docs/adr/0202-diagnostic-only-posture-reporting.md) -176. [ADR 0203 — Neutral permanent-provider-error signal](docs/adr/0203-permanent-provider-error-signal.md) -177. [mecak8s — MVP Implementation Plan](docs/design/MECAK8S-PLAN.md) +175. [mecak8s — MVP Implementation Plan](docs/design/MECAK8S-PLAN.md) +176. [ADR 0202 — Diagnostic-only posture reporting](docs/adr/0202-diagnostic-only-posture-reporting.md) +177. [ADR 0203 — Neutral permanent-provider-error signal](docs/adr/0203-permanent-provider-error-signal.md) 178. [ADR 0114 — Configurable learning-trigger policy](docs/adr/0114-configurable-learning-trigger-policy.md) 179. [ADR 0224 — Validated automatic learned-skill activation](docs/adr/0224-validated-automatic-skill-activation.md) 180. [ADR 0057 — MCP client: consume server-initiated notifications](docs/adr/0057-mcp-server-notifications.md) @@ -492,87 +493,88 @@ _Associative trails (Bush 1945): a topologically-valid path through each cluster 195. [16. Live e2e suite](docs/usage/e2e.md) 196. [ADR 0094 — Opt-in project ingestion: two-axis positive grants](docs/adr/0094-opt-in-project-ingestion.md) 197. [ADR 0217 — Session discovery uses durable kind metadata and an authoritative transcript](docs/adr/0217-session-discovery-continuation.md) -198. [ADR 0210 — OpenRouter downstream-provider steering + routing echo](docs/adr/0210-openrouter-downstream-provider-steering.md) -199. [ADR 0040 — Writable Subagent mode + serialized merge-back](docs/adr/0040-writable-subagent-and-serialized-merge.md) -200. [ADR 0044 — Host-supplied askID discriminator (cross-process-reconstructable askID)](docs/adr/0044-host-supplied-askid-discriminator.md) +198. [ADR 0044 — Host-supplied askID discriminator (cross-process-reconstructable askID)](docs/adr/0044-host-supplied-askid-discriminator.md) +199. [ADR 0210 — OpenRouter downstream-provider steering + routing echo](docs/adr/0210-openrouter-downstream-provider-steering.md) +200. [ADR 0040 — Writable Subagent mode + serialized merge-back](docs/adr/0040-writable-subagent-and-serialized-merge.md) 201. [Agent identity, part 2: the outbound hop](docs/agent-identity-outbound.md) 202. [mecatl documentation](docs/README.md) -203. [ADR 0243 — Local JSONL durability boundaries](docs/adr/0243-jsonl-durability.md) -204. [4. Guardrails — LLM-backed tool-content inspection (guardrails:)](docs/usage/guardrails.md) -205. [17. Troubleshooting / FAQ](docs/usage/troubleshooting.md) -206. [ADR 0113 — Operator-configured MCP authentication profiles](docs/adr/0113-operator-mcp-auth-profiles.md) -207. [ADR 0230 — Redis migration ownership and coverage are proved before mutation](docs/adr/0230-redis-migration-atomic-ownership-and-coverage.md) -208. [ADR 0245 — Safe build diagnostics](docs/adr/0245-safe-build-diagnostics.md) -209. [ADR 0233 — Secure external Redis on the shared connection layer](docs/adr/0233-secure-external-redis.md) -210. [Example skills](docs/examples/README.md) -211. [ADR 0067 — OpenAI Chat Completions adapter (OpenCode Go provider)](docs/adr/0067-openai-chat-completions-adapter.md) -212. [ADR 0273 — Remote mecatui OIDC client login](docs/adr/0273-remote-mecatui-oidc-login.md) -213. [ADR 0276 — Count the full request and expose durable manual compaction](docs/adr/0276-full-request-and-manual-compaction.md) -214. [ADR 0035 — Surface the per-delegation model for ALL children, not just routed ones](docs/adr/0035-per-delegation-model-surface.md) -215. [ADR 0285 — Predictable actionable mecatui session handles](docs/adr/0285-predictable-mecatui-session-handles.md) -216. [ADR 0058 — Writable named-specialist Subagent (mode:"read-write" + agent)](docs/adr/0058-writable-named-specialist-subagent.md) -217. [ADR 0052 — Global guardrails checker-down posture toggle](docs/adr/0052-guardrails-checker-down-toggle.md) -218. [ADR 0083 — Expose the routing miss/gate reason on delegation-start events](docs/adr/0083-routing-reason-on-delegation-start.md) -219. [ADR 0223 — Pin MCP transport-error semantics that do not replay rejected calls](docs/adr/0223-mcp-sdk-transport-error-semantics.md) -220. [ADR 0101 — OpenAI reasoning multiplicity: adapter-packed items + bounded encrypted-content repair](docs/adr/0101-openai-reasoning-multiplicity.md) -221. [ADR 0284 — Optional system trust for remote mecatui OIDC issuers](docs/adr/0284-optional-system-trust-remote-oidc.md) -222. [ADR 0286 — Public and private OIDC issuers are two transports, not one policy](docs/adr/0286-issuer-transport-split.md) -223. [ADR 0281 — Managed temporary command leases and deterministic reaping](docs/adr/0281-managed-temporary-command-leases.md) -224. [ADR 0246 — goccy/go-yaml parser migration](docs/adr/0246-goccy-yaml-parser.md) -225. [ADR 0247 — mecatui generated status lines](docs/adr/0247-mecatui-status-line.md) -226. [ADR 0079 — Converge delegation observability on two tiers (bounded previews for Subagent/Parallel)](docs/adr/0079-delegation-observability-convergence.md) -227. [ADR 0229 — Redis migration uses fenced renewable ownership and indexed coverage](docs/adr/0229-redis-migration-fencing.md) -228. [ADR 0252 — HTTP steer endpoint: POST /v1/sessions/{id}/steer](docs/adr/0252-http-steer-endpoint.md) -229. [ADR 0253 — SDK mocking testkit: vendor the proven unary pattern, defer streaming](docs/adr/0253-sdk-mocking-testkit.md) -230. [ADR 0088 — Explicit daemon.yaml (listener topology config)](docs/adr/0088-daemon-config-file.md) -231. [ADR 0097 — Scheduled fires are observable in-flight: a first-class persisted lifecycle stage](docs/adr/0097-scheduled-fire-inflight-state.md) -232. [ADR 0029 — Repo-map Tree-sitter Binding](docs/adr/0029-repomap-tree-sitter.md) -233. [ADR 0068 — Change reasoning effort via conversation fork (keep the transcript)](docs/adr/0068-effort-change-via-fork.md) -234. [ADR 0098 — Telemetry for the headless binaries (mecatequi, mecak8s)](docs/adr/0098-headless-telemetry.md) -235. [ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client](docs/adr/0288-studio-atrium-module.md) -236. [ADR 0282 — Managed workspace scratch cache](docs/adr/0282-managed-workspace-scratch-cache.md) -237. [goccy/go-yaml migration — acceptance plan](docs/acceptance/goccy-yaml-migration.md) -238. [Extensible mecatui status line — acceptance plan](docs/acceptance/mecatui-status-line.md) -239. [ADR 0076 — The schedule manager is store-shaped and pre-Service; the shared catalog carries the Schedule tool](docs/adr/0076-schedule-shared-catalog.md) -240. [Steer-while-running — acceptance plan](docs/acceptance/steer-while-running.md) -241. [ADR 0270 — Activity-gated remote OIDC refresh](docs/adr/0270-activity-gated-remote-oidc-refresh.md) -242. [ADR 0096 — mecatui live-feed reconnect: client-owned backoff + durable catch-up, no server cursor](docs/adr/0096-live-feed-reconnect.md) -243. [Agent Fabric Protocol](docs/agent-fabric-protocol.md) -244. [ADR 0080 — Guardrail-routed path-escape checking (composition pre-check, auto-only)](docs/adr/0080-guardrail-routed-escape-checking.md) -245. [Acceptance plans](docs/acceptance/README.md) -246. [Development process](docs/development-process.md) -247. [engine/api/ — committed public-API snapshots](engine/api/README.md) -248. [ADR 0227 — Dream consolidation safety boundary](docs/adr/0227-dream-consolidation-safety-boundary.md) -249. [Path-escape posture relax — acceptance plan](docs/acceptance/path-escape-posture.md) -250. [ADR 0238 — Operator-defined LLM providers](docs/adr/0238-operator-defined-llm-providers.md) -251. [ADR 0063 — MCP structured results: fail-closed + CallMcpWithQuery](docs/adr/0063-mcp-structured-failclosed-callmcpwithquery.md) -252. [ADR 0072 — The acceptance-plan spine (to-acceptance-plan / plan-orchestrate / ac-trace)](docs/adr/0072-acceptance-plan-spine.md) -253. [ADR 0074 — Many concurrent agent loops per server; the scheduler is single-leader for the tick, pooled (then sharded) for the drive](docs/adr/0074-many-loops-scheduler-shape.md) -254. [ADR 0099 — External transcript import (mecated import)](docs/adr/0099-external-transcript-import.md) -255. [ADR 0103 — mecatui seed prompt (-p/--prompt, --prompt-file)](docs/adr/0103-mecatui-seed-prompt.md) -256. [ADR 0200 — A failed delegated child is resumable (Recover, not refuse)](docs/adr/0200-resume-a-failed-subagent.md) -257. [ADR 0228 — Manual dream review](docs/adr/0228-manual-dream-review.md) -258. [ADR 0271 — Recover remote TUI authentication without replaying ownership-ambiguous work](docs/adr/0271-tui-reauth-owner-recovery.md) -259. [ADR 0272 — Safe target logout for remote mecatui OIDC](docs/adr/0272-remote-mecatui-logout.md) -260. [ADR 0280 — Automatic light theme selection in mecatui](docs/adr/0280-mecatui-light-theme-autodetect.md) -261. [ADR 0283 — Managed delegation-fork lifecycle](docs/adr/0283-managed-delegation-fork-lifecycle.md) -262. [Schedule tool — acceptance plan](docs/acceptance/schedule-tool.md) -263. [Authority evaluator port — acceptance plan](docs/acceptance/authority-evaluator-port.md) -264. [Caller separation — acceptance plan](docs/acceptance/caller-separation.md) -265. [Delegation observability convergence — acceptance plan](docs/acceptance/delegation-observability-convergence.md) -266. [Fire-result delivery — acceptance plan](docs/acceptance/fire-result-delivery.md) -267. [Listener-scoped workspace authority — acceptance plan](docs/acceptance/listener-scoped-workspace-authority.md) -268. [Local mecak8s Kind fixture — acceptance plan](docs/acceptance/mecak8s-kind-fixture.md) -269. [Operator-defined LLM providers — acceptance plan](docs/acceptance/operator-defined-llm-providers.md) -270. [Predictable mecatui session handles — acceptance plan](docs/acceptance/predictable-session-handles.md) -271. [Schedule shared catalog — acceptance plan](docs/acceptance/schedule-shared-catalog.md) -272. [TypeScript SDK core (M1) — acceptance plan](docs/acceptance/sdk-typescript-core.md) -273. [Session continuity UX — acceptance plan](docs/acceptance/session-continuity-ux.md) -274. [Session storage continuity — acceptance plan](docs/acceptance/session-storage-continuity.md) -275. [Spine convergence — acceptance plan](docs/acceptance/spine-convergence.md) -276. [Surface approval migration — acceptance plan](docs/acceptance/surface-approval-migration.md) -277. [Commit style](docs/examples/skills/commit-style/SKILL.md) -278. [Go table-driven tests](docs/examples/skills/go-table-tests/SKILL.md) +203. [ADR 0288 — Studio: the Atrium workspace as mecatl's daemon-only web client](docs/adr/0288-studio-atrium-module.md) +204. [ADR 0243 — Local JSONL durability boundaries](docs/adr/0243-jsonl-durability.md) +205. [4. Guardrails — LLM-backed tool-content inspection (guardrails:)](docs/usage/guardrails.md) +206. [17. Troubleshooting / FAQ](docs/usage/troubleshooting.md) +207. [ADR 0245 — Safe build diagnostics](docs/adr/0245-safe-build-diagnostics.md) +208. [ADR 0230 — Redis migration ownership and coverage are proved before mutation](docs/adr/0230-redis-migration-atomic-ownership-and-coverage.md) +209. [ADR 0113 — Operator-configured MCP authentication profiles](docs/adr/0113-operator-mcp-auth-profiles.md) +210. [ADR 0233 — Secure external Redis on the shared connection layer](docs/adr/0233-secure-external-redis.md) +211. [Example skills](docs/examples/README.md) +212. [ADR 0067 — OpenAI Chat Completions adapter (OpenCode Go provider)](docs/adr/0067-openai-chat-completions-adapter.md) +213. [ADR 0273 — Remote mecatui OIDC client login](docs/adr/0273-remote-mecatui-oidc-login.md) +214. [ADR 0276 — Count the full request and expose durable manual compaction](docs/adr/0276-full-request-and-manual-compaction.md) +215. [ADR 0035 — Surface the per-delegation model for ALL children, not just routed ones](docs/adr/0035-per-delegation-model-surface.md) +216. [ADR 0285 — Predictable actionable mecatui session handles](docs/adr/0285-predictable-mecatui-session-handles.md) +217. [ADR 0058 — Writable named-specialist Subagent (mode:"read-write" + agent)](docs/adr/0058-writable-named-specialist-subagent.md) +218. [ADR 0052 — Global guardrails checker-down posture toggle](docs/adr/0052-guardrails-checker-down-toggle.md) +219. [ADR 0083 — Expose the routing miss/gate reason on delegation-start events](docs/adr/0083-routing-reason-on-delegation-start.md) +220. [ADR 0223 — Pin MCP transport-error semantics that do not replay rejected calls](docs/adr/0223-mcp-sdk-transport-error-semantics.md) +221. [ADR 0101 — OpenAI reasoning multiplicity: adapter-packed items + bounded encrypted-content repair](docs/adr/0101-openai-reasoning-multiplicity.md) +222. [ADR 0284 — Optional system trust for remote mecatui OIDC issuers](docs/adr/0284-optional-system-trust-remote-oidc.md) +223. [ADR 0286 — Public and private OIDC issuers are two transports, not one policy](docs/adr/0286-issuer-transport-split.md) +224. [ADR 0281 — Managed temporary command leases and deterministic reaping](docs/adr/0281-managed-temporary-command-leases.md) +225. [ADR 0246 — goccy/go-yaml parser migration](docs/adr/0246-goccy-yaml-parser.md) +226. [ADR 0247 — mecatui generated status lines](docs/adr/0247-mecatui-status-line.md) +227. [ADR 0079 — Converge delegation observability on two tiers (bounded previews for Subagent/Parallel)](docs/adr/0079-delegation-observability-convergence.md) +228. [ADR 0229 — Redis migration uses fenced renewable ownership and indexed coverage](docs/adr/0229-redis-migration-fencing.md) +229. [ADR 0252 — HTTP steer endpoint: POST /v1/sessions/{id}/steer](docs/adr/0252-http-steer-endpoint.md) +230. [ADR 0253 — SDK mocking testkit: vendor the proven unary pattern, defer streaming](docs/adr/0253-sdk-mocking-testkit.md) +231. [ADR 0088 — Explicit daemon.yaml (listener topology config)](docs/adr/0088-daemon-config-file.md) +232. [ADR 0097 — Scheduled fires are observable in-flight: a first-class persisted lifecycle stage](docs/adr/0097-scheduled-fire-inflight-state.md) +233. [ADR 0029 — Repo-map Tree-sitter Binding](docs/adr/0029-repomap-tree-sitter.md) +234. [ADR 0068 — Change reasoning effort via conversation fork (keep the transcript)](docs/adr/0068-effort-change-via-fork.md) +235. [ADR 0098 — Telemetry for the headless binaries (mecatequi, mecak8s)](docs/adr/0098-headless-telemetry.md) +236. [ADR 0289 — Studio's chat list is the daemon's session store](docs/adr/0289-studio-server-backed-chats.md) +237. [ADR 0282 — Managed workspace scratch cache](docs/adr/0282-managed-workspace-scratch-cache.md) +238. [goccy/go-yaml migration — acceptance plan](docs/acceptance/goccy-yaml-migration.md) +239. [Extensible mecatui status line — acceptance plan](docs/acceptance/mecatui-status-line.md) +240. [ADR 0076 — The schedule manager is store-shaped and pre-Service; the shared catalog carries the Schedule tool](docs/adr/0076-schedule-shared-catalog.md) +241. [Steer-while-running — acceptance plan](docs/acceptance/steer-while-running.md) +242. [ADR 0270 — Activity-gated remote OIDC refresh](docs/adr/0270-activity-gated-remote-oidc-refresh.md) +243. [ADR 0096 — mecatui live-feed reconnect: client-owned backoff + durable catch-up, no server cursor](docs/adr/0096-live-feed-reconnect.md) +244. [Agent Fabric Protocol](docs/agent-fabric-protocol.md) +245. [ADR 0080 — Guardrail-routed path-escape checking (composition pre-check, auto-only)](docs/adr/0080-guardrail-routed-escape-checking.md) +246. [Acceptance plans](docs/acceptance/README.md) +247. [Development process](docs/development-process.md) +248. [engine/api/ — committed public-API snapshots](engine/api/README.md) +249. [ADR 0227 — Dream consolidation safety boundary](docs/adr/0227-dream-consolidation-safety-boundary.md) +250. [Path-escape posture relax — acceptance plan](docs/acceptance/path-escape-posture.md) +251. [ADR 0238 — Operator-defined LLM providers](docs/adr/0238-operator-defined-llm-providers.md) +252. [ADR 0063 — MCP structured results: fail-closed + CallMcpWithQuery](docs/adr/0063-mcp-structured-failclosed-callmcpwithquery.md) +253. [ADR 0072 — The acceptance-plan spine (to-acceptance-plan / plan-orchestrate / ac-trace)](docs/adr/0072-acceptance-plan-spine.md) +254. [ADR 0074 — Many concurrent agent loops per server; the scheduler is single-leader for the tick, pooled (then sharded) for the drive](docs/adr/0074-many-loops-scheduler-shape.md) +255. [ADR 0099 — External transcript import (mecated import)](docs/adr/0099-external-transcript-import.md) +256. [ADR 0103 — mecatui seed prompt (-p/--prompt, --prompt-file)](docs/adr/0103-mecatui-seed-prompt.md) +257. [ADR 0200 — A failed delegated child is resumable (Recover, not refuse)](docs/adr/0200-resume-a-failed-subagent.md) +258. [ADR 0228 — Manual dream review](docs/adr/0228-manual-dream-review.md) +259. [ADR 0271 — Recover remote TUI authentication without replaying ownership-ambiguous work](docs/adr/0271-tui-reauth-owner-recovery.md) +260. [ADR 0272 — Safe target logout for remote mecatui OIDC](docs/adr/0272-remote-mecatui-logout.md) +261. [ADR 0280 — Automatic light theme selection in mecatui](docs/adr/0280-mecatui-light-theme-autodetect.md) +262. [ADR 0283 — Managed delegation-fork lifecycle](docs/adr/0283-managed-delegation-fork-lifecycle.md) +263. [Schedule tool — acceptance plan](docs/acceptance/schedule-tool.md) +264. [Authority evaluator port — acceptance plan](docs/acceptance/authority-evaluator-port.md) +265. [Caller separation — acceptance plan](docs/acceptance/caller-separation.md) +266. [Delegation observability convergence — acceptance plan](docs/acceptance/delegation-observability-convergence.md) +267. [Fire-result delivery — acceptance plan](docs/acceptance/fire-result-delivery.md) +268. [Listener-scoped workspace authority — acceptance plan](docs/acceptance/listener-scoped-workspace-authority.md) +269. [Local mecak8s Kind fixture — acceptance plan](docs/acceptance/mecak8s-kind-fixture.md) +270. [Operator-defined LLM providers — acceptance plan](docs/acceptance/operator-defined-llm-providers.md) +271. [Predictable mecatui session handles — acceptance plan](docs/acceptance/predictable-session-handles.md) +272. [Schedule shared catalog — acceptance plan](docs/acceptance/schedule-shared-catalog.md) +273. [TypeScript SDK core (M1) — acceptance plan](docs/acceptance/sdk-typescript-core.md) +274. [Session continuity UX — acceptance plan](docs/acceptance/session-continuity-ux.md) +275. [Session storage continuity — acceptance plan](docs/acceptance/session-storage-continuity.md) +276. [Spine convergence — acceptance plan](docs/acceptance/spine-convergence.md) +277. [Surface approval migration — acceptance plan](docs/acceptance/surface-approval-migration.md) +278. [Commit style](docs/examples/skills/commit-style/SKILL.md) +279. [Go table-driven tests](docs/examples/skills/go-table-tests/SKILL.md) ## Known gaps diff --git a/studio/knip.ts b/studio/knip.ts index db5052d3f6..a2f0289e80 100644 --- a/studio/knip.ts +++ b/studio/knip.ts @@ -29,8 +29,6 @@ const config: KnipConfig = { // (these leave the list as their first reached consumers land later in // the stacked series: lucide-react/cmdk/tooltip with the workspace shell, // select/switch/tabs with the surface pages) - "@radix-ui/react-alert-dialog", - "@radix-ui/react-dropdown-menu", "@radix-ui/react-label", "@radix-ui/react-scroll-area", "@radix-ui/react-select", diff --git a/studio/package-lock.json b/studio/package-lock.json index 24f58a5de4..128dac77e4 100644 --- a/studio/package-lock.json +++ b/studio/package-lock.json @@ -25,6 +25,12 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", + "@tiptap/extension-mention": "^3.29.2", + "@tiptap/extension-placeholder": "^3.29.2", + "@tiptap/pm": "^3.27.1", + "@tiptap/react": "^3.27.1", + "@tiptap/starter-kit": "^3.27.1", + "@tiptap/suggestion": "^3.29.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "^1.1.1", @@ -35,12 +41,16 @@ "react": "19.2.4", "react-dom": "19.2.4", "react-hook-form": "^7.72.1", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "server-only": "^0.0.1", + "shiki": "^4.4.3", "sonner": "^2.0.7", "tailwind-merge": "3.5.0" }, "devDependencies": { "@biomejs/biome": "2.4.10", + "@playwright/test": "^1.56.1", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", @@ -1949,6 +1959,22 @@ "win32" ] }, + "node_modules/@playwright/test": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.62.1.tgz", + "integrity": "sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", @@ -3237,6 +3263,106 @@ "dev": true, "license": "MIT" }, + "node_modules/@shikijs/core": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.4.3.tgz", + "integrity": "sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==", + "license": "MIT", + "dependencies": { + "@shikijs/primitive": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5", + "hast-util-to-html": "^9.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", + "integrity": "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.6" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", + "integrity": "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/langs": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.4.3.tgz", + "integrity": "sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/primitive": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.4.3.tgz", + "integrity": "sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/themes": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.4.3.tgz", + "integrity": "sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "4.4.3" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/types": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.4.3.tgz", + "integrity": "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -3612,1219 +3738,2883 @@ "@testing-library/dom": ">=7.21.4" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, + "node_modules/@tiptap/core": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.31.0.tgz", + "integrity": "sha512-SY3yE2ACharFvk0BKgotV6SbwV8R4tN5fGtWOM6deY4JNF3v6CXOD78RFwjs2HSohGWBRyJVPDvrX9nfi8Nsxg==", "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "3.31.0" } }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.31.0.tgz", + "integrity": "sha512-E1EcYp28NOEKLkMqf9Z4t/ZjVO5hOX/c5r9nu/sfp5+KePj4TaxGDcruijP5DYJuKxlsZx2lr6/lldiK9KilXw==", "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.20.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", - "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", - "dev": true, + "node_modules/@tiptap/extension-bold": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.31.0.tgz", + "integrity": "sha512-KB3V3/8/XSrXNM0m7Svgg8xjwG/mr9nYh0krGznmDK1eArxj2GQd5h2g0QiBy8e+G//NMEbWQxSKbhKUKz7aoQ==", "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/@types/react": { - "version": "19.2.18", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", - "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", - "devOptional": true, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.31.0.tgz", + "integrity": "sha512-CNuyfYH2KfcREASRh280UkGqYTwuKlMNcyBR7KsZrpzAVwFVo9D6qMkE6/q7I8mNMt2+/Xebe9UFZIYnVccCJA==", "license": "MIT", + "optional": true, "dependencies": { - "csstype": "^3.2.2" + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/@types/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", - "devOptional": true, + "node_modules/@tiptap/extension-bullet-list": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.31.0.tgz", + "integrity": "sha512-jLrzWDzBhfeOK/idnrpyncTuRamBNGCscDC7omG1S+CD3CFuDrp5Ws2tcv5fgASt2Vc/f9Y9zC+gOPkeIEpYFA==", "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, "peerDependencies": { - "@types/react": "^19.2.0" + "@tiptap/extension-list": "3.31.0" } }, - "node_modules/@vitejs/plugin-react": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", - "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", - "dev": true, + "node_modules/@tiptap/extension-code": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.31.0.tgz", + "integrity": "sha512-QWo9oItNzyzNMXoCOG4/PiCNYoTjhUKtI5NE7iRJhd9op3KsLoyIO+og+P+0ORxG59Ic76SkXl2/lILaVsB48g==", "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "oxc-transform-react": "^0.145.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "oxc-transform-react": { - "optional": true - } + "@tiptap/core": "3.31.0" } }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", - "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", - "dev": true, + "node_modules/@tiptap/extension-code-block": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.31.0.tgz", + "integrity": "sha512-y/A6N96gBPjEHqVYZ3u/yO/Anyo92iDWgYqShMXAvHprn2Ipmn32wIkX05Kv983fa6j6ySXF7A1nJdHcGCl5aQ==", "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.11", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@vitest/browser": "4.1.11", - "vitest": "4.1.11" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/@vitest/expect": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", - "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", - "dev": true, + "node_modules/@tiptap/extension-document": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.31.0.tgz", + "integrity": "sha512-C2eDFoBlcRAb4sgbc9ZH+mrFFbjIS9VyoERGeDum3QRdqLEKU1h14Th7isHpGQzTvTywhXQxqXC64mgeSGXLsA==", "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.11", - "@vitest/utils": "4.1.11", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", - "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", - "dev": true, + "node_modules/@tiptap/extension-dropcursor": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.31.0.tgz", + "integrity": "sha512-hIZOPfM0sGkAhPV69IK1zWMbWBd3wmhS0XCKBRcXUWrE2MW4kkaSyYprSFqtqFuUa3lVIEFrlME1WOEBJ2/2rg==", "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.11", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "@tiptap/extensions": "3.31.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", - "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", - "dev": true, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.31.0.tgz", + "integrity": "sha512-NFwv1Kah3zGLmjj53Z+pA2584GdgMDClgvebJPqSunsSdHwHlFwfu+Op1u73xuEYOd6wGgCgzzDZ3V4CKwkJqg==", "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, + "optional": true, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/@vitest/runner": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", - "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", - "dev": true, + "node_modules/@tiptap/extension-gapcursor": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.31.0.tgz", + "integrity": "sha512-TY1S9D9x86iIHiPiDU4KtRmLYTgE6RKa54jXubFyekUYFmgNfHuUunWmpK/Rvy6tkG1CKHNL7X+wdo8QXi+hcw==", "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.11", - "pathe": "^2.0.3" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, + "peerDependencies": { + "@tiptap/extensions": "3.31.0" + } + }, + "node_modules/@tiptap/extension-hard-break": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.31.0.tgz", + "integrity": "sha512-e3e3YwTSxzO1bYalphs3A3zr+nYRWASMKFPzviO0C2P7vKE4kT4rvCYCWuUxIpfit8Vnh6TFVIkhrQ/6IFlDzw==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", - "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", - "dev": true, + "node_modules/@tiptap/extension-heading": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.31.0.tgz", + "integrity": "sha512-ryQnvq2iUW2IwjbLniGlRZliNA5YserRTX2mCKyQcv8z/u062J01JDLlX73o47tu4DjuBzmkx6t5SsiXNODRcg==", "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.11", - "@vitest/utils": "4.1.11", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, + "peerDependencies": { + "@tiptap/core": "3.31.0" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.31.0.tgz", + "integrity": "sha512-RTKLnuEmGJOlxzLOmKo85hstXfuLlg+zQSKAggdD6BrSTDR5fdPZXnZTb/gDCo6chCR5BoKHu7Xv1spnPzRmEw==", + "license": "MIT", "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/@vitest/spy": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", - "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", - "dev": true, + "node_modules/@tiptap/extension-italic": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.31.0.tgz", + "integrity": "sha512-8Top75aMS9feCtKYADWOYLrJjx3SGvazETWZdEwDYDGZtEOB7q2+FxcRu2xi+K6Lx9LhzxS9ChULtNdBuRevkQ==", "license": "MIT", "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/@vitest/utils": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", - "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", - "dev": true, + "node_modules/@tiptap/extension-link": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.31.0.tgz", + "integrity": "sha512-arzZYxyf6in2BzdB3GR53VhSlE3ZwmY8CflXRo1xHpSpxq393myLP6FnaxLPxcfcScfOOsaYh2KAED0HJoBZfA==", "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.11", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" + "linkifyjs": "^4.3.3" }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "node_modules/@tiptap/extension-list": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.31.0.tgz", + "integrity": "sha512-cED2TPewS3CtzZUdCZNG/qbc6vHJunKRzkFiT8x3RF+pmtIQlrPRH5Y9sbkzLt3yg0ua5KMGFtBH37AHaKUFDw==", "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, + "node_modules/@tiptap/extension-list-item": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.31.0.tgz", + "integrity": "sha512-ayZa+KEMigJis0s8n3X3pqRxzsMuom9JoqE6byJ5NP2oaI2HOQ95yfcuILNKukg1nXG8vkgifEsXxjXz4O2o2g==", "license": "MIT", - "engines": { - "node": ">=10" - }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.0" } }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "node_modules/@tiptap/extension-list-keymap": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.31.0.tgz", + "integrity": "sha512-bmZZmQyH0PibpIUA2thA3Cgm82cnrcC4oU4eNqiaoUqrva1NuOIqb71L2Br9keBnmtQKEtkzdODs4x+eI53kBg==", "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, - "engines": { - "node": ">=10" + "peerDependencies": { + "@tiptap/extension-list": "3.31.0" } }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" + "node_modules/@tiptap/extension-mention": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-3.31.0.tgz", + "integrity": "sha512-BqCFfRM0oEmu8XgOFgfhusK5RJbLF8WG4BnKdgaKCVUNLGqbqXvQgKXcwLKO530PmoWjfD4sUdlusV4ZaRnhMg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0", + "@tiptap/suggestion": "3.31.0" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, + "node_modules/@tiptap/extension-ordered-list": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.31.0.tgz", + "integrity": "sha512-KLCax7pI/3JTCkMY01n2+CXwZRPPsIVR1fvSmkFMryUPonkkMYWSUlXCuYKvF22+kM0gU0kiXsVjqsn/YFdeDg==", "license": "MIT", - "engines": { - "node": ">=12" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extension-list": "3.31.0" } }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", - "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", - "dev": true, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.31.0.tgz", + "integrity": "sha512-78l9D/jpKvvWXrBBzYPfgqwonBY76+1MMgSovkPffIbya+jaYDJdZhIpeCZXtSBDnw8cYE/rheAeusN1QG3SVQ==", "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/babel-plugin-react-compiler": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", - "devOptional": true, + "node_modules/@tiptap/extension-placeholder": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.31.0.tgz", + "integrity": "sha512-zH1Z8xZX72P7wZ3HD6fUGJ7XTVWMgyN5Sy+OiD3tuuxfr/oUvS6ykaoGBdHGTMBdrXZpp+GfAcch1MrvLGn8oA==", "license": "MIT", - "dependencies": { - "@babel/types": "^7.26.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/extensions": "3.31.0" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.20", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", - "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "node_modules/@tiptap/extension-strike": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.31.0.tgz", + "integrity": "sha512-M1X94ve0TNU7LrHCDn6nKf6vM8PEaKQt3FLTwhTIw5uTbx2Imw+uyRrySDdxLWHY/ul8x17I2w9MVLqK/D+Zvw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, - "engines": { - "node": ">=6.0.0" + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, + "node_modules/@tiptap/extension-text": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.31.0.tgz", + "integrity": "sha512-K+8nIKgNNBiL1+AarcuzvPoRpjuFk5qOe9MM/n9O3HyTTLeWNfRV/zFifcpSoHmhf0QvEAXqmnxDiIpyCpy9+g==", "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001810", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", - "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, + "node_modules/@tiptap/extension-underline": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.31.0.tgz", + "integrity": "sha512-B4VK5nu0UdKl7OiMvIcjDPMYmhe8+v1B763Is/GzPSJOMstUHRqAqPx2AsPla6A4UGuZwTx//ajVa5QAGionZQ==", "license": "MIT", - "engines": { - "node": ">=18" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0" } }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, + "node_modules/@tiptap/extensions": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.31.0.tgz", + "integrity": "sha512-WOv9Y1VjhzAYycrIGPygwmLkoO/UdFYFE5uDWN+FJWMui5sLmLrmfKtOOYzeWIevxomFGix/wl5hqaVGIev1+g==", "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.31.0.tgz", + "integrity": "sha512-JGPUSPLDFPjgFZHcNSghVJmEfvYp2SRPZQ1K8AZOuviEsUNvkYmJsBdudeYwUYNzYO38L4Yy0G6UMSuniBDIMg==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.4.1", + "prosemirror-commands": "^1.7.1", + "prosemirror-dropcursor": "^1.8.2", + "prosemirror-gapcursor": "^1.4.1", + "prosemirror-history": "^1.5.0", + "prosemirror-inputrules": "^1.5.1", + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.11", + "prosemirror-schema-list": "^1.5.1", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.5", + "prosemirror-transform": "^1.12.0", + "prosemirror-view": "^1.41.9" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", + "node_modules/@tiptap/react": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.31.0.tgz", + "integrity": "sha512-s3NfUSqXfkGpjQ6iL8In7jdTIlIdZ+Ll4bO30C54kokiNq6t8mct7JPt3qGeCCeH3FTqNMLzaBDAU8EfcZw+PQ==", + "license": "MIT", "dependencies": { - "clsx": "^2.1.1" + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" }, "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.31.0", + "@tiptap/extension-floating-menu": "^3.31.0" + }, + "peerDependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/starter-kit": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.31.0.tgz", + "integrity": "sha512-P80nhDEsBf6EMTaRb/ld3BaQW7iqITPR6BiIjOjbO3QDivCz/rGDKSS6q4ngsXfXOpSBc/clo/g0OyAdA8+Q4Q==", + "license": "MIT", + "dependencies": { + "@tiptap/core": "3.31.0", + "@tiptap/extension-blockquote": "3.31.0", + "@tiptap/extension-bold": "3.31.0", + "@tiptap/extension-bullet-list": "3.31.0", + "@tiptap/extension-code": "3.31.0", + "@tiptap/extension-code-block": "3.31.0", + "@tiptap/extension-document": "3.31.0", + "@tiptap/extension-dropcursor": "3.31.0", + "@tiptap/extension-gapcursor": "3.31.0", + "@tiptap/extension-hard-break": "3.31.0", + "@tiptap/extension-heading": "3.31.0", + "@tiptap/extension-horizontal-rule": "3.31.0", + "@tiptap/extension-italic": "3.31.0", + "@tiptap/extension-link": "3.31.0", + "@tiptap/extension-list": "3.31.0", + "@tiptap/extension-list-item": "3.31.0", + "@tiptap/extension-list-keymap": "3.31.0", + "@tiptap/extension-ordered-list": "3.31.0", + "@tiptap/extension-paragraph": "3.31.0", + "@tiptap/extension-strike": "3.31.0", + "@tiptap/extension-text": "3.31.0", + "@tiptap/extension-underline": "3.31.0", + "@tiptap/extensions": "3.31.0", + "@tiptap/pm": "3.31.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" } }, - "node_modules/cmdk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", - "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "node_modules/@tiptap/suggestion": { + "version": "3.31.0", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-3.31.0.tgz", + "integrity": "sha512-p/m8K4jmF+b5JdRcZ5onOKxJf3dsTcXhnV6qAjDu1xrvpY8lXZe4DXgtuAUvh5Q4Msmq6IYuMAvleR4WAkIXIQ==", "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.6", - "@radix-ui/react-id": "^1.1.0", - "@radix-ui/react-primitive": "^2.0.2" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "3.31.0", + "@tiptap/pm": "3.31.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + "tslib": "^2.4.0" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true, "license": "MIT" }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "@types/ms": "*" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "license": "MIT" }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT" + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } }, - "node_modules/enhanced-resolve": { - "version": "5.24.5", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", - "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", - "dev": true, + "node_modules/@types/hast": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz", + "integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" - }, - "engines": { - "node": ">=10.13.0" + "@types/unist": "*" } }, - "node_modules/entities": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", - "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" } }, - "node_modules/es-module-lexer": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", - "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", - "dev": true, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0" + "undici-types": "~6.21.0" } }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" } }, - "node_modules/fd-package-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", - "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", - "dev": true, + "node_modules/@types/react-dom": { + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==", "license": "MIT", - "dependencies": { - "walk-up-path": "^4.0.0" + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.4.0.tgz", + "integrity": "sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", "dev": true, "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, "engines": { - "node": ">=12.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "picomatch": "^3 || ^4" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" }, "peerDependenciesMeta": { - "picomatch": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { "optional": true } } }, - "node_modules/formatly": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.7.0.tgz", - "integrity": "sha512-7CXJtIIA0zy/u12StsYk25qVKxvdLA2ep2sTNxK3ov0mGNIIDqIvAXDSgTnAfDJFsPfWjuz0WjfYSdpvnLA5Tg==", + "node_modules/@vitest/coverage-v8": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz", + "integrity": "sha512-8MVGEFnJIcdGjcbfKmeq8z0pZHH0JlVtoVZH9Q/qwUp6wyFnEJUBMrw9DCaj+ra3vShGmhavjalMIhPNxZAUcw==", "dev": true, "license": "MIT", "dependencies": { - "fd-package-json": "^2.0.0", - "package-manager-detector": "^1.8.0" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.11", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, - "bin": { - "formatly": "bin/index.mjs" + "funding": { + "url": "https://opencollective.com/vitest" }, - "engines": { - "node": ">=18.3.0" + "peerDependencies": { + "@vitest/browser": "4.1.11", + "vitest": "4.1.11" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, - "hasInstallScript": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/get-tsconfig": { - "version": "4.14.3", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", - "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "dev": true, "license": "MIT", "dependencies": { - "resolve-pkg-maps": "^1.0.0" + "tinyrainbow": "^3.1.0" }, "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + "url": "https://opencollective.com/vitest" } }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, - "license": "ISC" + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", "dev": true, "license": "MIT", "dependencies": { - "@exodus/bytes": "^1.6.0" + "@vitest/pretty-format": "4.1.11", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" + "tslib": "^2.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-reports": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, - "license": "BSD-3-Clause", + "license": "Apache-2.0", "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" } }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", "dev": true, "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, - "node_modules/jsdom": { - "version": "29.1.1", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", - "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.20.tgz", + "integrity": "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^5.1.11", - "@asamuzakjp/dom-selector": "^7.1.1", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.3", - "@exodus/bytes": "^1.15.0", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.3.5", - "parse5": "^8.0.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.25.0", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", - "xml-name-validator": "^5.0.0" + "require-from-string": "^2.0.2" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + "node": ">=6" + } + }, + "node_modules/cmdk": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", + "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-id": "^1.1.0", + "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { - "canvas": "^3.0.0" + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "canvas": { + "supports-color": { "optional": true } } }, - "node_modules/knip": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-6.34.0.tgz", - "integrity": "sha512-bbHIrnGspYwe4EBPjjx+lvkUor0F2qfKQc5BPzPI4SOAImYA+k2ueVpIcF7d/W1LEVT7XoJUPt6zELeGZhXBgA==", + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/webpro" + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.24.5", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", + "integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/formatly": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.7.0.tgz", + "integrity": "sha512-7CXJtIIA0zy/u12StsYk25qVKxvdLA2ep2sTNxK3ov0mGNIIDqIvAXDSgTnAfDJFsPfWjuz0WjfYSdpvnLA5Tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fd-package-json": "^2.0.0", + "package-manager-detector": "^1.8.0" + }, + "bin": { + "formatly": "bin/index.mjs" + }, + "engines": { + "node": ">=18.3.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/knip": { + "version": "6.34.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.34.0.tgz", + "integrity": "sha512-bbHIrnGspYwe4EBPjjx+lvkUor0F2qfKQc5BPzPI4SOAImYA+k2ueVpIcF7d/W1LEVT7XoJUPt6zELeGZhXBgA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.7.0", + "get-tsconfig": "4.14.3", + "jiti": "^2.7.0", + "oxc-parser": "^0.147.0", + "oxc-resolver": "11.24.2", + "picomatch": "^4.0.7", + "smol-toml": "^1.8.0", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.10", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "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" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz", + "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lucide-react": { + "version": "0.577.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", + "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" }, { - "type": "opencollective", - "url": "https://opencollective.com/knip" + "type": "OpenCollective", + "url": "https://opencollective.com/unified" } ], - "license": "ISC", + "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "formatly": "^0.7.0", - "get-tsconfig": "4.14.3", - "jiti": "^2.7.0", - "oxc-parser": "^0.147.0", - "oxc-resolver": "11.24.2", - "picomatch": "^4.0.7", - "smol-toml": "^1.8.0", - "strip-json-comments": "5.0.3", - "tinyglobby": "^0.2.17", - "unbash": "^4.0.10", - "yaml": "^2.9.0", - "zod": "^4.4.3" - }, - "bin": { - "knip": "bin/knip.js", - "knip-bun": "bin/knip-bun.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "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" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lru-cache": { - "version": "11.5.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", - "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lucide-react": { - "version": "0.577.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.577.0.tgz", - "integrity": "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "bin": { - "lz-string": "bin/bin.js" + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "micromark-util-types": "^2.0.0" } }, - "node_modules/magicast": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", - "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", - "dev": true, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "source-map-js": "^1.2.1" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" }, "node_modules/min-indent": { "version": "1.0.1", @@ -4840,7 +6630,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -5003,6 +6792,29 @@ "node": ">=12.20.0" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.2.tgz", + "integrity": "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.6.tgz", + "integrity": "sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.2", + "regex": "^6.1.0", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" + }, "node_modules/oxc-parser": { "version": "0.147.0", "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.147.0.tgz", @@ -5078,6 +6890,31 @@ "dev": true, "license": "MIT" }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", @@ -5098,23 +6935,70 @@ "dev": true, "license": "MIT" }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz", + "integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.62.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz", + "integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } }, - "node_modules/picomatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", - "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/postcss": { @@ -5161,6 +7045,155 @@ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/prosemirror-changeset": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.2.tgz", + "integrity": "sha512-ViYrjMSg3YFiXwIhKaluu+/mi3Yrxt6AR8ri14ulTaGcZtXO1CThl7A2gv79qx5fQnOw8woKwyBU2u+9PVCm3w==", + "license": "MIT", + "dependencies": { + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz", + "integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.3.tgz", + "integrity": "sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.11", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.11.tgz", + "integrity": "sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.1.tgz", + "integrity": "sha512-t4F5615FycnCqsX7ShTUs8+jfnwcf46kuFRvSl/3qFx2QTZ4DjgowesLHQXcFP7G//TjesqZG3WtgL7vdyVJyA==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.42.3", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.3.tgz", + "integrity": "sha512-oTN7EtH+CpwxU9NrwEYWd0UZ4JUx7l048l5A2Xppm4p/60isZYLnth9QVQmC3VRIvdrIWCxwZSd+Uz791G31/w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.8", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5215,6 +7248,33 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", @@ -5298,6 +7358,96 @@ "node": ">=8" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -5352,6 +7502,12 @@ "@rolldown/binding-win32-x64-msvc": "1.2.6" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -5440,6 +7596,25 @@ } } }, + "node_modules/shiki": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-4.4.3.tgz", + "integrity": "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "4.4.3", + "@shikijs/engine-javascript": "4.4.3", + "@shikijs/engine-oniguruma": "4.4.3", + "@shikijs/langs": "4.4.3", + "@shikijs/themes": "4.4.3", + "@shikijs/types": "4.4.3", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -5485,6 +7660,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5499,6 +7684,20 @@ "dev": true, "license": "MIT" }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -5525,6 +7724,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -5689,6 +7906,26 @@ "node": ">=20" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5736,6 +7973,93 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -5779,6 +8103,43 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "8.2.2", "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", @@ -6276,6 +8637,12 @@ "vitest": ">=0.26.2" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -6393,6 +8760,16 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/studio/package.json b/studio/package.json index 58cbc25370..33909ed2d3 100644 --- a/studio/package.json +++ b/studio/package.json @@ -15,6 +15,7 @@ "format": "biome format --write", "test": "vitest", "test:coverage": "vitest run --coverage", + "test:e2e": "npm run build && playwright test", "typecheck": "tsc --noEmit", "knip": "knip", "test:server": "npm run build && node --test tests/rendered-html.test.mjs" @@ -37,6 +38,12 @@ "@radix-ui/react-toggle": "^1.1.10", "@radix-ui/react-toggle-group": "^1.1.11", "@radix-ui/react-tooltip": "^1.2.8", + "@tiptap/extension-mention": "^3.29.2", + "@tiptap/extension-placeholder": "^3.29.2", + "@tiptap/pm": "^3.27.1", + "@tiptap/react": "^3.27.1", + "@tiptap/starter-kit": "^3.27.1", + "@tiptap/suggestion": "^3.29.2", "class-variance-authority": "0.7.1", "clsx": "2.1.1", "cmdk": "^1.1.1", @@ -47,12 +54,16 @@ "react": "19.2.4", "react-dom": "19.2.4", "react-hook-form": "^7.72.1", + "react-markdown": "^10.1.0", + "remark-gfm": "^4.0.1", "server-only": "^0.0.1", + "shiki": "^4.4.3", "sonner": "^2.0.7", "tailwind-merge": "3.5.0" }, "devDependencies": { "@biomejs/biome": "2.4.10", + "@playwright/test": "^1.56.1", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.9.1", diff --git a/studio/playwright.config.mts b/studio/playwright.config.mts new file mode 100644 index 0000000000..3d57d7875b --- /dev/null +++ b/studio/playwright.config.mts @@ -0,0 +1,47 @@ +import { defineConfig, devices } from "@playwright/test"; + +const BASE_URL = process.env.BASE_URL || "http://127.0.0.1:3300"; +const FIXTURE_PORT = 8099; + +/** + * E2E runs the production build in EXTERNAL mode against the fixture daemon + * (tests/e2e/fixture-daemon.mjs): browser → real proxy tier → fixture wire. + * `npm run test:e2e` builds first. + */ +export default defineConfig({ + testDir: "./tests/e2e", + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + reporter: process.env.CI ? "github" : "list", + timeout: 30_000, + use: { + baseURL: BASE_URL, + trace: "on-first-retry", + screenshot: "only-on-failure", + }, + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: [ + { + command: `node tests/e2e/fixture-daemon.mjs`, + url: `http://127.0.0.1:${FIXTURE_PORT}/v1/models`, + timeout: 15_000, + env: { FIXTURE_DAEMON_PORT: String(FIXTURE_PORT) }, + }, + { + command: `npx next start -p ${new URL(BASE_URL).port}`, + url: BASE_URL, + timeout: 120_000, + env: { + MECATL_BASE_URL: `http://127.0.0.1:${FIXTURE_PORT}`, + MECATL_WORKSPACE: "/workspace/fixture", + MECATL_STUDIO_PUBLIC_ORIGIN: BASE_URL, + }, + }, + ], +}); diff --git a/studio/src/app/workspace/_components/chat-input.test.tsx b/studio/src/app/workspace/_components/chat-input.test.tsx new file mode 100644 index 0000000000..51991d9258 --- /dev/null +++ b/studio/src/app/workspace/_components/chat-input.test.tsx @@ -0,0 +1,21 @@ +import { render } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { ChatInput } from "./chat-input"; + +// jsdom cannot host a live ProseMirror view (TipTap mounts asynchronously and +// owns its own capture-phase handlers), so the editor is stubbed to its +// pre-mount (null) state and this file covers the composer chrome around it. +vi.mock("@tiptap/react", () => ({ + useEditor: () => null, + EditorContent: () =>
, +})); + +describe("ChatInput", () => { + it("renders the composer chrome with the send button disabled while empty", () => { + const { getByLabelText, getByTestId } = render(); + expect(getByTestId("composer-editor")).toBeTruthy(); + expect((getByLabelText("Send message") as HTMLButtonElement).disabled).toBe( + true, + ); + }); +}); diff --git a/studio/src/app/workspace/_components/chat-input.tsx b/studio/src/app/workspace/_components/chat-input.tsx new file mode 100644 index 0000000000..dae0827e19 --- /dev/null +++ b/studio/src/app/workspace/_components/chat-input.tsx @@ -0,0 +1,525 @@ +"use client"; + +import Placeholder from "@tiptap/extension-placeholder"; +import { EditorContent, useEditor } from "@tiptap/react"; +import StarterKit from "@tiptap/starter-kit"; +import type { SuggestionProps } from "@tiptap/suggestion"; +import { ArrowUp, Bot, Check, ChevronDown, Mic } from "lucide-react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + getAgentMentions, + getSlashCommands, +} from "@/features/agent/composer-capabilities"; +import { cn } from "@/lib/utils"; +import { + type ComposerMenuItem, + composerText, + createComposerMentions, + setComposerText, +} from "./composer-mentions"; + +interface ChatInputProps { + placeholder?: string; + rows?: number; + compact?: boolean; + onSend?: (content: string) => void; + disabled?: boolean; + isStreaming?: boolean; + appendText?: string | null; + onAppendConsumed?: () => void; + /** Plain-text seed dropped into an empty composer (e.g. a "next step" chip + that pre-fills a prompt without sending it). Unlike `appendText`, it is + not quoted and replaces rather than appends. */ + initialText?: string | null; + onInitialTextConsumed?: () => void; +} + +/** + * Ghost-style trigger used by dropdowns rendered BELOW the input box + * (matches the "Build / Claude Opus 5 / Default" reference design). + * No pill background; subtle hover; small chevron via the trigger itself. + */ +const GHOST_TRIGGER_CLASS = + "h-7 gap-1 rounded-full px-2.5 text-sm font-normal text-foreground bg-transparent hover:bg-zinc-200 dark:hover:bg-zinc-700 border-0 shadow-none"; + +/** + * Controls whether the agent draws on (and writes to) its long-term memory for + * this conversation. A pill matching the model selector, opening a small On/Off + * menu; on by default. + */ +function MemoryToggle() { + const [on, setOn] = useState(true); + return ( + + + + + e.preventDefault()} + align="start" + className="w-40" + > + {[true, false].map((value) => ( + setOn(value)} + > + + {value ? "On" : "Off"} + + ))} + + + ); +} + +function useVoiceInput(onTranscript: (text: string) => void) { + const [isListening, setIsListening] = useState(false); + const [isSupported, setIsSupported] = useState(false); + const recognitionRef = useRef | null>(null); + + useEffect(() => { + const SR = + typeof window !== "undefined" + ? window.SpeechRecognition || window.webkitSpeechRecognition + : null; + setIsSupported(!!SR); + }, []); + + const toggle = useCallback(() => { + if (isListening) { + recognitionRef.current?.stop(); + setIsListening(false); + return; + } + + const SR = window.SpeechRecognition || window.webkitSpeechRecognition; + if (!SR) return; + + const recognition = new SR(); + recognition.continuous = false; + recognition.interimResults = true; + recognition.lang = "en-US"; + + let finalText = ""; + + recognition.onresult = (event: SpeechRecognitionEvent) => { + let interim = ""; + for (let i = event.resultIndex; i < event.results.length; i++) { + const transcript = event.results[i][0].transcript; + if (event.results[i].isFinal) { + finalText += transcript; + } else { + interim += transcript; + } + } + onTranscript(finalText + interim); + }; + + recognition.onend = () => { + setIsListening(false); + if (finalText) onTranscript(finalText); + }; + + recognition.onerror = () => { + setIsListening(false); + }; + + recognitionRef.current = recognition; + recognition.start(); + setIsListening(true); + }, [isListening, onTranscript]); + + return { isListening, isSupported, toggle }; +} + +type SpeechRecognitionType = new () => { + continuous: boolean; + interimResults: boolean; + lang: string; + onresult: ((event: SpeechRecognitionEvent) => void) | null; + onend: (() => void) | null; + onerror: ((event: Event) => void) | null; + start: () => void; + stop: () => void; +}; + +declare global { + interface Window { + SpeechRecognition: SpeechRecognitionType; + webkitSpeechRecognition: SpeechRecognitionType; + } +} + +const DEFAULT_PLACEHOLDER = + "Pull in tools and expertise by including @agent or +Files"; + +/** Filter the agent list for the `@`-mention menu by handle or name. */ +function agentMenuItems(query: string): ComposerMenuItem[] { + const q = query.toLowerCase(); + return getAgentMentions() + .filter((a) => a.handle.startsWith(q) || a.name.toLowerCase().includes(q)) + .map((a) => ({ + id: a.handle, + label: a.handle, + primary: a.name, + secondary: a.description, + })); +} + +/** Filter the slash-command list for the `/`-command menu by name prefix. */ +function commandMenuItems(query: string): ComposerMenuItem[] { + const q = query.toLowerCase(); + return getSlashCommands() + .filter((c) => c.name.startsWith(q)) + .map((c) => ({ + id: c.name, + label: c.name, + primary: `/${c.name}`, + secondary: c.description, + })); +} + +/** Open autocomplete menu state, mirrored from TipTap's suggestion lifecycle. */ +interface ComposerMenu { + kind: "agent" | "command"; + items: ComposerMenuItem[]; + index: number; + select: (item: ComposerMenuItem) => void; +} + +/** + * Drive the open autocomplete menu from a keydown. Returns true when the key + * was for the menu (arrows move the highlight, Enter/Tab pick the row, Escape + * closes it). Selection is deferred a microtask so the chip insert lands after + * ProseMirror finishes dispatching the key. + */ +function handleMenuNavKey( + event: KeyboardEvent, + menu: ComposerMenu, + setMenu: React.Dispatch>, +): boolean { + if (menu.items.length === 0) return false; + switch (event.key) { + case "ArrowDown": + setMenu((m) => (m ? { ...m, index: (m.index + 1) % m.items.length } : m)); + return true; + case "ArrowUp": + setMenu((m) => + m + ? { ...m, index: (m.index - 1 + m.items.length) % m.items.length } + : m, + ); + return true; + case "Enter": + case "Tab": { + const item = menu.items[menu.index]; + if (item) queueMicrotask(() => menu.select(item)); + return true; + } + case "Escape": + setMenu(null); + return true; + default: + return false; + } +} + +export function ChatInput({ + placeholder: placeholderProp, + compact = false, + onSend, + disabled = false, + appendText, + onAppendConsumed, + initialText, + onInitialTextConsumed, +}: ChatInputProps) { + const placeholder = placeholderProp ?? DEFAULT_PLACEHOLDER; + // Plain-text mirror of the editor, kept in sync via onUpdate. Used only for + // "is there something to send" checks; the editor document is the source of + // truth for the message itself. + const [text, setText] = useState(""); + + // Autocomplete menu, mirrored from TipTap's suggestion lifecycle so we can + // render the same full-width popover the pre-TipTap composer used. `menuRef` + // gives the suggestion keydown handler a synchronous read of current state. + const [menu, setMenu] = useState(null); + + // Wire each mention's TipTap suggestion to the shared menu state. `command` + // (from the suggestion props) inserts the atomic chip at the trigger range. + // Keyboard navigation is handled by the editor's handleKeyDown (below), not + // here, so the suggestion's own onKeyDown is intentionally omitted. + const makeRender = useCallback( + (kind: "agent" | "command") => () => ({ + onStart: (props: SuggestionProps) => { + setMenu({ + kind, + items: props.items, + index: 0, + select: (item) => props.command(item), + }); + }, + onUpdate: (props: SuggestionProps) => { + setMenu((m) => { + if (!m || m.kind !== kind) return m; + const index = props.items.length + ? Math.min(m.index, props.items.length - 1) + : 0; + return { + kind, + items: props.items, + index, + select: (item) => props.command(item), + }; + }); + }, + onExit: () => setMenu((m) => (m && m.kind === kind ? null : m)), + }), + [], + ); + + const mentions = useMemo( + () => + createComposerMentions({ + agentItems: agentMenuItems, + commandItems: commandMenuItems, + makeRender, + }), + [makeRender], + ); + + const editor = useEditor({ + immediatelyRender: false, + autofocus: "end", + extensions: [ + // A deliberately plain field: keep the editing primitives (undo, hard + // break, drop/gap cursors) but drop every rich-text mark and block so + // typing `# `, `**`, `- ` etc. stays literal, exactly like the textarea. + StarterKit.configure({ + heading: false, + bold: false, + italic: false, + strike: false, + code: false, + codeBlock: false, + blockquote: false, + bulletList: false, + orderedList: false, + listItem: false, + horizontalRule: false, + link: false, + underline: false, + }), + Placeholder.configure({ placeholder }), + ...mentions, + ], + onUpdate: ({ editor }) => setText(editor.getText({ blockSeparator: "\n" })), + }); + + useEffect(() => { + editor?.setEditable(!disabled); + }, [editor, disabled]); + + useEffect(() => { + if (appendText && editor) { + const raw = editor.getText({ blockSeparator: "\n" }); + const sep = raw && !raw.endsWith("\n") ? "\n" : ""; + setComposerText(editor, `${raw}${sep}> ${appendText}\n`); + setText(editor.getText({ blockSeparator: "\n" })); + onAppendConsumed?.(); + requestAnimationFrame(() => editor.commands.focus("end")); + } + }, [appendText, onAppendConsumed, editor]); + + useEffect(() => { + if (initialText && editor) { + setComposerText(editor, initialText); + setText(editor.getText({ blockSeparator: "\n" })); + onInitialTextConsumed?.(); + requestAnimationFrame(() => editor.commands.focus("end")); + } + }, [initialText, onInitialTextConsumed, editor]); + + const voice = useVoiceInput( + useCallback( + (transcript: string) => { + if (editor) setComposerText(editor, transcript); + setText(transcript); + }, + [editor], + ), + ); + + const handleSend = useCallback(() => { + const trimmed = editor ? composerText(editor) : ""; + if (!trimmed || disabled) return; + onSend?.(trimmed); + editor?.commands.clearContent(); + setText(""); + }, [editor, disabled, onSend]); + + // Menu nav + Enter-to-send are wired with a native capture-phase keydown + // listener on the editor DOM, re-subscribed each render with fresh closures + // over `menu`/`handleSend`. Capture phase runs before ProseMirror's own + // (bubble-phase) handler, and stopPropagation keeps the base keymap and the + // suggestion plugins from also acting. This sidesteps both TipTap re-syncing + // its editorProps and the React Compiler not preserving render-phase refs. + useEffect(() => { + const dom = editor?.view.dom; + if (!dom) return; + const onKeyDown = (event: KeyboardEvent) => { + if (menu) { + if (handleMenuNavKey(event, menu, setMenu)) { + event.preventDefault(); + event.stopPropagation(); + } + return; + } + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault(); + event.stopPropagation(); + handleSend(); + } + }; + dom.addEventListener("keydown", onKeyDown, true); + return () => dom.removeEventListener("keydown", onKeyDown, true); + }, [editor, menu, handleSend]); + + const hasText = text.trim().length > 0; + + return ( +
+ {/* Autocomplete popover (agent @-mentions or slash commands), floating + above the input box. Driven by TipTap's suggestion lifecycle. */} + {menu && menu.items.length > 0 && ( +
+
+ {menu.items.map((item, i) => ( + + ))} +
+
+ )} + {/* Input box: textarea + inline toolbar (+, mic) + send button. + Has its own rounded border. The toolbar row below has a matching + border on its top/sides/bottom; the two borders meet along the + input box's bottom edge, sharing a single visible line. */} +
+ {voice.isListening && ( +
+ + + Listening + +
+ )} +
+ {/* TipTap composer: resolved @agent / /skill mentions are atomic + green chips (Backspace removes a whole chip); Enter sends and + Shift+Enter inserts a newline (handled in the editor keymap). */} +
+ +
+
+ {/* Bottom row: mic on the left; send right */} +
+ {voice.isSupported && ( + + )} + +
+
+ {/* Toolbar sits BEHIND the input box: negative top margin pulls it up + so its top edge overlaps the input box's bottom rounded corners, + making the two boxes appear to share a single outline. */} + {/* @container: the Model/Memory pills collapse their value labels via + container queries when THIS row runs narrow (a ~400px side-panel + composer), independent of the viewport width. */} +
+ {!compact && } +
+
+ ); +} diff --git a/studio/src/app/workspace/_components/composer-mentions.ts b/studio/src/app/workspace/_components/composer-mentions.ts new file mode 100644 index 0000000000..6c5ba400de --- /dev/null +++ b/studio/src/app/workspace/_components/composer-mentions.ts @@ -0,0 +1,111 @@ +import Mention from "@tiptap/extension-mention"; +import { PluginKey } from "@tiptap/pm/state"; +import type { Editor } from "@tiptap/react"; +import type { SuggestionOptions } from "@tiptap/suggestion"; + +/** + * Atomic `@agent` / `/skill` chips for the TipTap composer. + * + * Each is a Mention node (an atom, so Backspace removes the whole chip in one + * press) rendered as the same green "connected" pill the old overlay painted. + * `renderText` serializes a chip back to `@handle` / `/name`, so `editor.getText` + * reconstructs the exact plain-text message the model receives. + * + * Autocomplete is delegated to the caller: the caller supplies the filtered + * items per keystroke and a `render` that drives its own menu UI (we reuse the + * existing full-width popover rather than TipTap's default), keeping the + * on-screen behaviour identical to the pre-TipTap composer. + */ + +/** One row in the autocomplete menu (and the data an inserted chip carries). */ +export interface ComposerMenuItem { + /** Text inserted after the trigger char — the agent handle or command name. */ + readonly id: string; + /** Chip label (same as `id`; the trigger char is added by `renderText`). */ + readonly label: string; + /** Primary text shown in the menu row. */ + readonly primary: string; + /** Muted description shown in the menu row. */ + readonly secondary: string; +} + +/** The trigger kind, so the caller can render agent vs command rows. */ +export type ComposerMentionKind = "agent" | "command"; + +/** `render` factory: given the kind, return a TipTap suggestion renderer. */ +type MakeRender = ( + kind: ComposerMentionKind, +) => SuggestionOptions["render"]; + +const CHIP_CLASS = + "rounded px-1 py-0.5 bg-emerald-500/15 text-emerald-700 dark:text-emerald-400"; + +function mentionNode( + name: string, + char: string, + pluginKeyName: string, + items: (query: string) => ComposerMenuItem[], + makeRender: MakeRender, + kind: ComposerMentionKind, + allow?: SuggestionOptions["allow"], +) { + return Mention.extend({ name }).configure({ + HTMLAttributes: { class: CHIP_CLASS, "data-composer-mention": name }, + // `id` is what renderText appends after the trigger char. + renderText: ({ node }) => `${char}${node.attrs.label ?? node.attrs.id}`, + suggestion: { + char, + pluginKey: new PluginKey(pluginKeyName), + items: ({ query }) => items(query), + allow, + render: makeRender(kind), + }, + }); +} + +/** + * Build the two mention extensions. `agentItems` / `commandItems` filter the + * live lists for the current query; `makeRender` wires each suggestion to the + * caller's menu. Commands only trigger at the very start of the message + * (`range.from === 1`), matching the old "slash menu only when the message is a + * single /token" rule; agents trigger anywhere after whitespace. + */ +export function createComposerMentions(opts: { + agentItems: (query: string) => ComposerMenuItem[]; + commandItems: (query: string) => ComposerMenuItem[]; + makeRender: MakeRender; +}) { + return [ + mentionNode( + "agentMention", + "@", + "composerAgentMention", + opts.agentItems, + opts.makeRender, + "agent", + ), + mentionNode( + "commandMention", + "/", + "composerCommandMention", + opts.commandItems, + opts.makeRender, + "command", + ({ range }) => range.from === 1, + ), + ]; +} + +/** Serialize the editor to the plain-text message (chips → `@handle`/`/name`). */ +export function composerText(editor: Editor): string { + return editor.getText({ blockSeparator: "\n" }).trim(); +} + +/** Replace the whole document with plain text (voice input, quoted replies). */ +export function setComposerText(editor: Editor, text: string) { + const content = text.split("\n").map((line) => ({ + type: "paragraph", + content: line ? [{ type: "text", text: line }] : [], + })); + editor.commands.setContent({ type: "doc", content }); +} diff --git a/studio/src/app/workspace/_components/resize-handle.tsx b/studio/src/app/workspace/_components/resize-handle.tsx new file mode 100644 index 0000000000..ca3e94dc01 --- /dev/null +++ b/studio/src/app/workspace/_components/resize-handle.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useRef } from "react"; +import { cn } from "@/lib/utils"; + +interface ResizeHandleProps { + direction?: "left" | "right"; + width: number; + onWidthChange: (width: number) => void; + min?: number; + max?: number; +} + +export function ResizeHandle({ + direction = "right", + width, + onWidthChange, + min = 200, + max = 720, +}: ResizeHandleProps) { + const isDraggingRef = useRef(false); + + return ( + // biome-ignore lint/a11y/noStaticElementInteractions: drag-only resize handle +
{ + e.preventDefault(); + isDraggingRef.current = true; + const startX = e.clientX; + const startW = width; + const sign = direction === "right" ? 1 : -1; + const onMove = (ev: MouseEvent) => { + if (!isDraggingRef.current) return; + onWidthChange( + Math.max(min, Math.min(max, startW + sign * (ev.clientX - startX))), + ); + }; + const onUp = () => { + isDraggingRef.current = false; + document.removeEventListener("mousemove", onMove); + document.removeEventListener("mouseup", onUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }; + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", onMove); + document.addEventListener("mouseup", onUp); + }} + /> + ); +} diff --git a/studio/src/app/workspace/chat/[[...sessionId]]/page.tsx b/studio/src/app/workspace/chat/[[...sessionId]]/page.tsx new file mode 100644 index 0000000000..ef81125363 --- /dev/null +++ b/studio/src/app/workspace/chat/[[...sessionId]]/page.tsx @@ -0,0 +1,16 @@ +import { ChatWorkspace } from "../_components/chat-workspace"; + +/** + * `/workspace/chat` (a draft with no daemon session yet) and + * `/workspace/chat/` are served by one optional-catch-all route, + * so moving between them keeps `ChatWorkspace` mounted — a draft's in-flight + * stream survives the router.replace to its freshly minted session id. + */ +export default async function ChatPage({ + params, +}: { + params: Promise<{ sessionId?: string[] }>; +}) { + const { sessionId } = await params; + return ; +} diff --git a/studio/src/app/workspace/chat/_components/approval-panel.tsx b/studio/src/app/workspace/chat/_components/approval-panel.tsx new file mode 100644 index 0000000000..67a44e0e75 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/approval-panel.tsx @@ -0,0 +1,121 @@ +"use client"; + +import { ShieldAlert } from "lucide-react"; +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import type { ApprovalChoice, ApprovalRequest } from "@/features/agent"; +import { cn } from "@/lib/utils"; + +const DELETE_WORDS = /\b(delete|remove|drop|revoke|destroy|purge|rm)\b/i; + +/** + * The daemon's verdict is three-way (allow_once / allow_always / deny), so the + * panel offers exactly those scopes — no "for session" button, which would + * promise a grant scope the backend does not model. + */ +export function ApprovalPanel({ + approval, + onRespond, +}: { + approval: ApprovalRequest; + onRespond: (choice: ApprovalChoice) => void; +}) { + // ONE ask = ONE tool call. The pill names the tool; the args belong in the + // preview block below, never badge-ified (a Write ask's args are a whole + // file). Destructiveness is judged on the tool name alone — scanning file + // CONTENT for the word "delete" painted harmless writes red. + const toolName = + approval.toolName || + approval.description.replace(/ needs your approval\.?$/i, "").trim() || + "Tool"; + const destructive = DELETE_WORDS.test(toolName); + + return ( +
+
+ + + Permission required + +
+

{approval.description}

+
+ + {toolName} + +
+ {destructive && ( +

+ This action modifies or deletes data. +

+ )} +
+        {approval.details}
+      
+
+ + + +
+
+ ); +} diff --git a/studio/src/app/workspace/chat/_components/chat-view.tsx b/studio/src/app/workspace/chat/_components/chat-view.tsx new file mode 100644 index 0000000000..5ca893605c --- /dev/null +++ b/studio/src/app/workspace/chat/_components/chat-view.tsx @@ -0,0 +1,498 @@ +"use client"; + +import { + AlertCircle, + ArrowDown, + CirclePlus, + Ellipsis, + Loader2, + MessageCircle, + PanelLeftClose, + PanelLeftOpen, + PanelRightClose, + PanelRightOpen, + Pencil, + RotateCcw, + Trash2, + Wrench, +} from "lucide-react"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import type { + AgentMessage, + AgentSession, + ApprovalChoice, + ApprovalRequest, + ClarificationRequest, +} from "@/features/agent"; +import { formatTokens } from "@/lib/formatters"; +import type { SessionListSide } from "@/lib/profile-preferences"; +import { ChatInput } from "../../_components/chat-input"; +import { ApprovalPanel } from "./approval-panel"; +import { ClarificationPanel } from "./clarification-panel"; +import { MessageBubble } from "./message-bubble"; +import { MockProviderNotice } from "./mock-provider-notice"; + +/** + * Bottom-of-transcript activity line while a turn is running: three + * staggered pulsing dots, a phase label derived from the streaming + * assistant message (thinking / running tools / writing), and elapsed time. + */ +function StreamingIndicator({ message }: { message?: AgentMessage }) { + const [elapsed, setElapsed] = useState(0); + const startedAt = message?.timestamp; + useEffect(() => { + if (!startedAt) return; + const tick = () => + setElapsed(Math.max(0, Math.round((Date.now() - startedAt) / 1000))); + tick(); + const timer = setInterval(tick, 1000); + return () => clearInterval(timer); + }, [startedAt]); + + const runningTool = message?.toolCalls?.some( + (call) => call.status === "running", + ); + const phase = runningTool + ? "Running tools" + : message?.content + ? "Writing" + : "Thinking"; + const time = + elapsed >= 60 + ? `${Math.floor(elapsed / 60)}m ${elapsed % 60}s` + : `${elapsed}s`; + + return ( + // Mirrors the message-row geometry (avatar column + gap) so the label + // lines up with message text; the dots sit centered in the avatar slot. +
+
+ ); +} + +/** Token counts as a read-only info row inside the chat context menus. */ +function UsageMenuRow({ + usage, +}: { + usage?: { inputTokens: number; outputTokens: number } | null; +}) { + if (!usage || usage.inputTokens + usage.outputTokens <= 0) return null; + return ( +
+

Token usage

+

+ {formatTokens(usage.inputTokens)} input +

+

+ {formatTokens(usage.outputTokens)} output +

+
+ ); +} + +function TextSelectionToolbar({ + containerRef, + onAddToChat, + onAskInSideChat, + addLabel = "Add to chat", + askLabel = "Ask in a chat thread", +}: { + containerRef: React.RefObject; + onAddToChat: (text: string) => void; + /** Omitted = the toolbar offers only the add action (the thread panel). */ + onAskInSideChat?: (text: string) => void; + addLabel?: string; + askLabel?: string; +}) { + const [pos, setPos] = useState<{ x: number; y: number } | null>(null); + const [selectedText, setSelectedText] = useState(""); + const toolbarRef = useRef(null); + + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + const handleMouseUp = () => { + requestAnimationFrame(() => { + const selection = window.getSelection(); + const text = selection?.toString().trim(); + if (!text || text.length < 3) { + setPos(null); + return; + } + + const range = selection?.getRangeAt(0); + if (!range) return; + + if (!container.contains(range.commonAncestorContainer)) { + setPos(null); + return; + } + + const rect = range.getBoundingClientRect(); + const containerRect = container.getBoundingClientRect(); + + setSelectedText(text); + setPos({ + x: rect.left + rect.width / 2 - containerRect.left, + y: rect.top - containerRect.top - 8, + }); + }); + }; + + const handleMouseDown = (e: MouseEvent) => { + if (toolbarRef.current?.contains(e.target as Node)) return; + setPos(null); + }; + + container.addEventListener("mouseup", handleMouseUp); + document.addEventListener("mousedown", handleMouseDown); + return () => { + container.removeEventListener("mouseup", handleMouseUp); + document.removeEventListener("mousedown", handleMouseDown); + }; + }, [containerRef]); + + if (!pos) return null; + + return ( +
+ + {onAskInSideChat && ( + <> +
+ + + )} +
+ ); +} + +export function ChatView({ + session, + messages, + isStreaming, + onSend, + botName, + usage, + error, + onRetry, + sidebarOpen, + sidebarSide, + onToggleSidebar, + pendingApproval, + onRespondApproval, + pendingClarification, + onRespondClarification, + onRename, + onDelete, + initialDraft, + onInitialDraftConsumed, +}: { + session: AgentSession; + messages: AgentMessage[]; + isStreaming: boolean; + onSend: (content: string, files?: File[]) => void; + botName: string; + /** True while the daemon connection is up. */ + live?: boolean; + usage?: { inputTokens: number; outputTokens: number }; + /** The last turn failed with this text; rendered as an inline strip. */ + error?: string | null; + onRetry?: () => void; + sidebarOpen: boolean; + sidebarSide: SessionListSide; + onToggleSidebar: () => void; + pendingApproval: ApprovalRequest | null; + onRespondApproval: (choice: ApprovalChoice) => void; + pendingClarification: ClarificationRequest | null; + onRespondClarification: (response: string) => void; + onRename?: () => void; + onDelete?: () => void; + /** Fires when the right-hand side panel (artifact/attachment) opens or + closes, so the parent can collapse the chat list while it's open. */ + onSidePanelOpenChange?: (open: boolean) => void; + /** Plain-text prompt to pre-fill the composer with on mount (a "next step" + chip that seeds the message without sending it). */ + initialDraft?: string | null; + onInitialDraftConsumed?: () => void; +}) { + const messagesEndRef = useRef(null); + // Whether the transcript is scrolled to (near) the bottom; when it isn't, + // a floating control above the composer jumps back down. The ref mirror is + // what the follow effect reads — it must see the value as of the latest + // scroll, not the latest render. + const [atBottom, setAtBottom] = useState(true); + const atBottomRef = useRef(true); + const messagesContainerRef = useRef(null); + const [showActivity, setShowActivity] = useState(false); + const [appendText, setAppendText] = useState(null); + const handleAppendConsumed = useCallback(() => setAppendText(null), []); + + // Jump to the latest message whenever the active chat changes so users + // always land at the bottom (most-recent) of the conversation. + // biome-ignore lint/correctness/useExhaustiveDependencies: scrolling is intentionally driven by session.id changes + useEffect(() => { + messagesEndRef.current?.scrollIntoView({ behavior: "instant" }); + atBottomRef.current = true; + setAtBottom(true); + }, [session.id]); + + // Pinned-follow: while the user sits at the bottom, a sent message and the + // streaming response keep the view pinned there; once they scroll up, their + // position holds (the floating arrow offers the way back down). + // biome-ignore lint/correctness/useExhaustiveDependencies: re-runs on every transcript update by design + useEffect(() => { + if (!atBottomRef.current) return; + const el = messagesContainerRef.current; + if (el) el.scrollTop = el.scrollHeight; + }, [messages]); + + // The sidebar toggle renders on the header edge nearest the panel it + // controls: leading when the session list docks left, trailing when right. + const sidebarToggle = ( + + + + + + {sidebarOpen ? "Hide sidebar" : "Show sidebar"} + + + ); + + return ( +
+
+
+ {sidebarSide === "left" && sidebarToggle} + {isStreaming && ( + + )} +

+ {session.title || "Untitled"} +

+ {sidebarSide === "right" && sidebarToggle} + + + + + + {/* Token usage as the daemon reported it (was a header pill; + it lives in the menu now). Hidden until any lands. */} + + setShowActivity((v) => !v)}> + + {showActivity ? "Hide Tools" : "Show Tools"} + + {onRename && ( + + + Rename + + )} + {onDelete && ( + + + Delete + + )} + + +
+ +
+
{ + const el = event.currentTarget; + const pinned = + el.scrollHeight - el.scrollTop - el.clientHeight < 80; + atBottomRef.current = pinned; + setAtBottom(pinned); + }} + className="h-full overflow-y-auto px-3 pt-1 pb-48 max-[499px]:pb-24 lg:px-6 lg:pt-2 lg:pb-56" + > + setAppendText(text)} + /> +
+ {messages.map((msg) => ( + + ))} + {pendingApproval && ( + + )} + {isStreaming && ( + + )} +
+
+
+
+ {!atBottom && ( +
+ +
+ )} +
+ {error && ( +
+ +

+ {error} +

+ {onRetry && ( + + )} +
+ )} + + {pendingClarification ? ( + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/studio/src/app/workspace/chat/_components/chat-workspace.tsx b/studio/src/app/workspace/chat/_components/chat-workspace.tsx new file mode 100644 index 0000000000..05f983485e --- /dev/null +++ b/studio/src/app/workspace/chat/_components/chat-workspace.tsx @@ -0,0 +1,621 @@ +"use client"; + +import { Loader2, PanelLeft, PanelRight, SquarePen } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + type AgentSession, + type RosterAgent, + useAgentChat, + useAgentRoster, + useAgentSessions, +} from "@/features/agent"; +import { useConfirm } from "@/hooks/use-confirm"; +import { useIsCompact } from "@/hooks/use-mobile"; +import { useNavReopenSidebar } from "@/hooks/use-nav-reopen-sidebar"; +import { usePanelWidth } from "@/hooks/use-panel-width"; +import { usePrompt } from "@/hooks/use-prompt"; +import { + type SessionListSide, + useAgentDisplayName, + useSessionListSide, +} from "@/lib/profile-preferences"; +import { useShortcut } from "@/lib/shortcuts/use-shortcuts"; +import { pageTitleClass } from "@/lib/typography"; +import { cn } from "@/lib/utils"; +import { ChatInput } from "../../_components/chat-input"; +import { ResizeHandle } from "../../_components/resize-handle"; +import { ChatView } from "./chat-view"; +import { + AgentList, + type SessionActions, + SessionList, + SidebarGroup, +} from "./session-sidebar"; + +/** Route for a chat, or the base (a new draft) when none is selected. */ +const chatHref = (id?: string) => + id ? `/workspace/chat/${id}` : "/workspace/chat"; + +/** One-click prompts on the draft state, to seed the first message. */ +const STARTER_PROMPTS = [ + "Summarise what changed in the repo this week", + "Draft a plan for a new feature", + "Review my open pull requests", + "Find and explain a bug in the codebase", +] as const; + +const DAY_MS = 86_400_000; + +/** + * Bucket recency-sorted sessions into Today / This week / Earlier for the + * flat sidebar list. Empty buckets are dropped. + */ +function groupSessionsByRecency( + sessions: AgentSession[], +): { label: string; sessions: AgentSession[] }[] { + const startOfToday = new Date(); + startOfToday.setHours(0, 0, 0, 0); + const t0 = startOfToday.getTime(); + const order = ["Today", "This week", "Earlier"] as const; + const buckets: Record<(typeof order)[number], AgentSession[]> = { + Today: [], + "This week": [], + Earlier: [], + }; + for (const s of sessions) { + const ts = s.updatedAt ?? 0; + const label = + ts >= t0 ? "Today" : ts >= t0 - 7 * DAY_MS ? "This week" : "Earlier"; + buckets[label].push(s); + } + return order + .filter((l) => buckets[l].length > 0) + .map((l) => ({ label: l, sessions: buckets[l] })); +} + +function SidebarContent({ + onNewChat, + isLoading, + error, + groups, + agents, + selectedId, + onSelect, + actions, +}: { + onNewChat: () => void; + isLoading: boolean; + error: string | null; + groups: { label: string; sessions: AgentSession[] }[]; + agents: RosterAgent[]; + selectedId: string; + onSelect: (id: string) => void; + actions: SessionActions; +}) { + return ( + <> +
+

+ Chat History +

+ + + + + New chat + +
+ +
+ {error && ( +

+ {error} +

+ )} + {isLoading ? ( +
+ +
+ ) : groups.length > 0 ? ( +
+ {groups.map((group) => ( + + + + ))} +
+ ) : ( + !error && ( +

+ No chats yet +

+ ) + )} + {!isLoading && agents.length > 0 && ( +
0 ? "pt-3" : undefined}> + + + +
+ )} +
+ + ); +} + +/** Draft chat (no daemon session yet): greeting + starter chips, composer docked. */ +function DraftView({ + onSend, + seed, + onSeedConsumed, + onPickSeed, + error, + showSidebarButton, + sidebarSide, + onShowSidebar, +}: { + onSend: (content: string, files?: File[]) => void; + seed: string | null; + onSeedConsumed: () => void; + onPickSeed: (text: string) => void; + error: string | null; + showSidebarButton: boolean; + sidebarSide: SessionListSide; + onShowSidebar: () => void; +}) { + return ( +
+ {/* Same header bar as an open chat, so a draft doesn't lose the title + row and its controls. */} +
+

+ New chat +

+ {showSidebarButton && ( + + )} +
+ {/* The composer docks at the bottom exactly like an open chat, so the + draft-to-chat transition doesn't move the input under your hands. */} +
+
+
+

+ What can I help you with? +

+
+ {STARTER_PROMPTS.map((p) => ( + + ))} +
+
+
+
+
+ {error &&

{error}

} + +
+
+
+
+ ); +} + +/** + * The chat workspace: one flat, daemon-backed session list plus the open + * conversation. Selection is driven by the URL (`/workspace/chat/`), + * so deep-links, back/forward, and hard reloads resolve to the same chat. + * `/workspace/chat` with no id is a draft whose daemon session is minted on + * the first send. + */ +export function ChatWorkspace({ sessionId }: { sessionId?: string }) { + const { + sessions, + isLoading: sessionsLoading, + error: sessionsError, + deleteSession, + renameSession, + refreshSessions, + } = useAgentSessions(); + const { agents } = useAgentRoster(); + const router = useRouter(); + const { name: agentName } = useAgentDisplayName(); + const { side: sidebarSide } = useSessionListSide(); + const isCompact = useIsCompact(); + const { confirm, ConfirmDialog } = useConfirm(); + const { prompt, PromptDialog } = usePrompt(); + + // Selection is URL-driven; the state mirror keeps it in sync while also + // allowing an optimistic update before the client navigation settles. + const [selectedId, setSelectedIdState] = useState(sessionId ?? ""); + // The id minted for a draft on first send. While the URL settles on that id + // the chat hook keeps its draft binding (it already owns the live stream); + // re-keying it would wipe the in-flight messages with a transcript refetch. + const draftMintedIdRef = useRef(null); + useEffect(() => { + setSelectedIdState(sessionId ?? ""); + if (sessionId !== draftMintedIdRef.current) draftMintedIdRef.current = null; + }, [sessionId]); + + /** + * Select a chat: optimistic state + a native history push. Deliberately not + * router.push — moving the optional catch-all between zero and one segments + * changes the route shape, which remounts this page; the remount re-runs + * the pre-measurement useState inits (sidebarOpen assumes desktop until the + * viewport is measured), so on a phone the list rendered straight over the + * chat that was just tapped. The App Router syncs its state from native + * history updates without remounting. + */ + const selectId = useCallback((id: string) => { + setSelectedIdState(id); + window.history.pushState(null, "", chatHref(id)); + }, []); + + const [sidebarOpen, setSidebarOpen] = useState(!isCompact); + const wasCompactRef = useRef(isCompact); + // Mirrors `sidebarOpen` for reads inside the stable side-panel handler. + const sidebarOpenRef = useRef(sidebarOpen); + useEffect(() => { + sidebarOpenRef.current = sidebarOpen; + }, [sidebarOpen]); + // Remembers whether the chat list was open before a side panel forced it + // closed, so closing the panel restores the user's prior choice. + const sidebarBeforeSidePanelRef = useRef(null); + + const handleSidePanelOpenChange = useCallback((open: boolean) => { + if (open) { + if (sidebarBeforeSidePanelRef.current === null) { + sidebarBeforeSidePanelRef.current = sidebarOpenRef.current; + setSidebarOpen(false); + } + } else if (sidebarBeforeSidePanelRef.current !== null) { + setSidebarOpen(sidebarBeforeSidePanelRef.current); + sidebarBeforeSidePanelRef.current = null; + } + }, []); + + // Sync sidebar visibility with viewport transitions: entering compact + // closes it; leaving compact reopens it. + useEffect(() => { + const wasCompact = wasCompactRef.current; + if (!wasCompact && isCompact) { + setSidebarOpen(false); + } else if (wasCompact && !isCompact) { + setSidebarOpen(true); + } + wasCompactRef.current = isCompact; + }, [isCompact]); + const [sidebarWidth, setSidebarWidth] = usePanelWidth(); + + const handleSessionCreated = useCallback( + (id: string) => { + draftMintedIdRef.current = id; + setSelectedIdState(id); + // Native replaceState, deliberately not router.replace: moving the + // optional catch-all from zero segments to one changes the route + // shape, which remounts this page — and a remount replaces the chat + // hook instance, so the in-flight stream would render into dead + // state and the pane would sit empty until a reload. The App Router + // syncs its state from native history updates without remounting. + window.history.replaceState(null, "", chatHref(id)); + void refreshSessions(); + }, + [refreshSessions], + ); + + // The draft keeps a null hook id even after its session is minted and the + // URL updates — the hook already streams against the minted id internally. + const hookSessionId = + selectedId && selectedId !== draftMintedIdRef.current ? selectedId : null; + + const { + messages, + isStreaming, + status, + error: chatError, + harnessLive, + sendMessage, + retryLast, + pendingApproval, + respondToApproval, + pendingClarification, + respondToClarification, + usage, + } = useAgentChat(hookSessionId, { + onSessionCreated: handleSessionCreated, + }); + + useNavReopenSidebar(setSidebarOpen); + + // Seed for the draft composer, set when a starter prompt is picked. + const [draftSeed, setDraftSeed] = useState(null); + const clearDraftSeed = useCallback(() => setDraftSeed(null), []); + + const orderedSessions = useMemo( + () => [...sessions].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0)), + [sessions], + ); + const groups = useMemo( + () => groupSessionsByRecency(orderedSessions), + [orderedSessions], + ); + + const selectedSession = useMemo(() => { + if (!selectedId) return undefined; + const found = sessions.find((s) => s.id === selectedId); + if (found) return found; + // A just-minted draft's row may not have landed in the polled list yet; + // a stand-in keeps the conversation rendered until the walk catches up. + return { + id: selectedId, + title: "Untitled chat", + projectId: null, + model: "", + createdAt: 0, + updatedAt: 0, + pinned: false, + archived: false, + messageCount: 0, + isStreaming: false, + inputTokens: 0, + outputTokens: 0, + unread: false, + estimatedCost: null, + contextLength: null, + lastPromptTokens: null, + thresholdTokens: null, + }; + }, [selectedId, sessions]); + + const handleSelectSession = useCallback( + (id: string) => { + selectId(id); + if (isCompact) setSidebarOpen(false); + }, + [selectId, isCompact], + ); + + /** "New chat" opens the draft route; the daemon session is minted on send. */ + const handleNewChat = useCallback(() => { + setSelectedIdState(""); + router.push(chatHref()); + if (isCompact) setSidebarOpen(false); + }, [router, isCompact]); + + const deselectIfActive = useCallback( + (id: string) => { + if (id === selectedId) { + setSelectedIdState(""); + router.push(chatHref()); + } + }, + [selectedId, router], + ); + + const sessionActions: SessionActions = useMemo( + () => ({ + onRename: async (id: string) => { + const s = sessions.find((x) => x.id === id); + const name = await prompt({ + title: "Rename chat", + placeholder: "Chat name", + defaultValue: s?.title ?? "", + confirmText: "Rename", + }); + if (name) await renameSession(id, name); + }, + onDelete: async (id: string) => { + const ok = await confirm({ + title: "Delete chat", + description: + "This chat and its messages will be permanently deleted.", + confirmText: "Delete", + destructive: true, + }); + if (!ok) return; + await deleteSession(id); + deselectIfActive(id); + }, + }), + [sessions, prompt, renameSession, confirm, deleteSession, deselectIfActive], + ); + + // Flattened, in-display-order chat ids for keyboard navigation. + const navOrder = useMemo( + () => groups.flatMap((g) => g.sessions.map((s) => s.id)), + [groups], + ); + + const navigateBy = useCallback( + (forward: boolean) => { + if (navOrder.length === 0) return; + const cur = navOrder.indexOf(selectedId); + const idx = + cur === -1 + ? forward + ? 0 + : navOrder.length - 1 + : forward + ? Math.min(cur + 1, navOrder.length - 1) + : Math.max(cur - 1, 0); + handleSelectSession(navOrder[idx]); + }, + [navOrder, selectedId, handleSelectSession], + ); + + useShortcut("chat.new", handleNewChat); + useShortcut("chat.toggleList", () => setSidebarOpen((o) => !o)); + useShortcut("chat.next", () => navigateBy(true)); + useShortcut("chat.next.vim", () => navigateBy(true)); + useShortcut("chat.prev", () => navigateBy(false)); + useShortcut("chat.prev.vim", () => navigateBy(false)); + + const sidebarContentProps = { + onNewChat: handleNewChat, + isLoading: sessionsLoading, + error: sessionsError, + groups, + agents, + selectedId, + onSelect: handleSelectSession, + actions: sessionActions, + }; + + const dialogs = ( + <> + {ConfirmDialog} + {PromptDialog} + + ); + + const turnError = + status === "error" ? (chatError ?? "The last turn failed.") : null; + + const chatView = (open: boolean, onToggle: () => void) => + selectedSession ? ( + sessionActions.onRename(selectedSession.id) + : undefined + } + onDelete={ + selectedSession.canDelete === true + ? () => sessionActions.onDelete(selectedSession.id) + : undefined + } + onSidePanelOpenChange={handleSidePanelOpenChange} + /> + ) : null; + + return ( +
+ {dialogs} +
+ {selectedSession ? ( + chatView(sidebarOpen, () => setSidebarOpen((o) => !o)) + ) : ( + setSidebarOpen(true)} + /> + )} +
+ + {sidebarOpen && + (isCompact ? ( + <> + {/* Backdrop closes the overlay sidebar on outside click */} +
+ ); +} diff --git a/studio/src/app/workspace/chat/_components/clarification-panel.tsx b/studio/src/app/workspace/chat/_components/clarification-panel.tsx new file mode 100644 index 0000000000..2627d16de8 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/clarification-panel.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { CircleHelp } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import type { ClarificationRequest } from "@/features/agent"; + +export function ClarificationPanel({ + clarification, + onRespond, +}: { + clarification: ClarificationRequest; + onRespond: (response: string) => void; +}) { + const [input, setInput] = useState(""); + const [choices, setChoices] = useState([]); + + useEffect(() => { + const lines = clarification.question.split("\n").filter((l) => l.trim()); + const numbered = lines.filter((l) => /^\d+[.)]\s/.test(l.trim())); + if (numbered.length > 0) { + setChoices(numbered.map((l) => l.replace(/^\d+[.)]\s*/, "").trim())); + } + }, [clarification.question]); + + const questionText = clarification.question + .split("\n") + .filter((l) => !/^\d+[.)]\s/.test(l.trim())) + .join("\n") + .trim(); + + return ( +
+
+ + + Clarification needed + +
+

{questionText || clarification.question}

+ {choices.length > 0 && ( +
+ {choices.map((choice, i) => ( + + ))} + +
+ )} +
+ setInput(e.target.value)} + placeholder="Type your response..." + className="flex-1 rounded-lg border border-info/30 bg-background px-3 py-2 text-sm focus:outline-none focus:border-info" + onKeyDown={(e) => { + if (e.key === "Enter" && input.trim()) { + onRespond(input.trim()); + setInput(""); + } + }} + /> + +
+
+ ); +} diff --git a/studio/src/app/workspace/chat/_components/code-block.test.tsx b/studio/src/app/workspace/chat/_components/code-block.test.tsx new file mode 100644 index 0000000000..3261ce461d --- /dev/null +++ b/studio/src/app/workspace/chat/_components/code-block.test.tsx @@ -0,0 +1,33 @@ +import { render, waitFor } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; +import { CodeBlock } from "./code-block"; + +/** + * `CodeBlock` renders a synchronous plain fallback and then swaps in Shiki's + * highlighted tokens once the grammar loads. Each test waits for that swap + * (`data-highlighted="true"`) so the async state update settles inside `act`. + * Assertions stay engine-agnostic — row count and preserved text hold in both + * the fallback and highlighted states. + */ +describe("CodeBlock", () => { + it("renders one numbered row per line and preserves the code text", async () => { + const code = "const x = 1;\nfunction f() {\n return x;\n}"; + const { container } = render(); + await waitFor(() => + expect(container.querySelector("[data-highlighted='true']")).toBeTruthy(), + ); + const rows = container.querySelectorAll("tbody tr"); + expect(rows.length).toBe(4); + // Text is preserved (gutter digits are interleaved but fragments survive). + expect(container.textContent).toContain("const x = 1;"); + expect(container.textContent).toContain("return x;"); + }); + + it("drops a single trailing newline so there's no phantom last line", async () => { + const { container } = render(); + await waitFor(() => + expect(container.querySelector("[data-highlighted='true']")).toBeTruthy(), + ); + expect(container.querySelectorAll("tbody tr").length).toBe(2); + }); +}); diff --git a/studio/src/app/workspace/chat/_components/code-block.tsx b/studio/src/app/workspace/chat/_components/code-block.tsx new file mode 100644 index 0000000000..573ae7b8c1 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/code-block.tsx @@ -0,0 +1,107 @@ +"use client"; + +import { type CSSProperties, useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; +import { type HLine, highlightCode } from "./code-highlighter"; + +/** + * Theme-aware, line-numbered code viewer for the file-preview panel. + * + * Tokenizing runs through Shiki (real TextMate grammars, JS engine — see + * `code-highlighter.ts`), which is async: the first paint renders the raw lines + * synchronously in the exact same gutter/table layout, then the highlighted + * tokens swap in with no layout shift once the grammar has loaded. Each token + * carries a light and a dark colour, selected via a CSS `.dark` variant. + */ + +/** Wrap a single raw line of text as one uncoloured (inherit) token run. */ +function plainLine(text: string): HLine { + return text + ? [ + { + content: text, + light: "inherit", + dark: "inherit", + italic: false, + bold: false, + }, + ] + : []; +} + +export function CodeBlock({ + code, + lang = "text", +}: { + code: string; + /** Shiki language id (see `langForExtension`); defaults to plain text. */ + lang?: string; +}) { + // Drop a single trailing newline so we don't render a phantom last line. + const source = code.replace(/\n$/, ""); + const [highlighted, setHighlighted] = useState(null); + + useEffect(() => { + let active = true; + setHighlighted(null); + highlightCode(source, lang) + .then((lines) => { + if (active) setHighlighted(lines); + }) + .catch(() => { + // Leave the plain fallback in place; highlighting is best-effort. + }); + return () => { + active = false; + }; + }, [source, lang]); + + const lines: HLine[] = highlighted ?? source.split("\n").map(plainLine); + const gutterWidth = `${String(lines.length).length + 1}ch`; + + return ( +
+ + + {lines.map((line, i) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: lines are positional + + + + + ))} + +
+ {i + 1} + + {line.length === 0 + ? " " + : line.map((tok, j) => ( + + {tok.content} + + ))} +
+
+ ); +} diff --git a/studio/src/app/workspace/chat/_components/code-highlighter.test.ts b/studio/src/app/workspace/chat/_components/code-highlighter.test.ts new file mode 100644 index 0000000000..867bdfa655 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/code-highlighter.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from "vitest"; +import { highlightCode, langForExtension } from "./code-highlighter"; + +describe("langForExtension", () => { + it("maps known extensions to Shiki language ids", () => { + expect(langForExtension("ts")).toBe("typescript"); + expect(langForExtension("tsx")).toBe("tsx"); + expect(langForExtension("py")).toBe("python"); + expect(langForExtension("sh")).toBe("bash"); + expect(langForExtension("rs")).toBe("rust"); + }); + + it("falls back to plain text for unknown extensions", () => { + expect(langForExtension("")).toBe("text"); + expect(langForExtension("xyz")).toBe("text"); + }); +}); + +describe("highlightCode", () => { + function textOf(lines: readonly (readonly { content: string }[])[]): string { + return lines.map((line) => line.map((t) => t.content).join("")).join("\n"); + } + + it("splits into one token line per source line and preserves the text", async () => { + const code = "const x = 1;\nconst y = 2;"; + const lines = await highlightCode(code, "typescript"); + expect(lines.length).toBe(2); + expect(textOf(lines)).toBe(code); + }); + + it("assigns real colours to a known language", async () => { + const lines = await highlightCode("const x = 1;", "typescript"); + const colours = lines.flat().map((t) => t.light); + // At least one token is coloured rather than inheriting the foreground. + expect(colours.some((c) => c !== "inherit" && c.startsWith("#"))).toBe( + true, + ); + }); + + it("returns plain tokens for an unknown language without throwing", async () => { + const code = "just some words"; + const lines = await highlightCode(code, "text"); + expect(textOf(lines)).toBe(code); + }); +}); diff --git a/studio/src/app/workspace/chat/_components/code-highlighter.ts b/studio/src/app/workspace/chat/_components/code-highlighter.ts new file mode 100644 index 0000000000..3b1d13b538 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/code-highlighter.ts @@ -0,0 +1,145 @@ +import { createHighlighterCore, type HighlighterCore } from "shiki/core"; +import { createJavaScriptRegexEngine } from "shiki/engine/javascript"; +import { bundledLanguages } from "shiki/langs"; +import { bundledThemes } from "shiki/themes"; + +/** + * Shiki-backed syntax highlighting for the file-preview code viewer. + * + * Kept self-contained and CSP-safe: the JavaScript RegExp engine (no + * WebAssembly, no `eval`) tokenizes with real TextMate grammars, and both + * grammars and themes are loaded from Shiki's own lazy `import()` thunks — one + * grammar chunk per language, fetched only the first time that language is + * previewed. A single core highlighter is memoized for the session. + * + * The two GitHub themes are tokenized together (`defaultColor: false`) so each + * token carries both a light and a dark colour; the component picks between + * them with a CSS `.dark` variant, matching the app's class-based theme switch. + */ + +const LIGHT_THEME = "github-light-default"; +const DARK_THEME = "github-dark-default"; + +/** File extension → Shiki language id. Unknowns fall back to plain text. */ +const EXT_TO_LANG: Record = { + ts: "typescript", + tsx: "tsx", + js: "javascript", + jsx: "jsx", + mjs: "javascript", + cjs: "javascript", + json: "json", + py: "python", + sh: "bash", + bash: "bash", + zsh: "bash", + go: "go", + rs: "rust", + java: "java", + rb: "ruby", + php: "php", + c: "c", + cpp: "cpp", + h: "c", + hpp: "cpp", + cs: "csharp", + kt: "kotlin", + swift: "swift", + yml: "yaml", + yaml: "yaml", + toml: "toml", + sql: "sql", + css: "css", + scss: "scss", + html: "html", + xml: "xml", + graphql: "graphql", + prisma: "prisma", +}; + +/** Resolve a file extension to a Shiki language id (`"text"` when unknown). */ +export function langForExtension(ext: string): string { + return EXT_TO_LANG[ext] ?? "text"; +} + +let highlighterPromise: Promise | null = null; + +function getHighlighter(): Promise { + if (!highlighterPromise) { + highlighterPromise = createHighlighterCore({ + themes: [bundledThemes[LIGHT_THEME], bundledThemes[DARK_THEME]], + langs: [], + engine: createJavaScriptRegexEngine(), + }); + } + return highlighterPromise; +} + +interface HToken { + readonly content: string; + /** Colour under the light theme (or `"inherit"` for plain text). */ + readonly light: string; + /** Colour under the dark theme. */ + readonly dark: string; + readonly italic: boolean; + readonly bold: boolean; +} + +export type HLine = readonly HToken[]; + +/** + * Shape of a token when `codeToTokens` runs with multiple themes and + * `defaultColor: false`: instead of a single resolved colour, each token gets an + * `htmlStyle` map holding one CSS custom property per theme (`--shiki-light`, + * `--shiki-dark`) plus any `font-style` / `font-weight`. + */ +interface MultiThemeToken { + readonly content: string; + readonly htmlStyle?: Record; +} + +/** + * Tokenize `code` into per-line coloured runs. Unknown/`"text"` languages (or a + * grammar that fails to load) come back as one plain token per line so the + * caller always renders something. + */ +export async function highlightCode( + code: string, + lang: string, +): Promise { + const highlighter = await getHighlighter(); + + let resolvedLang = lang; + if (resolvedLang !== "text" && resolvedLang in bundledLanguages) { + if (!highlighter.getLoadedLanguages().includes(resolvedLang)) { + try { + await highlighter.loadLanguage( + bundledLanguages[resolvedLang as keyof typeof bundledLanguages], + ); + } catch { + resolvedLang = "text"; + } + } + } else { + resolvedLang = "text"; + } + + const { tokens } = highlighter.codeToTokens(code, { + lang: resolvedLang, + themes: { light: LIGHT_THEME, dark: DARK_THEME }, + defaultColor: false, + }); + + return (tokens as unknown as MultiThemeToken[][]).map((line) => + line.map((token) => { + const style = token.htmlStyle ?? {}; + return { + content: token.content, + light: style["--shiki-light"] ?? "inherit", + dark: style["--shiki-dark"] ?? "inherit", + italic: style["font-style"] === "italic", + bold: style["font-weight"] === "bold", + }; + }), + ); +} diff --git a/studio/src/app/workspace/chat/_components/markdown-components.tsx b/studio/src/app/workspace/chat/_components/markdown-components.tsx new file mode 100644 index 0000000000..f26c87d9a9 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/markdown-components.tsx @@ -0,0 +1,80 @@ +import type { Components } from "react-markdown"; + +export const mdComponents: Components = { + h1: ({ children }) => ( +

{children}

+ ), + h2: ({ children }) => ( +

{children}

+ ), + h3: ({ children }) => ( +

{children}

+ ), + h4: ({ children }) => ( +

{children}

+ ), + p: ({ children }) =>

{children}

, + ul: ({ children }) => ( +
    + {children} +
+ ), + ol: ({ children }) => ( +
    + {children} +
+ ), + li: ({ children }) =>
  • {children}
  • , + blockquote: ({ children }) => ( +
    + {children} +
    + ), + hr: () =>
    , + a: ({ href, children }) => ( + + {children} + + ), + code: ({ className, children }) => { + const isBlock = className?.includes("language-"); + if (isBlock) { + return {children}; + } + return ( + + {children} + + ); + }, + pre: ({ children }) => ( +
    +      {children}
    +    
    + ), + strong: ({ children }) => {children}, + em: ({ children }) => {children}, + table: ({ children }) => ( +
    + {children}
    +
    + ), + thead: ({ children }) => {children}, + tbody: ({ children }) => {children}, + tr: ({ children }) => ( + + {children} + + ), + th: ({ children }) => ( + + {children} + + ), + td: ({ children }) => {children}, +}; diff --git a/studio/src/app/workspace/chat/_components/message-bubble.test.ts b/studio/src/app/workspace/chat/_components/message-bubble.test.ts new file mode 100644 index 0000000000..623239048f --- /dev/null +++ b/studio/src/app/workspace/chat/_components/message-bubble.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { splitLeadingQuote } from "./message-bubble"; + +describe("splitLeadingQuote", () => { + it("splits the leading quote block from the user's words", () => { + expect(splitLeadingQuote("> a\n> b\n\nmy question")).toEqual({ + quote: "a\nb", + rest: "my question", + }); + }); + it("leaves unquoted messages untouched", () => { + expect(splitLeadingQuote("plain > not a quote")).toEqual({ + quote: null, + rest: "plain > not a quote", + }); + }); + it("treats a mid-message quote as plain text", () => { + expect(splitLeadingQuote("hello\n> quoted later")).toEqual({ + quote: null, + rest: "hello\n> quoted later", + }); + }); + it("handles a quote-only message", () => { + expect(splitLeadingQuote("> just a quote")).toEqual({ + quote: "just a quote", + rest: "", + }); + }); +}); diff --git a/studio/src/app/workspace/chat/_components/message-bubble.tsx b/studio/src/app/workspace/chat/_components/message-bubble.tsx new file mode 100644 index 0000000000..52e2550e9b --- /dev/null +++ b/studio/src/app/workspace/chat/_components/message-bubble.tsx @@ -0,0 +1,356 @@ +"use client"; + +import { + AlertCircle, + Bot, + Copy, + ExternalLink, + FileCode2, + FileSpreadsheet, + FileText, + GitBranch, + Image as ImageIcon, + User, +} from "lucide-react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { Badge } from "@/components/ui/badge"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import type { AgentMessage, Artifact } from "@/features/agent"; +import { formatMessageTime } from "@/lib/formatters"; +import { + useAgentAvatar, + useUserAvatar, + useUserDisplayName, +} from "@/lib/profile-preferences"; +import { cn } from "@/lib/utils"; +import { mdComponents } from "./markdown-components"; +import { ToolCallList } from "./tool-call-list"; + +function UserAvatar({ small = false }: { small?: boolean }) { + const { avatarUrl } = useUserAvatar(); + // The reply indicator overlaps small avatars, so that size gets a + // background ring to separate the stack. + const frame = small + ? "size-5 shrink-0 ring-2 ring-background" + : "size-7 lg:size-9 shrink-0"; + if (avatarUrl) { + return ( + // biome-ignore lint/performance/noImgElement: a locally stored data URL, not a remote image + You + ); + } + return ( +
    + +
    + ); +} + +function BotAvatar({ small = false }: { small?: boolean }) { + const { avatarUrl } = useAgentAvatar(); + const frame = small + ? "size-5 shrink-0 ring-2 ring-background" + : "size-7 lg:size-9 shrink-0"; + if (avatarUrl) { + return ( + // biome-ignore lint/performance/noImgElement: a locally stored data URL, not a remote image + + ); + } + return ( +
    + +
    + ); +} + +/** + * Splits the leading "> " blockquote lines off a user message. Add-to-chat + * and thread-root quoting compose messages as a quote block, a blank line, + * then the user's own words — user text otherwise stays plain (never + * markdown-interpreted), so only this one deterministic shape gets styling. + */ +export function splitLeadingQuote(content: string): { + quote: string | null; + rest: string; +} { + const lines = content.split("\n"); + let i = 0; + while (i < lines.length && /^>\s?/.test(lines[i])) i++; + if (i === 0) return { quote: null, rest: content }; + const quote = lines + .slice(0, i) + .map((line) => line.replace(/^>\s?/, "")) + .join("\n"); + const rest = lines.slice(i).join("\n").replace(/^\n+/, ""); + return { quote, rest }; +} + +function MessageActions({ message }: { message: AgentMessage }) { + const copyContent = () => { + navigator.clipboard.writeText(message.content).catch(() => {}); + }; + + const btnClass = + "flex items-center justify-center size-6 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors"; + + return ( + +
    + + + + + + Copy to clipboard + + +
    +
    + ); +} + +const ARTIFACT_META: Record< + string, + { icon: typeof FileText; label: string; color: string; bg: string } +> = { + spreadsheet: { + icon: FileSpreadsheet, + label: "Spreadsheet", + color: "text-emerald-600", + bg: "bg-emerald-50 dark:bg-emerald-950/40", + }, + document: { + icon: FileText, + label: "Document", + color: "text-blue-600", + bg: "bg-blue-50 dark:bg-blue-950/40", + }, + code: { + icon: FileCode2, + label: "Code", + color: "text-violet-600", + bg: "bg-violet-50 dark:bg-violet-950/40", + }, + image: { + icon: ImageIcon, + label: "Image", + color: "text-amber-600", + bg: "bg-amber-50 dark:bg-amber-950/40", + }, + pdf: { + icon: FileText, + label: "PDF", + color: "text-red-600", + bg: "bg-red-50 dark:bg-red-950/40", + }, + markdown: { + icon: FileText, + label: "Markdown", + color: "text-sky-600", + bg: "bg-sky-50 dark:bg-sky-950/40", + }, +}; + +function ArtifactCard({ + artifact, + onClick, +}: { + artifact: Artifact; + onClick?: () => void; +}) { + const meta = ARTIFACT_META[artifact.type] ?? ARTIFACT_META.document; + const Icon = meta.icon; + + return ( + + ); +} + +export function MessageBubble({ + message, + onOpenArtifact, + botName = "Mecatl", + showActivity = true, +}: { + message: AgentMessage; + onOpenArtifact?: (artifact: Artifact) => void; + botName?: string; + showActivity?: boolean; +}) { + const isUser = message.role === "user"; + const { name: userName } = useUserDisplayName(); + + if (message.role === "tool") return null; + + const hasToolCalls = message.toolCalls && message.toolCalls.length > 0; + const hasContent = message.content?.trim(); + // Notices and delegations can repeat verbatim within a turn, so rows get + // positional ids up front to keep React keys unique. + const notices = (message.notices ?? []).map((text, index) => ({ + id: `${index}:${text}`, + text, + })); + const delegations = (message.delegations ?? []).map((d, index) => ({ + ...d, + id: `${index}:${d.kind}:${d.label}`, + })); + // A failed turn must always render (never look like an empty success), as + // must one that only carries notices or delegation badges. + const hasExtras = + Boolean(message.failed) || notices.length > 0 || delegations.length > 0; + + if (!isUser && !hasContent && !hasToolCalls && !hasExtras) return null; + if (!isUser && !hasContent && hasToolCalls && !showActivity && !hasExtras) + return null; + + return ( +
    +
    {isUser ? : }
    +
    +
    + + {isUser ? userName || "You" : (message.agentName ?? botName)} + + + {formatMessageTime(message.timestamp)} + +
    + {hasToolCalls && showActivity && message.toolCalls && ( + + )} + {delegations.length > 0 && ( +
    + {delegations.map((d) => ( + + + {d.kind}: {d.label} + {d.detail ? ` · ${d.detail}` : ""} + + ))} +
    + )} + {hasContent && ( +
    + {isUser ? ( + (() => { + const { quote, rest } = splitLeadingQuote(message.content); + return ( +
    + {quote && ( +
    + {quote} +
    + )} +
    {rest}
    +
    + ); + })() + ) : ( + + {message.content} + + )} +
    + )} + {notices.length > 0 && ( +
    + {notices.map((notice) => ( +

    + {notice.text} +

    + ))} +
    + )} + {message.failed && ( +
    + +
    +

    This turn failed

    + {message.failureDetail && ( +

    + {message.failureDetail} +

    + )} +
    +
    + )} + {message.artifact && ( + { + if (message.artifact) onOpenArtifact(message.artifact); + } + : undefined + } + /> + )} +
    +
    + +
    +
    + ); +} diff --git a/studio/src/app/workspace/chat/_components/mock-provider-notice.tsx b/studio/src/app/workspace/chat/_components/mock-provider-notice.tsx new file mode 100644 index 0000000000..9aa845fb0e --- /dev/null +++ b/studio/src/app/workspace/chat/_components/mock-provider-notice.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { TriangleAlert } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { useRuntimeStatus } from "@/features/agent/runtime-status"; + +/** + * Warns above the composer when the daemon is answering from the offline + * mock rather than a real model, with a one-click way off it: switch + * straight to the sole other configured provider when there is exactly one + * unambiguous choice, otherwise link to the provider settings page. External + * mode owns its own provider (the deployment, not this UI), so it renders + * nothing there. + */ +export function MockProviderNotice() { + const { + mode, + isMock, + configuredProviders, + toolhiveAvailable, + switchProvider, + } = useRuntimeStatus(); + const [switching, setSwitching] = useState(false); + const [switchError, setSwitchError] = useState(null); + + if (mode !== "managed" || !isMock) return null; + + const quickTarget = + configuredProviders.length === 1 && !toolhiveAvailable + ? configuredProviders[0] + : configuredProviders.length === 0 && toolhiveAvailable + ? "toolhive" + : null; + + const handleSwitch = async () => { + if (!quickTarget) return; + setSwitching(true); + setSwitchError(null); + try { + await switchProvider(quickTarget); + } catch (caught) { + setSwitchError(caught instanceof Error ? caught.message : String(caught)); + } finally { + setSwitching(false); + } + }; + + return ( +
    +
    + +

    + Running on the offline mock provider — replies are canned, not from a + real model. +

    + {quickTarget ? ( + + ) : ( + + )} +
    + {switchError &&

    {switchError}

    } +
    + ); +} diff --git a/studio/src/app/workspace/chat/_components/session-sidebar.tsx b/studio/src/app/workspace/chat/_components/session-sidebar.tsx new file mode 100644 index 0000000000..2f36257e68 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/session-sidebar.tsx @@ -0,0 +1,340 @@ +"use client"; + +import { + Bot, + ChevronDown, + ChevronUp, + Ellipsis, + Pencil, + Trash2, +} from "lucide-react"; +import { useState } from "react"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import type { AgentSession, RosterAgent } from "@/features/agent"; +import { formatRelativeTime } from "@/lib/formatters"; +import { cn } from "@/lib/utils"; + +export interface SessionActions { + onRename: (id: string) => void; + onDelete: (id: string) => void; +} + +export function SidebarGroup({ + label, + children, +}: { + label: string; + children: React.ReactNode; +}) { + return ( +
    + + {label} + + {children} +
    + ); +} + +function SessionContextMenu({ + session, + actions, + children, + onOpenChange, +}: { + session: AgentSession; + actions: SessionActions; + children: React.ReactNode; + onOpenChange?: (open: boolean) => void; +}) { + // Eligibility comes from the daemon row's capabilities; an omitted + // capability is a denial. A disabled item shows the daemon's reason inline + // (disabled menu items swallow pointer events, so a tooltip can't open). + const canRename = session.canRename === true; + const canDelete = session.canDelete === true; + + return ( + + {children} + + actions.onRename(session.id)} + title={!canRename ? session.renameReason : undefined} + > + + + Rename + {!canRename && session.renameReason && ( + + {session.renameReason} + + )} + + + actions.onDelete(session.id)} + title={!canDelete ? session.deleteReason : undefined} + > + + + Delete + {!canDelete && session.deleteReason && ( + + {session.deleteReason} + + )} + + + + + ); +} + +function SessionRow({ + session, + isSelected, + onSelect, + actions, +}: { + session: AgentSession; + isSelected: boolean; + onSelect: (id: string) => void; + actions: SessionActions; +}) { + const [menuOpen, setMenuOpen] = useState(false); + const isRunning = session.isStreaming || session.state === "running"; + + return ( +
    + +
    + {isRunning ? ( + + ) : ( + + {formatRelativeTime(session.updatedAt)} + + )} + + + +
    +
    + ); +} + +export function SessionList({ + sessions, + selectedId, + onSelect, + actions, + cap = 8, +}: { + sessions: AgentSession[]; + selectedId: string; + onSelect: (id: string) => void; + actions: SessionActions; + /** Rows shown before the Show-more expander (which reveals ALL rows). */ + cap?: number; +}) { + const [showAll, setShowAll] = useState(false); + const visible = showAll ? sessions : sessions.slice(0, cap); + + return ( +
    + {visible.map((session) => ( + + ))} + {sessions.length > cap && ( + + )} +
    + ); +} + +/** + * The def color hint is frontmatter relayed verbatim, so only a small safe + * subset is honored as a CSS color: common named colors or a hex literal. + * Anything else falls back to the default muted tint. + */ +const AGENT_COLOR_NAMES = new Set([ + "red", + "orange", + "amber", + "yellow", + "lime", + "green", + "emerald", + "teal", + "cyan", + "sky", + "blue", + "indigo", + "violet", + "purple", + "magenta", + "fuchsia", + "pink", + "rose", + "brown", + "gray", + "grey", +]); + +function safeAgentColor(color: string): string | undefined { + const value = color.trim().toLowerCase(); + if (AGENT_COLOR_NAMES.has(value)) return value; + return /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/.test(value) ? value : undefined; +} + +/** Title-attribute tooltip: description, then the def's scope details. */ +function agentRowTooltip(agent: RosterAgent): string | undefined { + const lines: string[] = []; + if (agent.description) lines.push(agent.description); + const details: string[] = []; + details.push(`permissions: ${agent.permissionMode || "default"}`); + if (agent.tools.length > 0) details.push(`tools: ${agent.tools.join(", ")}`); + lines.push(details.join(" · ")); + return lines.join("\n"); +} + +/** + * The daemon's real agent roster, listed below the chat groups. Agents are + * not chat containers — selecting one simply starts a new chat draft. Each + * row shows the def's pinned model ("auto" when it inherits), tints its + * avatar with the def's color hint, and carries tools + permission mode in + * the tooltip (D2.2). + */ +export function AgentList({ + agents, + onStartChat, +}: { + agents: RosterAgent[]; + onStartChat: () => void; +}) { + return ( +
    + {agents.map((agent) => { + const tint = safeAgentColor(agent.color); + return ( + + ); + })} +
    + ); +} diff --git a/studio/src/app/workspace/chat/_components/tool-call-list.test.ts b/studio/src/app/workspace/chat/_components/tool-call-list.test.ts new file mode 100644 index 0000000000..d782765c45 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/tool-call-list.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from "vitest"; +import { activitySummary } from "./tool-call-list"; + +const call = (status: "running" | "completed" | "failed") => ({ status }); + +/** + * The collapsed "Activity" line's text: the count fragment, pluralized only + * past one call. + */ +describe("activitySummary", () => { + it("counts every call regardless of status", () => { + expect(activitySummary([call("completed"), call("running")])).toEqual({ + tools: "2 tools", + }); + }); + + it("pluralizes only the tool count", () => { + expect(activitySummary([call("failed")]).tools).toBe("1 tool"); + }); +}); diff --git a/studio/src/app/workspace/chat/_components/tool-call-list.tsx b/studio/src/app/workspace/chat/_components/tool-call-list.tsx new file mode 100644 index 0000000000..eabe4c3532 --- /dev/null +++ b/studio/src/app/workspace/chat/_components/tool-call-list.tsx @@ -0,0 +1,68 @@ +"use client"; + +import { ChevronRight, Plug } from "lucide-react"; +import { useState } from "react"; +import type { ToolCallInfo } from "@/features/agent"; +import { cn } from "@/lib/utils"; + +function formatPreview(output: string | undefined): string { + if (!output) return ""; + const clean = output.replace(/\n/g, " ").trim(); + return clean.length > 120 ? `${clean.slice(0, 120)}...` : clean; +} + +/** + * The collapsed summary line's text: "3 tools", pluralized only past one + * call. Split so the component can render the count without re-deriving it. + */ +export function activitySummary(toolCalls: Pick[]): { + tools: string; +} { + const count = toolCalls.length; + return { + tools: `${count} tool${count === 1 ? "" : "s"}`, + }; +} + +export function ToolCallList({ toolCalls }: { toolCalls: ToolCallInfo[] }) { + const [expanded, setExpanded] = useState(false); + const toolNames = toolCalls.map((t) => t.name).join(" · "); + const summary = activitySummary(toolCalls); + + return ( +
    + + {expanded && ( +
    + {toolCalls.map((tc) => ( +
    + + {tc.name} + {tc.output && ( + {formatPreview(tc.output)} + )} +
    + ))} +
    + )} +
    + ); +} diff --git a/studio/src/features/agent/hooks/use-agent-chat.test.ts b/studio/src/features/agent/hooks/use-agent-chat.test.ts new file mode 100644 index 0000000000..79e6ce86d3 --- /dev/null +++ b/studio/src/features/agent/hooks/use-agent-chat.test.ts @@ -0,0 +1,41 @@ +import { act, renderHook } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { useAgentChat } from "./use-agent-chat"; + +// The hook's network surface is exercised against the fixture daemon (e2e); +// here we pin the client-side resting shape every later stage builds on. +vi.mock("../runtime-status", () => ({ + useRuntimeStatus: () => ({ + connected: false, + features: new Set(), + serverCapabilities: {}, + }), +})); + +describe("useAgentChat", () => { + it("opens a draft chat idle: empty transcript, no approval, zero usage", () => { + const { result } = renderHook(() => useAgentChat(null)); + expect(result.current.messages).toEqual([]); + expect(result.current.status).toBe("idle"); + expect(result.current.isStreaming).toBe(false); + expect(result.current.harnessLive).toBe(false); + expect(result.current.pendingApproval).toBeNull(); + expect(result.current.usage).toEqual({ + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheWriteTokens: 0, + reasoningTokens: 0, + estimatedCost: null, + }); + }); + + it("refuses to send while the daemon is unreachable — no optimistic bubble", async () => { + const { result } = renderHook(() => useAgentChat(null)); + await act(async () => { + await result.current.sendMessage("hello"); + }); + expect(result.current.messages).toEqual([]); + expect(result.current.status).toBe("idle"); + }); +}); diff --git a/studio/src/features/agent/hooks/use-agent-chat.ts b/studio/src/features/agent/hooks/use-agent-chat.ts new file mode 100644 index 0000000000..7fb6bdb110 --- /dev/null +++ b/studio/src/features/agent/hooks/use-agent-chat.ts @@ -0,0 +1,512 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { + cancelHarnessRun, + createHarnessSession, + fetchSessionTranscriptMessages, + respondToHarnessApproval, + streamHarnessPrompt, +} from "@/lib/harness/client"; +import { + encodeSessionPermissionMode, + type SessionPermissionMode, + type SessionTranscript, +} from "@/lib/protocol"; +import { useRuntimeStatus } from "../runtime-status"; +import type { + AgentMessage, + ApprovalChoice, + ApprovalRequest, + ClarificationRequest, + StreamEvent, + ToolCallInfo, +} from "../types"; + +type ChatStatus = + | "idle" + | "streaming" + | "waiting_approval" + | "waiting_clarification" + | "error"; + +function messagesFromTranscript(transcript: SessionTranscript): AgentMessage[] { + const messages: AgentMessage[] = []; + let sequence = 0; + for (const entry of transcript.messages) { + sequence += 1; + if (entry.role === "user") { + messages.push({ + id: `history-user-${sequence}`, + role: "user", + content: entry.text, + timestamp: 0, + }); + continue; + } + if (entry.role === "assistant") { + messages.push({ + id: `history-assistant-${sequence}`, + role: "assistant", + content: entry.text, + timestamp: 0, + toolCalls: entry.toolCalls.length + ? entry.toolCalls.map((call) => ({ + callId: call.id, + name: call.name, + input: call.args, + status: "completed" as const, + })) + : undefined, + }); + continue; + } + // A tool entry resolves the matching call on the latest assistant turn. + const result = entry.toolResult; + if (!result) continue; + for (let index = messages.length - 1; index >= 0; index -= 1) { + const message = messages[index]; + const call = message.toolCalls?.find( + (candidate) => candidate.callId === result.callId, + ); + if (call) { + call.output = result.content; + call.isError = result.isError; + call.status = result.isError ? "failed" : "completed"; + break; + } + } + } + if (!transcript.complete && messages.length) { + messages[0] = { + ...messages[0], + notices: [ + "This transcript could not be proven complete; earlier turns may be missing.", + ...(messages[0].notices ?? []), + ], + }; + } + return messages; +} + +/** + * Chat state for one daemon session. Daemon-only: the sidebar id IS the + * daemon session id — there is no client-side session mapping and no demo + * fallback. Opening a chat rehydrates its history from the authoritative + * transcript endpoint; a null id is a draft whose daemon session is minted on + * the first send. + */ +export function useAgentChat( + sessionId: string | null, + options?: { + onSessionCreated?: (sessionId: string) => void; + /** The permission mode a draft's lazily-minted session is created with + * (the composer's pending Mode selection). Read at mint time — a ref-like + * getter, because the selection can change after this render's closure. */ + createMode?: () => SessionPermissionMode; + /** The composer's pending model pick ("" = auto-routed); read at mint + * time like createMode. */ + createModel?: () => { modelId: string; providerId: string } | null; + }, +) { + const { connected } = useRuntimeStatus(); + const [messages, setMessages] = useState([]); + const [status, setStatus] = useState("idle"); + const [error, setError] = useState(null); + const [pendingApproval, setPendingApproval] = + useState(null); + const [pendingClarification] = useState(null); + const [usage, setUsage] = useState({ + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheWriteTokens: 0, + reasoningTokens: 0, + estimatedCost: null as number | null, + }); + + // The daemon session backing this chat: the route id, or the one minted for + // a draft on first send. A ref so an in-flight stream keeps its binding + // while the parent navigates to the new id. + const daemonIdRef = useRef(sessionId); + const abortRef = useRef(null); + const lastPromptRef = useRef(null); + + const onSessionCreatedRef = useRef(options?.onSessionCreated); + onSessionCreatedRef.current = options?.onSessionCreated; + const createModeRef = useRef(options?.createMode); + createModeRef.current = options?.createMode; + const createModelRef = useRef(options?.createModel); + createModelRef.current = options?.createModel; + + /** + * Rebuilds the message list from the daemon's authoritative transcript. + */ + const rehydrate = useCallback(async (id: string, signal?: AbortSignal) => { + const transcript = await fetchSessionTranscriptMessages(id, signal); + if (signal?.aborted) return; + const rebuilt = messagesFromTranscript(transcript); + setMessages(rebuilt); + }, []); + + // Opening a chat (or switching chats) rehydrates from the daemon. + useEffect(() => { + daemonIdRef.current = sessionId; + setMessages([]); + setPendingApproval(null); + setError(null); + setStatus("idle"); + // Usage is per-visit, per-chat: the context meter must not carry one + // chat's spend into the next. + setUsage({ + inputTokens: 0, + outputTokens: 0, + cacheReadTokens: 0, + cacheWriteTokens: 0, + reasoningTokens: 0, + estimatedCost: null, + }); + if (!sessionId || !connected) return; + const controller = new AbortController(); + void rehydrate(sessionId, controller.signal).catch((caught) => { + if (controller.signal.aborted) return; + setError(caught instanceof Error ? caught.message : String(caught)); + setStatus("error"); + }); + return () => controller.abort(); + }, [sessionId, connected, rehydrate]); + + /** Builds the per-run stream-event handler the prompt stream drives. */ + const makeStreamHandler = useCallback( + (daemonId: string, ids: { assistant: string }) => { + // Every update is a functional setState: tokens and tool results arrive + // faster than React commits, so reading the previous array from the + // closure would drop frames (rerender-functional-setstate). + const patch = (apply: (message: AgentMessage) => AgentMessage) => + setMessages((prev) => + prev.map((message) => + message.id === ids.assistant ? apply(message) : message, + ), + ); + return (event: StreamEvent) => { + switch (event.type) { + case "token": + patch((message) => ({ + ...message, + content: message.content + event.text, + })); + break; + case "reasoning": + patch((message) => ({ + ...message, + reasoning: (message.reasoning ?? "") + event.text, + })); + break; + case "tool_call": { + const call: ToolCallInfo = { + callId: event.callId, + name: event.name, + input: event.input, + status: "running", + }; + patch((message) => ({ + ...message, + toolCalls: [...(message.toolCalls ?? []), call], + })); + break; + } + case "tool_result": + patch((message) => ({ + ...message, + toolCalls: (message.toolCalls ?? []).map((call) => + call.callId === event.callId + ? { + ...call, + output: event.output, + isError: event.isError, + status: event.isError ? "failed" : "completed", + } + : call, + ), + })); + break; + case "approval": + setPendingApproval({ + approvalId: event.approvalId, + sessionId: daemonId, + toolName: event.toolName, + description: event.description, + details: event.details, + }); + setStatus("waiting_approval"); + break; + case "retract": + // The ask was withdrawn (e.g. its child was cancelled); the + // run is still going. + setPendingApproval((current) => + current?.approvalId === event.approvalId ? null : current, + ); + setStatus((current) => + current === "waiting_approval" ? "streaming" : current, + ); + break; + case "notice": + patch((message) => ({ + ...message, + notices: [...(message.notices ?? []), event.text], + })); + break; + case "delegation": + patch((message) => ({ + ...message, + delegations: [ + ...(message.delegations ?? []), + { + kind: event.kind, + label: event.label, + detail: event.detail, + childId: event.childId, + background: event.background, + routingReason: event.routingReason, + }, + ], + })); + break; + case "usage": + // The daemon reports per-run figures; the chat total is + // their sum. (Lost on reload: the HTTP read surface does + // not expose the session's cumulative usage yet.) + setUsage((prev) => ({ + inputTokens: prev.inputTokens + event.inputTokens, + outputTokens: prev.outputTokens + event.outputTokens, + cacheReadTokens: + prev.cacheReadTokens + (event.cacheReadTokens ?? 0), + cacheWriteTokens: + prev.cacheWriteTokens + (event.cacheWriteTokens ?? 0), + reasoningTokens: + prev.reasoningTokens + (event.reasoningTokens ?? 0), + estimatedCost: event.estimatedCost, + })); + break; + case "run_result": + if (event.stop === "error") { + const detail = + event.errorText || "The run failed without a specific error."; + patch((message) => ({ + ...message, + failed: true, + failureDetail: event.permanent + ? `${detail} (permanent — retrying the identical request cannot succeed)` + : detail, + toolCalls: (message.toolCalls ?? []).map((call) => + call.status === "running" + ? { ...call, status: "failed" as const } + : call, + ), + })); + setError(detail); + setStatus("error"); + } else if (event.text) { + // A run that produced no deltas (a rehydrated approve, a + // recovered run) still carries its final text here. + patch((message) => ({ + ...message, + content: message.content || event.text, + })); + } + break; + default: + break; + } + }; + }, + [], + ); + + const sendMessage = useCallback( + async (content: string) => { + if (!connected) return; + if (status === "streaming" || status === "waiting_approval") return; + + const userMessage: AgentMessage = { + id: `user-${Date.now()}`, + role: "user", + content, + timestamp: Date.now(), + }; + + setMessages((prev) => [...prev, userMessage]); + setStatus("streaming"); + setError(null); + lastPromptRef.current = content; + + const ids = { assistant: `assistant-${Date.now()}` }; + setMessages((prev) => [ + ...prev, + { + id: ids.assistant, + role: "assistant", + content: "", + timestamp: Date.now(), + }, + ]); + + const controller = new AbortController(); + abortRef.current = controller; + + // Failure patches outside the stream handler target the assistant + // bubble. + const patch = (apply: (message: AgentMessage) => AgentMessage) => + setMessages((prev) => + prev.map((message) => + message.id === ids.assistant ? apply(message) : message, + ), + ); + + let daemonId = daemonIdRef.current; + try { + if (!daemonId) { + const createModel = createModelRef.current?.() ?? null; + daemonId = await createHarnessSession( + encodeSessionPermissionMode(createModeRef.current?.() ?? "default"), + createModel + ? { + modelId: createModel.modelId, + providerId: createModel.providerId, + } + : {}, + ); + daemonIdRef.current = daemonId; + onSessionCreatedRef.current?.(daemonId); + } + await streamHarnessPrompt( + daemonId, + content, + [], + makeStreamHandler(daemonId, ids), + controller.signal, + ); + // A parked approval keeps its own status: the stream ends while the + // run is still waiting on the operator, and flipping to idle here + // would hide the pending prompt. A failed turn keeps its error state. + setStatus((current) => + current === "waiting_approval" || current === "error" + ? current + : "idle", + ); + } catch (caught) { + if (controller.signal.aborted) { + setStatus("idle"); + return; + } + const message = + caught instanceof Error ? caught.message : String(caught); + setError(message); + setStatus("error"); + patch((current) => ({ + ...current, + failed: true, + failureDetail: current.failureDetail ?? message, + toolCalls: (current.toolCalls ?? []).map((call) => + call.status === "running" + ? { ...call, status: "failed" as const } + : call, + ), + })); + } finally { + abortRef.current = null; + } + }, + [status, connected, makeStreamHandler], + ); + + /** Re-sends the last prompt after a failure — the error banner's Retry. */ + const retryLast = useCallback(async () => { + if (status === "streaming") return; + const prompt = lastPromptRef.current; + if (!prompt) return; + // Drop the failed exchange so the retry replaces it instead of stacking. + setMessages((prev) => { + const trimmed = [...prev]; + while (trimmed.length) { + const last = trimmed[trimmed.length - 1]; + if (last.role === "assistant" && (last.failed || !last.content)) { + trimmed.pop(); + continue; + } + if (last.role === "user" && last.content === prompt) { + trimmed.pop(); + } + break; + } + return trimmed; + }); + setError(null); + setStatus("idle"); + await sendMessage(prompt); + }, [status, sendMessage]); + + const cancelChat = useCallback(async () => { + abortRef.current?.abort(); + if (daemonIdRef.current) { + await cancelHarnessRun(daemonIdRef.current); + } + setStatus("idle"); + }, []); + + const respondToApproval = useCallback( + async (choice: ApprovalChoice) => { + const daemonId = daemonIdRef.current; + const approvalId = pendingApproval?.approvalId; + setPendingApproval(null); + // The verdict resumes the SAME run — the prompt stream stays open and + // keeps delivering (the daemon acks the approve; only the run's end + // closes the stream). Mirror the retract handler: back to streaming, + // never idle. The stream's own end handler owns the eventual idle. + setStatus((current) => + current === "waiting_approval" ? "streaming" : current, + ); + if (!daemonId || !approvalId) return; + try { + // The daemon's verdict is three-way. "session" and "always" both map + // to allow_always — the daemon models one persistent grant scope, and + // splitting hairs the backend does not model would be a lie in the UI. + await respondToHarnessApproval( + daemonId, + approvalId, + choice === "deny" + ? "deny" + : choice === "once" + ? "allow_once" + : "allow_always", + ); + } catch (caught) { + setError(caught instanceof Error ? caught.message : String(caught)); + setStatus("error"); + } + }, + [pendingApproval], + ); + + const respondToClarification = useCallback(async (_response: string) => { + setStatus("idle"); + }, []); + + return { + messages, + // A parked approval is still an in-flight run daemon-side; the composer + // treats both as "run active". + isStreaming: status === "streaming" || status === "waiting_approval", + status, + error, + harnessLive: connected, + sendMessage, + retryLast, + cancelChat, + pendingApproval, + pendingClarification, + respondToApproval, + respondToClarification, + usage, + }; +} diff --git a/studio/src/features/agent/index.ts b/studio/src/features/agent/index.ts index 15547a93a9..09468e3fa9 100644 --- a/studio/src/features/agent/index.ts +++ b/studio/src/features/agent/index.ts @@ -1,4 +1,5 @@ // Hooks +export { useAgentChat } from "./hooks/use-agent-chat"; export { useAgentCron } from "./hooks/use-agent-cron"; export { useAgentMemory } from "./hooks/use-agent-memory"; export { type RosterAgent, useAgentRoster } from "./hooks/use-agent-roster"; diff --git a/studio/src/hooks/use-confirm.tsx b/studio/src/hooks/use-confirm.tsx new file mode 100644 index 0000000000..d4e54ae23b --- /dev/null +++ b/studio/src/hooks/use-confirm.tsx @@ -0,0 +1,87 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { cn } from "@/lib/utils"; + +interface ConfirmOptions { + title?: string; + description?: string; + confirmText?: string; + cancelText?: string; + destructive?: boolean; +} + +export function useConfirm() { + const [open, setOpen] = useState(false); + const [resolvePromise, setResolvePromise] = useState< + ((value: boolean) => void) | null + >(null); + const [options, setOptions] = useState({}); + + const confirm = useCallback((opts: ConfirmOptions = {}): Promise => { + return new Promise((resolve) => { + setOptions(opts); + setOpen(true); + setResolvePromise(() => resolve); + }); + }, []); + + const handleConfirm = useCallback(() => { + setOpen(false); + if (resolvePromise) { + resolvePromise(true); + setResolvePromise(null); + } + }, [resolvePromise]); + + const handleCancel = useCallback(() => { + setOpen(false); + if (resolvePromise) { + resolvePromise(false); + setResolvePromise(null); + } + }, [resolvePromise]); + + const ConfirmDialog = ( + + + + + {options.title || "Are you sure?"} + + {options.description && ( + + {options.description} + + )} + + + + {options.cancelText || "Cancel"} + + + {options.confirmText || "Confirm"} + + + + + ); + + return { confirm, ConfirmDialog }; +} diff --git a/studio/src/hooks/use-nav-reopen-sidebar.ts b/studio/src/hooks/use-nav-reopen-sidebar.ts new file mode 100644 index 0000000000..1455a1a307 --- /dev/null +++ b/studio/src/hooks/use-nav-reopen-sidebar.ts @@ -0,0 +1,11 @@ +"use client"; + +import { useEffect } from "react"; + +export function useNavReopenSidebar(setSidebarOpen: (open: boolean) => void) { + useEffect(() => { + const handler = () => setSidebarOpen(true); + window.addEventListener("nav-reopen-sidebar", handler); + return () => window.removeEventListener("nav-reopen-sidebar", handler); + }, [setSidebarOpen]); +} diff --git a/studio/src/hooks/use-panel-width.ts b/studio/src/hooks/use-panel-width.ts new file mode 100644 index 0000000000..8c2718fc98 --- /dev/null +++ b/studio/src/hooks/use-panel-width.ts @@ -0,0 +1,45 @@ +"use client"; + +import { useCallback, useSyncExternalStore } from "react"; + +/** + * One persisted width, shared by every resizable chat panel — the session + * list and the threaded/file side panels read and write the same setting, so + * a resize in one carries to the others and survives reloads. + */ +const STORAGE_KEY = "workspace-panel-width"; +const DEFAULT_WIDTH = 400; +const MIN_WIDTH = 200; +const MAX_WIDTH = 720; + +const listeners = new Set<() => void>(); + +function getSnapshot(): number { + const stored = localStorage.getItem(STORAGE_KEY); + if (!stored) return DEFAULT_WIDTH; + const n = Number(stored); + return Number.isFinite(n) + ? Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, n)) + : DEFAULT_WIDTH; +} + +function getServerSnapshot(): number { + return DEFAULT_WIDTH; +} + +function subscribe(callback: () => void): () => void { + listeners.add(callback); + return () => listeners.delete(callback); +} + +export function usePanelWidth() { + const width = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); + + const setWidth = useCallback((next: number) => { + const clamped = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, next)); + localStorage.setItem(STORAGE_KEY, String(clamped)); + for (const fn of listeners) fn(); + }, []); + + return [width, setWidth] as const; +} diff --git a/studio/src/hooks/use-prompt.tsx b/studio/src/hooks/use-prompt.tsx new file mode 100644 index 0000000000..ea315760d4 --- /dev/null +++ b/studio/src/hooks/use-prompt.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { useCallback, useEffect, useRef, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Input } from "@/components/ui/input"; + +interface PromptOptions { + title?: string; + description?: string; + placeholder?: string; + defaultValue?: string; + confirmText?: string; + cancelText?: string; +} + +export function usePrompt() { + const [open, setOpen] = useState(false); + const [value, setValue] = useState(""); + const [resolvePromise, setResolvePromise] = useState< + ((value: string | null) => void) | null + >(null); + const [options, setOptions] = useState({}); + const inputRef = useRef(null); + + const prompt = useCallback( + (opts: PromptOptions = {}): Promise => { + return new Promise((resolve) => { + setOptions(opts); + setValue(opts.defaultValue ?? ""); + setOpen(true); + setResolvePromise(() => resolve); + }); + }, + [], + ); + + useEffect(() => { + if (open) { + const timer = setTimeout(() => inputRef.current?.focus(), 0); + return () => clearTimeout(timer); + } + }, [open]); + + const handleConfirm = useCallback(() => { + if (!value.trim()) return; + setOpen(false); + if (resolvePromise) { + resolvePromise(value.trim()); + setResolvePromise(null); + } + }, [resolvePromise, value]); + + const handleCancel = useCallback(() => { + setOpen(false); + if (resolvePromise) { + resolvePromise(null); + setResolvePromise(null); + } + }, [resolvePromise]); + + const PromptDialog = ( + !v && handleCancel()}> + + + {options.title || "Enter a value"} + {options.description && ( + {options.description} + )} + + setValue(e.target.value)} + placeholder={options.placeholder} + onKeyDown={(e) => { + if (e.key === "Enter") handleConfirm(); + }} + /> + + + + + + + ); + + return { prompt, PromptDialog }; +} diff --git a/studio/src/lib/formatters.ts b/studio/src/lib/formatters.ts index 2f764c003d..4e11b8b86a 100644 --- a/studio/src/lib/formatters.ts +++ b/studio/src/lib/formatters.ts @@ -1,3 +1,25 @@ +export function formatRelativeTime(ts: number): string { + if (!ts || ts < 1000) return ""; + try { + const diffMs = Date.now() - ts; + const diffMin = Math.floor(diffMs / 60000); + if (diffMin < 1) return "<1m"; + if (diffMin < 60) return `${diffMin}m`; + const diffHr = Math.floor(diffMin / 60); + if (diffHr < 24) return `${diffHr}h`; + const diffDay = Math.floor(diffHr / 24); + return `${diffDay}d`; + } catch { + return ""; + } +} + +export function formatTokens(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`; + if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`; + return String(n); +} + const CRON_DAYS = [ "Sunday", "Monday", @@ -60,3 +82,13 @@ export function describeCron(expr: string): string { } return expr; } + +export function formatMessageTime(ts: number): string { + if (!ts || ts < 1000) return ""; + const d = new Date(ts); + return d.toLocaleTimeString(undefined, { + hour: "2-digit", + minute: "2-digit", + hour12: false, + }); +} diff --git a/studio/src/lib/profile-preferences.ts b/studio/src/lib/profile-preferences.ts index 820025c324..ca00ed804b 100644 --- a/studio/src/lib/profile-preferences.ts +++ b/studio/src/lib/profile-preferences.ts @@ -8,6 +8,12 @@ import { useCallback, useEffect, useState } from "react"; * server-side "agent name" or user-identity record — so these live in * localStorage only, same as the appearance/notification prefs on this page. */ +const AGENT_NAME_KEY = "mecatl-studio.agent-name"; +const AVATAR_KEY = "mecatl-studio.user-avatar"; +const USER_NAME_KEY = "mecatl-studio.user-name"; +const AGENT_AVATAR_KEY = "mecatl-studio.agent-avatar"; +const SESSION_LIST_SIDE_KEY = "mecatl-studio.session-list-side"; +const DEFAULT_AGENT_NAME = "Mecatl"; function readLocalStorage(key: string): string | null { if (typeof window === "undefined") return null; @@ -28,6 +34,51 @@ function writeLocalStorage(key: string, value: string | null) { } } +/** The agent's display name shown in chat, e.g. "Mecatl" or "Astra". */ +export function useAgentDisplayName() { + const [name, setNameState] = useState(DEFAULT_AGENT_NAME); + useEffect(() => { + const stored = readLocalStorage(AGENT_NAME_KEY); + if (stored) setNameState(stored); + }, []); + + const setName = useCallback((next: string) => { + const trimmed = next.trim(); + const value = trimmed || DEFAULT_AGENT_NAME; + setNameState(value); + writeLocalStorage(AGENT_NAME_KEY, trimmed ? value : null); + }, []); + + return { name, setName, defaultName: DEFAULT_AGENT_NAME }; +} + +function useStoredAvatar(key: string) { + const [avatarUrl, setAvatarUrlState] = useState(null); + useEffect(() => { + setAvatarUrlState(readLocalStorage(key)); + }, [key]); + + const setAvatarUrl = useCallback( + (next: string | null) => { + setAvatarUrlState(next); + writeLocalStorage(key, next); + }, + [key], + ); + + return { avatarUrl, setAvatarUrl }; +} + +/** The user's profile picture, stored as a data URL (no upload endpoint exists). */ +export function useUserAvatar() { + return useStoredAvatar(AVATAR_KEY); +} + +/** The agent's picture, replacing the default bot mark in chat when set. */ +export function useAgentAvatar() { + return useStoredAvatar(AGENT_AVATAR_KEY); +} + const UI_SCALE_MIN = 0.85; const UI_SCALE_MAX = 1.3; @@ -76,3 +127,45 @@ export function useUiScale() { return { scale, setScale }; } + +export type SessionListSide = "left" | "right"; + +/** + * Which side of the chat the session list docks on. The thread and document + * panels stay on the right regardless — only the list moves. + */ +export function useSessionListSide() { + const [side, setSideState] = useState("right"); + useEffect(() => { + if (readLocalStorage(SESSION_LIST_SIDE_KEY) === "left") { + setSideState("left"); + } + }, []); + + const setSide = useCallback((next: SessionListSide) => { + setSideState(next); + writeLocalStorage(SESSION_LIST_SIDE_KEY, next === "left" ? "left" : null); + }, []); + + return { side, setSide }; +} + +/** + * The user's display name — browser-local, cosmetic. It labels your chat + * messages in Studio; the AGENT learns your name in conversation (its memory + * stores user/identity/name itself — Studio has no write path into the + * daemon's user model by design). + */ +export function useUserDisplayName() { + const [name, setNameState] = useState(""); + useEffect(() => { + const stored = readLocalStorage(USER_NAME_KEY); + if (stored) setNameState(stored); + }, []); + const setName = useCallback((value: string) => { + setNameState(value); + const trimmed = value.trim(); + writeLocalStorage(USER_NAME_KEY, trimmed ? value : null); + }, []); + return { name, setName }; +} diff --git a/studio/tests/e2e/fixture-daemon.mjs b/studio/tests/e2e/fixture-daemon.mjs new file mode 100644 index 0000000000..2a4a696821 --- /dev/null +++ b/studio/tests/e2e/fixture-daemon.mjs @@ -0,0 +1,126 @@ +/** + * A fixture mecated for e2e: a tiny HTTP server speaking just enough of the + * daemon's wire (stdlib-JSON responses, SSE prompt relay) for Studio's + * surfaces to render real content through the real proxy tier. This is a test + * double behind the API seam — not a UI-level demo fallback, which Studio + * forbids. + */ +import http from "node:http"; + +const port = Number(process.env.FIXTURE_DAEMON_PORT || 8099); + +const session = { + session_id: "session-fixture-1", + title: "Fix the flaky scheduler test", + state: "idle", + turns: 2, + model_id: "fixture-model", + workspace: "/workspace/fixture", + created_at_unix: 1_755_000_000, + modified_at_unix: 1_755_003_600, + capabilities: { + rename: true, + delete: true, + view_transcript: true, + reasons: {}, + }, +}; + +const routes = { + "GET /v1/models": { + models: [{ id: "fixture-model", provider_id: "fixture" }], + }, + "GET /v1/sessions": { sessions: [session], next_cursor: "" }, + "GET /v1/sessions/session-fixture-1/transcript": { + session_id: "session-fixture-1", + complete: true, + messages: [ + { role: "user", text: "Why does the scheduler test flake?" }, + { + role: "assistant", + text: "The fixture transcript renders: the test races the claim sentinel.", + }, + ], + }, + "GET /v1/schedules": { + schedules: [ + { + spec: { + name: "nightly-fixture-digest", + prompt: "Summarise the day", + trigger: { cron: "0 9 * * *" }, + timezone: "UTC", + mode: 2, + mutating: false, + }, + state: { enabled: true, fire_count: 3 }, + }, + ], + }, + "GET /v1/schedules/nightly-fixture-digest/fires": { + fires: [ + { + id: "fire-1", + schedule_name: "nightly-fixture-digest", + session_id: "sched--nightly-fixture-digest-1", + fired_at: { seconds: 1_755_000_000 }, + stop: "end_turn", + }, + ], + }, + "GET /v1/skills": { + skills: [ + { + name: "code-review-fixture", + description: "Review a diff for correctness.", + }, + ], + }, + "GET /v1/usermodel": { + entries: [ + { + key: "prefers-tabs", + description: "The operator prefers tabs over spaces.", + }, + ], + size_bytes: 64, + sha256: "f".repeat(64), + }, + "GET /v1/agents": { + agents: [{ name: "reviewer", description: "Reviews changes." }], + }, + "GET /v1/commands": { commands: [] }, +}; + +http + .createServer((request, response) => { + const path = new URL(request.url, "http://fixture").pathname; + const key = `${request.method} ${path}`; + if (key === "POST /v1/sessions/session-fixture-1/prompt") { + response.writeHead(200, { "Content-Type": "text/event-stream" }); + const frames = [ + { type: "message.delta", text: "Streaming " }, + { type: "message.delta", text: "from the fixture." }, + { + type: "result", + result: { + stop: "end_turn", + text: "Streaming from the fixture.", + usage: { input_tokens: 10, output_tokens: 5 }, + }, + }, + ]; + for (const frame of frames) + response.write(`data: ${JSON.stringify(frame)}\n\n`); + response.end(); + return; + } + const body = routes[key]; + response.writeHead(body ? 200 : 404, { + "Content-Type": "application/json", + }); + response.end(JSON.stringify(body ?? { error: `fixture has no ${key}` })); + }) + .listen(port, "127.0.0.1", () => { + console.log(`fixture daemon on 127.0.0.1:${port}`); + }); diff --git a/studio/tests/e2e/workspace.spec.ts b/studio/tests/e2e/workspace.spec.ts new file mode 100644 index 0000000000..cdc8037304 --- /dev/null +++ b/studio/tests/e2e/workspace.spec.ts @@ -0,0 +1,19 @@ +import { expect, test } from "@playwright/test"; + +/** + * Browser smoke over the real stack: Studio's production build in external + * mode → the /api/mecatl proxy → the fixture daemon (tests/e2e/ + * fixture-daemon.mjs). Assertions are read-only renders of fixture content; + * the streaming/approval mechanics are covered by the protocol unit suite + * and the hermetic server-tier suite. + */ + +test("the chat list and transcript come from the daemon", async ({ page }) => { + await page.goto("/workspace/chat"); + // The sidebar row is the daemon's session inventory. + await page.getByText("Fix the flaky scheduler test").first().click(); + // Opening the chat rehydrates the authoritative transcript. + await expect( + page.getByText("the test races the claim sentinel", { exact: false }), + ).toBeVisible(); +}); diff --git a/studio/tests/rendered-html.test.mjs b/studio/tests/rendered-html.test.mjs index 293184b011..e6f4ceaa04 100644 --- a/studio/tests/rendered-html.test.mjs +++ b/studio/tests/rendered-html.test.mjs @@ -46,10 +46,8 @@ async function waitForServer(url, child) { if (child.exitCode !== null) throw new Error(`Next exited during test startup (${child.exitCode})`); try { - // Any HTTP answer means Next is up (the root renders the branded - // not-found page — a 404 — until the chat route lands in the stack). const response = await fetch(url); - if (response.status) return; + if (response.ok) return; } catch { /* still starting */ } @@ -125,9 +123,8 @@ after(async () => { }); test("server-renders Mecatl Studio", async () => { - // No status assertion: the root redirects into the branded not-found page - // until the chat route lands in the stack — the SSR proof is the body. - const response = await fetch(`${studioBaseURL}/`); + const response = await fetch(`${studioBaseURL}/workspace/chat`); + assert.equal(response.status, 200); assert.match(response.headers.get("content-type") ?? "", /^text\/html\b/i); const html = await response.text(); assert.match(html, /Mecatl Studio/); @@ -214,8 +211,8 @@ test("an unreachable daemon is a friendly 503, never demo content", async () => // The page itself still serves — the offline state is the UI's to render — // and carries no fabricated inventory. - const page = await fetch(`${offlineBaseURL}/`); - assert.match(await page.text(), /Mecatl Studio/); + const page = await fetch(`${offlineBaseURL}/workspace/chat`); + assert.equal(page.status, 200); }); test("controller policy rejects CSRF and DNS-rebinding requests", () => { diff --git a/user-docs/building/what-you-get/studio.md b/user-docs/building/what-you-get/studio.md index abc5d1438f..6319d1eb13 100644 --- a/user-docs/building/what-you-get/studio.md +++ b/user-docs/building/what-you-get/studio.md @@ -12,8 +12,9 @@ Studio reads and writes the daemon's state rather than keeping its own. :::note Landing in progress Studio is landing as a stacked series of pull requests. This page grows with -each one; right now the module foundation and the server tier (proxy + -managed-mode controller) are in the tree, and the surfaces arrive next. +each one; right now the module foundation, the server tier (proxy + managed-mode +controller), the typed protocol seam, the workspace shell, and the Chats +surface are in the tree; the remaining surfaces arrive next. ::: ## Starting it @@ -47,3 +48,15 @@ never holds a daemon address or credential; Studio's own server tier pins Host/Origin, injects the bearer and the session workspace server-side, and allowlists headers in both directions. The decision record is ADR 0288 (`docs/adr/0288-studio-atrium-module.md` in the repo). + +## Chats + +Chats are the daemon's sessions — there is no separate Studio store. The +sidebar lists the daemon's session inventory bucketed by recency, with +capability-gated rename and delete. A new chat is a draft until you send: +the session is minted on first send, so abandoned drafts never litter the +daemon. The transcript streams live (text, reasoning, tool activity, and +delegation badges), failed turns surface as alerts instead of vanishing, and +permission asks render as three-way approvals (allow once / always / deny) +that withdraw if the daemon retracts them. @-mentions offer the daemon's +agent roster and slash commands its command list.