diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md
new file mode 100644
index 0000000..9f6f463
--- /dev/null
+++ b/.claude/CLAUDE.md
@@ -0,0 +1,3 @@
+
+@../AGENTS.md
+
diff --git a/.claude/settings.json b/.claude/settings.json
new file mode 100644
index 0000000..7d522d5
--- /dev/null
+++ b/.claude/settings.json
@@ -0,0 +1,52 @@
+{
+ "hooks": {
+ "PostToolUse": [
+ {
+ "hooks": [
+ {
+ "command": "C:\\Users\\Patrick\\.cargo\\bin\\sinapsi.exe hook post-tool-use",
+ "type": "command"
+ }
+ ],
+ "matcher": "Edit|Write|MultiEdit"
+ }
+ ],
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "command": "C:\\Users\\Patrick\\.cargo\\bin\\sinapsi.exe hook session-start",
+ "type": "command"
+ }
+ ]
+ }
+ ],
+ "Stop": [
+ {
+ "hooks": [
+ {
+ "command": "C:\\Users\\Patrick\\.cargo\\bin\\sinapsi.exe hook stop",
+ "type": "command"
+ }
+ ]
+ }
+ ],
+ "UserPromptSubmit": [
+ {
+ "hooks": [
+ {
+ "command": "C:\\Users\\Patrick\\.cargo\\bin\\sinapsi.exe hook user-prompt-submit",
+ "type": "command"
+ }
+ ]
+ }
+ ]
+ },
+ "permissions": {
+ "allow": [
+ "Bash(sinapsi:*)",
+ "mcp__sinapsi__*",
+ "Read(.sinapsi/**)"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..52bd55d
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,19 @@
+# Application
+NEXT_PUBLIC_APP_URL=http://localhost:3000
+DATABASE_URL=file:./data/autoblog.db
+DATABASE_AUTH_TOKEN=
+BETTER_AUTH_SECRET=replace-with-at-least-32-random-characters
+AUTH_SIGN_IN_RATE_LIMIT=8
+DEMO_ENABLED=true
+
+# AI: mock is deterministic and makes no provider call
+AI_MODE=mock
+GEMINI_API_KEY=
+GEMINI_MODEL=gemini-2.5-flash
+
+# Durable job runner / deployment scheduler
+CRON_SECRET=replace-with-a-distinct-random-secret
+
+# Limits
+AI_MONTHLY_CHARACTER_QUOTA=200000
+MEDIA_MAX_BYTES=5242880
diff --git a/.env.local b/.env.local
deleted file mode 100644
index b402e8a..0000000
--- a/.env.local
+++ /dev/null
@@ -1,22 +0,0 @@
-# Database
-MONGODB_URI=""
-DBNAME=""
-
-# Cloudinary
-NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=""
-CLOUDINARY_CLOUD_NAME=""
-CLOUDINARY_API_KEY=""
-CLOUDINARY_API_SECRET=""
-CLOUDINARY_WEBSITE_FOLDER=""
-
-# Gemini
-GEMINI_API_KEY=""
-
-# Firebase (client-safe public variables)
-NEXT_PUBLIC_FIREBASE_API_KEY=""
-NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=""
-NEXT_PUBLIC_FIREBASE_PROJECT_ID=""
-NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=""
-NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=""
-NEXT_PUBLIC_FIREBASE_APP_ID=""
-NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=""
\ No newline at end of file
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..319b78f
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,44 @@
+
+This project uses Sinapsi for code context engineering.
+
+Read `.sinapsi/AGENT_INSTRUCTIONS.md` first — it defines the workflow for this repository.
+
+Before you think, plan or open any source file, read one file:
+
+- `.sinapsi/summary.md` — directory tree, the last 10 sessions, and a short recap
+
+That is the cardinal rule. `.sinapsi/session.md` (what happened) and
+`.sinapsi/handoff.md` (current working state) are the sources it is built from — open
+them only when the summary leaves your actual question unanswered.
+
+The last action of every patch, in this order: append `session.md`, rewrite `handoff.md`
+(≤ 150 lines), then update `summary.md` from both — leaving its mechanical tree block
+untouched. A patch that leaves any of the three stale is incomplete.
+
+Start code exploration with Sinapsi MCP — it is the cheaper first move, not a rule
+against searching. Pick the tool by what you already know:
+
+- behaviour only → `query_graph`
+- exact symbol → `get_node`
+- exact symbol + impact → `get_neighbors`
+- project shape → `graph_stats` (once)
+
+The graph is a derived index built from the source: it lags edits and drops what its
+extractors cannot see. The filesystem is the authority — confirm with `grep`/`rg`
+whenever you want to.
+
+Every pack carries four lines. Read them independently; none implies another:
+
+- `RELEVANCE` → how much of your question the top result explains
+- `COVERAGE` → the token budget only, never what was not nominated
+- `STRUCTURE` → compiler-resolved relationships
+- `FRESHNESS` → index staleness
+
+`RELEVANCE: weak` means the answering symbol may be absent: retry with project
+vocabulary, then search the filesystem.
+
+`COVERAGE: partial` means the budget cut something: raise it, narrow the scope, or
+search — `complete` is not a promise that the answer is present.
+
+`STRUCTURE: absent` means relationships are unknown, not absent.
+
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..55dfdbc
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: npm
+ directory: /
+ schedule: { interval: weekly }
+ open-pull-requests-limit: 5
+ - package-ecosystem: github-actions
+ directory: /
+ schedule: { interval: monthly }
+ open-pull-requests-limit: 3
diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
new file mode 100644
index 0000000..04811d8
--- /dev/null
+++ b/.github/workflows/quality.yml
@@ -0,0 +1,143 @@
+name: Quality gates
+
+on:
+ pull_request:
+ push:
+ branches: [main]
+
+permissions:
+ contents: read
+
+env:
+ BUN_VERSION: 1.3.12
+ DATABASE_URL: file:./data/ci.db
+ BETTER_AUTH_SECRET: ci-only-secret-with-at-least-thirty-two-characters
+ NEXT_PUBLIC_APP_URL: http://127.0.0.1:3000
+ DEMO_ENABLED: "true"
+ AI_MODE: mock
+ CRON_SECRET: ci-only-distinct-cron-secret-000000000000
+
+jobs:
+ dependency-integrity:
+ name: Frozen install
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+
+ typecheck:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run typecheck
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run lint
+
+ unit-tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run test
+
+ integration-tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run test:integration
+
+ e2e:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bunx playwright install --with-deps chromium
+ - run: bun run test:e2e
+
+ accessibility:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bunx playwright install --with-deps chromium
+ - run: bun run test:a11y
+
+ visual-regression:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bunx playwright install chromium
+ - run: bun run test:visual
+
+ performance-budgets:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
+ with: { node-version: "22.19.0" }
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bunx playwright install --with-deps chromium
+ - run: bun run build
+ - run: bun run test:performance
+ - run: bun run test:lighthouse
+ - name: Preserve Lighthouse and bundle reports
+ if: always()
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
+ with:
+ name: performance-reports
+ path: |
+ .lighthouse/
+ .performance/
+ include-hidden-files: true
+ if-no-files-found: error
+ retention-days: 7
+
+ production-build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run build
+
+ security-audit:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
+ with: { fetch-depth: 0 }
+ - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6
+ with: { bun-version: "1.3.12" }
+ - run: bun install --frozen-lockfile
+ - run: bun run audit
+ - run: bun run security:secrets
+ - name: Scan Git history without exposing values
+ uses: trufflesecurity/trufflehog@27b0417c16317ca9a472a9a8092acce143b49c55 # v3.95.9
+ with:
+ extra_args: --results=verified,unknown
diff --git a/.gitignore b/.gitignore
index b7bab6e..2cb8780 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,4 +38,21 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
-CMS blog.code-workspace
\ No newline at end of file
+CMS blog.code-workspace
+.sinapsi/graph.json
+.sinapsi/.watcher
+.sinapsi/journal
+
+# environment and durable local state
+.env
+.env.*
+!.env.example
+data/
+
+# test and quality artifacts
+coverage/
+playwright-report/
+test-results/
+tests/visual/surfaces.spec.ts-snapshots/*-chromium-win32.png
+.lighthouse/
+.performance/
diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 0000000..a16727e
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,11 @@
+{
+ "mcpServers": {
+ "sinapsi": {
+ "args": [
+ "serve",
+ ".sinapsi/graph.json"
+ ],
+ "command": "C:\\Users\\Patrick\\.cargo\\bin\\sinapsi.exe"
+ }
+ }
+}
\ No newline at end of file
diff --git a/.sinapsi/AGENT_INSTRUCTIONS.md b/.sinapsi/AGENT_INSTRUCTIONS.md
new file mode 100644
index 0000000..2115540
--- /dev/null
+++ b/.sinapsi/AGENT_INSTRUCTIONS.md
@@ -0,0 +1,67 @@
+# Sinapsi — operating instructions
+
+Short on purpose. This file says how to work *here*; it does not repeat your project's
+stack, style or test conventions — those live in your own AGENTS.md, untouched.
+
+## Memory: read one file first, write three last
+
+Before you think, plan or open a source file, read **one** file:
+
+- `.sinapsi/summary.md` — the directory tree, the last 10 sessions, and a short recap
+
+That is the cardinal rule. `session.md` and `handoff.md` are the sources `summary.md` is
+built from; open them only when the summary leaves your actual question unanswered.
+
+At the end of every patch, in this order:
+
+1. **append** `.sinapsi/session.md` — the full entry for this patch
+2. **rewrite** `.sinapsi/handoff.md` — current working state, **≤ 150 lines**
+3. **update** `.sinapsi/summary.md` from both — add one dated line under *Recent
+ sessions* (keep the last 10), rewrite *Where things stand* in 5–10 lines, and leave
+ the tree block at the top alone: Sinapsi maintains it itself, live, and any edit you
+ make inside its markers is thrown away on the next change
+
+A patch that leaves any of the three stale is not finished.
+
+`session.md` archives itself: past 150 lines (or its token budget) Sinapsi moves it to
+`.sinapsi/archive/` and starts a fresh one, whose header names the latest archive. Read
+that one if you must — never the whole folder. `sinapsi compact` forces the same check.
+
+## Exploring
+
+Start with Sinapsi MCP rather than a filesystem sweep — it is the cheaper first move,
+not a prohibition. Pick the tool by what you already know:
+
+- behaviour only → `query_graph`
+- exact symbol, need its location → `get_node`
+- exact symbol, need callers/impact → `get_neighbors`
+- no idea of the shape → `graph_stats` (once)
+
+The graph (`.sinapsi/graph.json`) is a derived index, not the truth: it is built from the
+source, it lags edits, and it drops what its extractors cannot see. The filesystem is
+the authority. Reach for `grep`/`rg` whenever you want to confirm what the graph told
+you — verifying a result is never a failure, and each header below says what it does
+*not* cover.
+
+## Reading the headers
+
+Four independent claims. None implies another.
+
+- `RELEVANCE` — how much of your question the top result explains. `RELEVANCE: weak`
+ means the answering symbol may be absent entirely: re-query in the project's
+ vocabulary, then search the filesystem.
+- `COVERAGE` — the token budget only. It says nothing about symbols the ranker never
+ nominated, so `complete` is not a claim that the answer is present.
+- `STRUCTURE` — `absent` means relationships are unknown, never "no callers".
+- `FRESHNESS` — how stale the index may be.
+
+## Decisions
+
+`.sinapsi/rfc/` before a boundary moves: public API, schema, storage format, an
+invariant, a dependency. An RFC states the problem, the measured evidence, the
+alternatives rejected, and the test that would prove it wrong. Propose one when the
+boundary moves — do not wait to be asked.
+
+`.sinapsi/adr/` after it is decided. An ADR is immutable: supersede it, never edit it.
+
+Small choices get no RFC — one line in `decisions.md` is the whole ceremony.
diff --git a/.sinapsi/adr/000-template.md b/.sinapsi/adr/000-template.md
new file mode 100644
index 0000000..d2f0e7e
--- /dev/null
+++ b/.sinapsi/adr/000-template.md
@@ -0,0 +1,15 @@
+# ADR 000 —
+
+> Copy to `NNN-slug.md`. **Immutable once accepted** — to change a decision, write a new ADR that supersedes this one.
+
+## Status
+
+
+## Context
+
+
+## Decision
+
+
+## Consequences
+
diff --git a/.sinapsi/adr/001-modular-libsql-better-auth.md b/.sinapsi/adr/001-modular-libsql-better-auth.md
new file mode 100644
index 0000000..3582239
--- /dev/null
+++ b/.sinapsi/adr/001-modular-libsql-better-auth.md
@@ -0,0 +1,30 @@
+# ADR 001 — Modular monolith on libSQL with Better Auth
+
+## Status
+
+Accepted on 2026-07-22. Immutable; supersede with a new ADR.
+
+## Context
+
+AutoBlog needs one durable, workspace-isolated behavior in local development, CI and a serverless
+public demo. It also requires revocable server-side sessions, relational revision history,
+optimistic writes and idempotent scheduled jobs. RFC 001 contains baseline measurements,
+alternatives and falsification criteria.
+
+## Decision
+
+Use a Next.js 16 modular monolith. Use Drizzle ORM over `@libsql/client` for both local `file:` and
+remote libSQL databases. Use Better Auth database sessions and secure HTTP-only cookies. Keep
+authorization, workflow and concurrency rules in application modules; route handlers and React
+components are adapters only. Use the same relational database for durable jobs and outbox-style
+compensation.
+
+## Consequences
+
+- Schema and migrations become the persistence authority; Mongoose and the in-memory simulator
+ are removed.
+- Local and CI execution remain free and deterministic; deployment needs externally owned libSQL
+ and auth configuration.
+- The bounded write rate is compatible with SQLite/libSQL. Higher sustained write concurrency
+ would trigger a measured PostgreSQL migration RFC.
+- Better Auth owns identity/session tables while workspace roles remain an application concern.
diff --git a/.sinapsi/adr/002-versioned-workflow-durable-jobs.md b/.sinapsi/adr/002-versioned-workflow-durable-jobs.md
new file mode 100644
index 0000000..6adb2e3
--- /dev/null
+++ b/.sinapsi/adr/002-versioned-workflow-durable-jobs.md
@@ -0,0 +1,26 @@
+# ADR 002 — Versioned workflow with database-leased publication jobs
+
+## Status
+
+Accepted on 2026-07-22. Immutable; supersede with a new ADR.
+
+## Context
+
+RFC 002 requires review decisions, immutable public artifacts, stale-command protection and scheduled
+execution that survives restarts without paid queue infrastructure.
+
+## Decision
+
+Keep the state machine in the editorial domain and authorize each action through the central RBAC
+policy. Use the post version as the precondition for state and revision commands. Store the exact
+revision in each publication record. Schedule a unique publish job in the same database transaction,
+claim work through a conditional lease, retry at most three times and make completion idempotent against
+the publication status. Public reads resolve the published revision pointer, never the mutable draft.
+
+## Consequences
+
+- Review and publication invariants are testable without React or provider infrastructure.
+- A scheduler only triggers `jobs:run`; it cannot select workspace, post or revision data.
+- SQLite/libSQL conditional writes serialize claims for the bounded workload. Sustained multi-worker
+ contention or throughput above the measured portfolio load would require a superseding queue ADR.
+- Version gaps document non-content transitions and are intentional.
diff --git a/.sinapsi/adr/003-verified-media-governed-ai.md b/.sinapsi/adr/003-verified-media-governed-ai.md
new file mode 100644
index 0000000..bc9975b
--- /dev/null
+++ b/.sinapsi/adr/003-verified-media-governed-ai.md
@@ -0,0 +1,27 @@
+# ADR 003 — Verified durable media and explicit governed AI suggestions
+
+## Status
+
+Accepted on 2026-07-22. Immutable; supersede with a new ADR.
+
+## Context
+
+RFC 003 requires provider failures to preserve active media and requires AI limits to survive process
+restart. Demo and CI cannot depend on paid/private providers.
+
+## Decision
+
+Use bounded multipart upload and decode every image before provider storage. Implement a media provider
+port with a durable libSQL object adapter; finalize or replace workspace metadata only after provider
+success and clean stale objects through durable jobs. Use a partial unique index for one active asset per
+post. Use one validated AI port, deterministic mock and structured Gemini adapter. Reserve quota and
+consume rate limits in database transactions. Store provider/model/latency/character counts and only
+provider-reported token counts; never infer tokens or retain prompt content.
+
+## Consequences
+
+- Local, CI and configured deployments retain one business/repository path.
+- Media storage is capped per request and suitable for the bounded demo, not a general asset CDN.
+- Gemini is opt-in configuration; mock results are visibly labeled and contain no fabricated usage.
+- Provider operations have application timeouts, but upstream Gemini work may continue or be billed
+ after abort, as documented by the SDK.
diff --git a/.sinapsi/adr/004-bounded-demo-measured-quality.md b/.sinapsi/adr/004-bounded-demo-measured-quality.md
new file mode 100644
index 0000000..f55eb30
--- /dev/null
+++ b/.sinapsi/adr/004-bounded-demo-measured-quality.md
@@ -0,0 +1,24 @@
+# ADR 004 — Bounded reset and measured product quality
+
+## Status
+
+Accepted on 2026-07-22. Immutable; supersede with a new ADR.
+
+## Context
+
+RFC 004 requires a first-time evaluator to repeat the complete demo without weakening tenant safety,
+and requires accessibility/performance claims to be backed by executable production evidence.
+
+## Decision
+
+Persist demo-reset idempotency outside the reset workspace foreign-key cascade while retaining an
+explicit workspace identifier. Enforce Owner policy, demo flag and database rate limit before reset.
+Delete only the demo workspace's object bytes and relational graph, then invoke the canonical seed.
+Measure direct Web Vitals/transfer budgets under `next start`; use axe, keyboard assertions and
+Chromium screenshots as targeted quality gates. Cache by route semantics rather than globally.
+
+## Consequences
+
+- Repeated requests are safe and auditable; unrelated workspaces and user sessions survive.
+- Reset execution remains bounded to one known demo workspace and cannot become a general delete API.
+- Performance and accessibility language must remain “tested targets,” not universal compliance.
diff --git a/.sinapsi/adr/005-lighthouse-release-gate.md b/.sinapsi/adr/005-lighthouse-release-gate.md
new file mode 100644
index 0000000..acaf0de
--- /dev/null
+++ b/.sinapsi/adr/005-lighthouse-release-gate.md
@@ -0,0 +1,30 @@
+# ADR 005 — Supplement direct budgets with local Lighthouse reports
+
+## Status
+
+Accepted on 2026-07-22. Immutable; supersede with a new ADR.
+
+## Context
+
+Direct production measurements pass at LCP 188/300 ms, CLS 0, interaction 16 ms and JavaScript
+141,959/221,420 bytes, but root RFC P-15 separately names Lighthouse reports as acceptance evidence.
+Official `lighthouse` 13.4.1 is the current audited engine; the release job pins its required Node
+22.19 runtime and reuses the already-installed Playwright Chromium.
+
+## Decision
+
+Run three local-only desktop Lighthouse passes for marketing and sign-in from the existing production
+performance job. Enforce category, LCP and CLS thresholds while retaining the direct interaction and
+JavaScript assertions as primary exact budgets. The direct suite writes a public/editor bundle report.
+GitHub Actions retains both report sets for seven days inside the authorized repository; no
+Lighthouse hosting account is used.
+
+## Consequences
+
+- P-15 evidence now includes both explicit metrics and a standard audited report.
+- Lighthouse variability is bounded with three runs and conservative release thresholds.
+- Reports may contain public route output, so artifacts remain ignored locally and limited to a
+ seven-day repository-scoped CI retention.
+- Lighthouse CI was rejected because its current CLI adds audited legacy transitive risk without
+ providing necessary capability for this local gate.
+- Any dependency audit regression falsifies this decision and blocks release.
diff --git a/.sinapsi/decisions.md b/.sinapsi/decisions.md
new file mode 100644
index 0000000..909c688
--- /dev/null
+++ b/.sinapsi/decisions.md
@@ -0,0 +1,7 @@
+# Decisions
+
+Semantic memory: one line per decision — the reasoning that must survive, not the diff.
+Format: `- YYYY-MM-DD: chose X over Y because Z`. Consult before re-opening a settled choice.
+Big, irreversible architectural decisions belong in `adr/` instead.
+
+- 2026-07-22: rely on the pinned TruffleHog action's built-in fail/no-update flags because duplicating them in `extra_args` aborts before scanning.
diff --git a/.sinapsi/handoff.md b/.sinapsi/handoff.md
new file mode 100644
index 0000000..4d43041
--- /dev/null
+++ b/.sinapsi/handoff.md
@@ -0,0 +1,46 @@
+# Handoff
+
+## Terminal state: EXTERNALLY BLOCKED
+
+- Branch `feat/rfc-editorial-cms`; draft PR
+ `https://github.com/PatrickDev-it/AutoBlog-CMS/pull/3` remains intentionally unmerged.
+- P-01 through P-17 are Verified. P-18 repository implementation is complete but release/public
+ acceptance is externally blocked.
+- `main` protection requires all eleven CI jobs, strict freshness and resolved conversations; admins
+ cannot bypass, force-push or delete the branch.
+
+## Verified evidence
+
+- Local: frozen install, migrations `0000`–`0003`/seed from empty, type/lint/build, 19 unit,
+ 25 integration, 10 E2E, 6 a11y, 5 visual, 2 direct performance and 6 Lighthouse runs pass.
+- Direct lab: LCP 188/300 ms, CLS 0, interaction 16 ms, response 146 ms and JavaScript
+ 141,959/221,420 bytes. Bundle JSON is generated for representative public/editor routes.
+- Lighthouse medians: marketing/sign-in performance 0.92/0.91, all other category scores 1.00,
+ LCP 1,718/1,709 ms and CLS 0. CI retains six HTML reports plus bundle evidence for seven days.
+- Runtime dependency audit and current-tree secret scan pass with zero findings.
+- Public GitHub is verified; description/topics and release documentation are current.
+
+## Current remote gate
+
+- Run `29909234038` attempt 2 passes all eleven protected jobs: frozen install, typecheck, lint, unit,
+ integration, E2E, accessibility, visual regression, performance/Lighthouse, production build and
+ security audit/history-range scan.
+- The corrected TruffleHog step executes successfully with its pinned action-owned fail semantics;
+ the visual rerun also confirms the preceding 691-pixel mismatch was transient rather than a code change.
+- PR #3 remains draft because green repository checks do not close the owner-controlled release gates.
+
+## External blockers
+
+- Provider owners must rotate/revoke historical MongoDB, Cloudinary and Gemini values in `8f83cec`.
+ Never print, suppress or rewrite them. After rotation, record owner confirmation.
+- Deployment owner must configure remote libSQL URL/token, unique auth/cron secrets and a one-minute
+ scheduler. No paid service may be provisioned without approval.
+- Vercel built the candidate, but anonymous access returns HTTP 302 to Vercel SSO. Existing production
+ remains old commit `485f037`; homepage correctly remains blank.
+
+## Owner completion sequence
+
+Rotate/revoke credentials; configure durable runtime and scheduler; expose an approved public URL; rerun
+PR #3. After every protected check passes, mark ready, merge normally, deploy merged SHA, execute the
+clean-browser workflow, set homepage and create/verify `v2.0.0`. Until then do not merge, tag, release,
+pin or advertise the old/protected deployment as v2.
diff --git a/.sinapsi/known-issues.md b/.sinapsi/known-issues.md
new file mode 100644
index 0000000..73fbc46
--- /dev/null
+++ b/.sinapsi/known-issues.md
@@ -0,0 +1,5 @@
+# Known issues & pitfalls
+
+Operational memory: what bit us, so it never bites twice.
+Format per entry: `symptom → root cause → fix / how to avoid`. Consult before debugging.
+
diff --git a/.sinapsi/project-map.md b/.sinapsi/project-map.md
new file mode 100644
index 0000000..463a70d
--- /dev/null
+++ b/.sinapsi/project-map.md
@@ -0,0 +1,34 @@
+# Project map
+
+
+
+
+**552** nodes · **797** edges · JavaScript 1, TypeScript 95
+
+> **Structure: absent.** No compiler-resolved index covers this repo, so the edges below are `defines` only — who contains whom, not who calls whom. Run `sinapsi index`. The hub list is a containment ranking until you do.
+
+## Levels
+root (419 code, 133 docs)
+
+## Node kinds
+- class: 15
+- constant: 115
+- dependency: 20
+- document: 23
+- function: 73
+- heading: 110
+- interface: 6
+- method: 94
+- module: 96
+
+## Most connected (hubs / entry points)
+- schema (37) — src/platform/db/schema.ts
+- client (35) — src/platform/db/client.ts
+- errors (26) — src/platform/observability/errors.ts
+- domain (21) — src/modules/identity/domain.ts
+- service (20) — src/modules/ai/service.ts
+- domain (20) — src/modules/editorial/domain.ts
+- drizzle-repository (20) — src/modules/editorial/drizzle-repository.ts
+- service (20) — src/modules/editorial/service.ts
+- env (20) — src/platform/config/env.ts
+- 4. Problem register and decisions (19) — RFC.md
diff --git a/.sinapsi/rfc/000-template.md b/.sinapsi/rfc/000-template.md
new file mode 100644
index 0000000..705ebf3
--- /dev/null
+++ b/.sinapsi/rfc/000-template.md
@@ -0,0 +1,18 @@
+# RFC 000 —
+
+> Copy this file to `NNN-slug.md` and fill it in **before** implementing an architectural change.
+
+## Context
+
+
+## Proposal
+
+
+## Alternatives
+
+
+## Decision
+
+
+## Consequences
+
diff --git a/.sinapsi/rfc/001-application-foundation.md b/.sinapsi/rfc/001-application-foundation.md
new file mode 100644
index 0000000..4c95944
--- /dev/null
+++ b/.sinapsi/rfc/001-application-foundation.md
@@ -0,0 +1,78 @@
+# RFC 001 — Application foundation
+
+- Status: Accepted
+- Date: 2026-07-22
+- Root RFC coverage: P-01, P-03, P-04, P-06, P-07, P-08, P-09, P-16, P-18
+
+## Context and measured evidence
+
+The baseline has two incompatible data paths: a 500+ line process-local simulator and
+unprotected Mongoose route handlers. `bun install --frozen-lockfile` fails, TypeScript reports
+42 errors, source lint reports 93 errors and 39 warnings with the current toolchain, and the
+runtime audit reports 48 findings (2 critical, 22 high). The production build succeeds only
+while explicitly skipping type and lint validation. No test or CI workflow exists.
+
+The product needs relational workspace boundaries, immutable revisions, conditional writes,
+durable idempotency records and database-backed sessions. Local development and CI must not
+require a paid service, while the public serverless deployment must use the same repository
+implementation.
+
+## Proposal
+
+1. Upgrade to the current supported Next.js 16 and React 19 line and use strict TypeScript.
+2. Use Drizzle ORM with the libSQL driver. Local/CI use a durable `file:` database; deployment
+ uses the same driver and schema with a remote libSQL URL.
+3. Use Better Auth with database-backed, revocable email/password sessions. Demo role entry
+ authenticates bounded seeded accounts; it does not bypass the session or policy layer.
+4. Organize one modular monolith into identity, editorial, media and AI modules behind server
+ adapters. React code never imports the database or provider clients.
+5. Represent scheduled work and compensation in the relational `jobs` table with unique
+ idempotency keys, leases and bounded retries.
+6. Use stable application errors with correlation IDs at every public adapter.
+
+## Alternatives considered
+
+- Keep MongoDB: rejected because the existing models do not enforce tenant joins, revision
+ relations or transactional job invariants, and would preserve the contradictory data path.
+- Local-only SQLite: rejected because a serverless deployment cannot durably write its local
+ filesystem.
+- PostgreSQL plus a test fake: rejected because it introduces a second persistence behavior or
+ requires external infrastructure for deterministic CI.
+- Prisma: rejected because Drizzle/libSQL has a smaller runtime boundary and direct SQL migration
+ artifacts while retaining explicit constraints.
+- Auth.js credentials sessions: rejected because the current credentials-provider path is less
+ direct for database-backed password accounts; Better Auth documents native Next.js 16,
+ database sessions, secure cookies and Drizzle integration.
+- Custom signed cookies: rejected because the RFC requires a maintained authentication solution
+ and revocation semantics.
+- Microservices or an external queue: rejected as operationally disproportionate to this modular
+ monolith and incompatible with the no-billing constraint.
+
+## Boundary and invariants
+
+- All mutable records contain `workspace_id`; membership is reloaded from the database.
+- Route workspace IDs are locators only and must match an authenticated membership.
+- Revision rows are append-only. A published pointer identifies one immutable revision.
+- Post mutations condition on `expectedVersion`; a zero-row update is `VERSION_CONFLICT`.
+- Job idempotency keys are unique and claims use a lease before execution.
+- AI output is persisted as usage metadata and returned as a suggestion only.
+- Media replacement activates the new asset only after verified storage succeeds.
+
+## Falsification tests
+
+The decision is invalid if any of the following remains true:
+
+- local and remote database URLs require different application repositories;
+- a cross-workspace ID can be read or mutated through an authenticated route;
+- a stale writer changes the current revision;
+- a repeated scheduled job publishes or audits twice;
+- session revocation still authorizes a mutation;
+- a failed media replacement removes the active asset;
+- a mock AI response is presented as a configured provider result.
+
+## Consequences
+
+The public deployment requires a remote libSQL URL/token and an authentication secret. Local and
+CI remain provider-free. SQLite serializes writes, which is acceptable for this bounded portfolio
+CMS; optimistic version checks remain mandatory. A future PostgreSQL move would require a new RFC
+and migration rather than an alternate live adapter.
diff --git a/.sinapsi/rfc/002-editorial-reliability.md b/.sinapsi/rfc/002-editorial-reliability.md
new file mode 100644
index 0000000..33a5fd7
--- /dev/null
+++ b/.sinapsi/rfc/002-editorial-reliability.md
@@ -0,0 +1,53 @@
+# RFC 002 — Editorial reliability and durable publication
+
+- Status: Accepted
+- Date: 2026-07-22
+- Root RFC coverage: P-07, P-08, P-09
+
+## Context and measured evidence
+
+Phase 1 persists every content save as an immutable revision and rejects stale versions, but the
+seven declared states have no transition implementation. The database already contains publication
+and job records, while the application exposes zero workflow, history, restore, scheduling or public
+preview commands. Nine unit, five integration and five browser tests pass before this change; none
+can prove the author-reviewer-publication path.
+
+## Proposal
+
+1. Implement one pure transition table for Draft, InReview, ChangesRequested, Approved, Scheduled,
+ Published and Archived. Each command declares its permission, source states and destination.
+2. Condition every transition and restore on `expectedVersion`; transition versions may create gaps
+ in revision numbering because only content mutations append revisions.
+3. Lock content during review, approval and scheduling. Editing a published post creates a new Draft
+ revision while retaining the immutable published pointer.
+4. Pin scheduled and direct publications to the selected immutable draft revision. Scheduled work is
+ recorded with a unique idempotency key and executed by a database-leased, three-attempt worker.
+5. Expose revision history, restore-as-new, transition commands and a public preview read model through
+ server adapters. All private commands retain session, membership, origin and policy enforcement.
+
+## Alternatives considered
+
+- Encode transitions in React: rejected because server authorization and non-UI clients could bypass it.
+- Mutable article snapshots: rejected because they cannot prove what was reviewed or published.
+- Platform-specific cron plus in-memory queue: rejected because retries and duplicate executions would
+ disappear on restart and diverge between local, CI and deployment.
+- External queue: rejected because the bounded portfolio workload does not justify billing or another
+ operational system.
+- Publish the current draft when a delayed job runs: rejected because edits after scheduling would
+ silently change the approved artifact.
+
+## Falsification tests
+
+- An Author or Reviewer can directly publish or schedule.
+- A transition from an undeclared source state succeeds.
+- A stale transition, save or restore changes the post.
+- Restore mutates an old revision instead of appending a new one.
+- A scheduled job publishes a revision other than the one pinned at scheduling time.
+- Reclaiming or rerunning a job creates a second publication or audit event.
+- Editing after publication changes the public response before another explicit publication.
+
+## Consequences
+
+The post version is a concurrency token, not a count of revision rows. Scheduled execution requires a
+recurring invocation of the repository-provided worker command; local and CI use the same command as a
+deployment scheduler. Provider-specific scheduling remains deployment configuration, not business logic.
diff --git a/.sinapsi/rfc/003-media-ai-boundaries.md b/.sinapsi/rfc/003-media-ai-boundaries.md
new file mode 100644
index 0000000..ff799e3
--- /dev/null
+++ b/.sinapsi/rfc/003-media-ai-boundaries.md
@@ -0,0 +1,61 @@
+# RFC 003 — Secure media and governed AI boundaries
+
+- Status: Accepted
+- Date: 2026-07-22
+- Root RFC coverage: P-10, P-11, P-12
+
+## Context and measured evidence
+
+The accepted foundation contains media/AI metadata tables but exposes no provider ports or user
+paths. The current tree has no upload endpoint and no AI endpoint; all previous base64, Cloudinary
+and Gemini routes were removed in Phase 0. Runtime audit is zero. The installed `sharp` and
+`@google/genai` packages are unused. Official `@google/genai` documentation confirms structured JSON
+schemas, bounded output tokens, response usage metadata and an abort signal, while warning that
+client cancellation does not necessarily cancel provider billing.
+
+## Proposal
+
+1. Accept only authenticated multipart images up to 5 MiB (configurable downward/up to 10 MiB).
+ Verify decoded PNG/JPEG/WebP format, dimensions up to 4096×4096 and 16 megapixels with `sharp`.
+2. Use a media provider port. The durable default adapter writes opaque objects to libSQL, preserving
+ the same local/CI/deployment repository path. Metadata activates only after object persistence.
+3. Replace in one metadata transaction: mark the prior asset replaced and activate the verified new
+ asset. Durable cleanup jobs remove old/orphan objects with leases and three retries.
+4. Define one AI suggestion contract. The deterministic mock and configured Gemini adapters share
+ validated input/output. Gemini uses structured JSON, a fixed output bound and an eight-second
+ abort signal; public errors never include provider details.
+5. Reserve monthly workspace character quota transactionally before provider invocation and enforce
+ a per-user five-request/minute database rate limit. Release reservations on failure and record only
+ bounded usage metadata on success.
+6. Return suggestions without mutating posts. React provides separate Preview and Apply actions and
+ labels mock/configured mode.
+
+## Alternatives considered
+
+- Base64 JSON uploads: rejected because parser allocation occurs before meaningful limits.
+- Arbitrary remote URLs: rejected because they create SSRF and host-trust problems; the allowlist is
+ intentionally empty.
+- Local filesystem objects: rejected because serverless instances are ephemeral.
+- Direct Cloudinary dependency: rejected because CI would require private credentials and the old path
+ deleted active data before replacement succeeded.
+- Store prompts/responses for analytics: rejected as unnecessary sensitive content retention.
+- Mutate posts directly from AI: rejected because assistance must remain an explicit user decision.
+- In-memory quotas/rate limits: rejected because restart and multi-instance behavior would bypass them.
+
+## Falsification tests
+
+- A forged extension/MIME, oversized image, excessive dimension or non-image reaches storage.
+- A provider/finalization failure changes or deletes the active asset.
+- Another workspace can list, replace, read or delete an asset by ID.
+- Repeated cleanup execution deletes a new active replacement or audits twice.
+- Anonymous/forbidden AI invocation reaches an adapter.
+- Concurrent quota reservations exceed the configured workspace budget.
+- Timeout, malformed provider JSON or excessive output is returned as a suggestion.
+- Any suggestion changes a revision before the user chooses Apply.
+
+## Consequences
+
+Database object storage is intentionally bounded and operationally sufficient for this portfolio
+deployment; high media volume would trigger a new direct-upload object-store RFC. Configured Gemini
+requires an externally owned API key and may incur provider charges, so demo/CI remain deterministic
+mock mode. Abort limits local waiting but cannot guarantee provider-side cancellation or avoid charges.
diff --git a/.sinapsi/rfc/004-product-quality-demo-performance.md b/.sinapsi/rfc/004-product-quality-demo-performance.md
new file mode 100644
index 0000000..54ec96c
--- /dev/null
+++ b/.sinapsi/rfc/004-product-quality-demo-performance.md
@@ -0,0 +1,57 @@
+# RFC 004 — Recruiter demo, accessibility and performance evidence
+
+- Status: Accepted
+- Date: 2026-07-22
+- Root RFC coverage: P-13, P-14, P-15, P-17
+
+## Context and measured evidence
+
+The flagship browser workflow passes, but reset exists only as a local seed option, the checklist is
+static, axe covers only marketing and no visual/performance evidence is versioned. The production
+build currently exposes a static marketing route, dynamic authenticated editor and a 60-second
+revalidated public preview. No global no-cache header exists.
+
+## Proposal
+
+1. Add Owner-only demo reset with a persisted idempotency record, a three-request/hour database rate
+ limit and a hard `is_demo` guard. Reset deletes only bounded demo data and its object bytes, then
+ runs the same validated seed path.
+2. Derive the recruiter checklist from persisted post state and provide concise in-product guidance,
+ mode/role disclosure and reset feedback.
+3. Add axe scans for marketing, sign-in, workspace and public preview; keyboard/focus-order tests for
+ primary navigation and controls; retain explicit form labels and reduced-motion CSS.
+4. Add versioned desktop/mobile visual snapshots for marketing, sign-in, workspace and public preview.
+5. Measure production-mode Web Vitals and transferred initial JavaScript with budgets set before
+ measurement: LCP ≤ 2.5 s, INP/event duration ≤ 200 ms, CLS ≤ 0.10, landing JS ≤ 180 KiB and
+ authenticated workspace JS ≤ 320 KiB. Fail the test when a budget regresses.
+6. Keep authenticated routes dynamic/no-store by server semantics; retain 60-second public preview
+ revalidation and static marketing generation.
+
+## Alternatives considered
+
+- Reset through a public seed endpoint: rejected because it weakens authorization and tenant scope.
+- Delete/recreate the entire database: rejected because configured workspaces and sessions would be
+ destroyed.
+- Claim broad WCAG compliance from axe: rejected because automated checks are targeted evidence, not
+ a complete audit.
+- Measure development server performance: rejected because HMR and unoptimized bundles invalidate
+ release budgets.
+- Lighthouse-only scores: rejected as hardware-sensitive aggregates; direct Web Vital/bundle budgets
+ are retained as primary evidence. Lighthouse can supplement them in release documentation.
+- Global no-cache: rejected because marketing and published preview have different data semantics.
+
+## Falsification tests
+
+- A non-Owner, non-demo workspace or anonymous caller can reset data.
+- Repeating an idempotency key performs another reset or audit.
+- Reset deletes a configured workspace, identity/session or leaves demo media objects behind.
+- Flagship actions cannot be completed with a keyboard or lose visible focus.
+- Axe reports serious/critical violations on a primary surface.
+- A versioned major surface changes without an reviewed snapshot update.
+- A production route exceeds any declared LCP/INP/CLS/JavaScript budget.
+
+## Consequences
+
+Performance measurements run against `next start` and therefore require a completed production
+build. Results are local/CI targets rather than universal field guarantees. Visual baselines are
+Chromium-specific review artifacts. Idempotency records intentionally survive demo workspace deletion.
diff --git a/.sinapsi/rfc/005-lighthouse-release-evidence.md b/.sinapsi/rfc/005-lighthouse-release-evidence.md
new file mode 100644
index 0000000..da99f96
--- /dev/null
+++ b/.sinapsi/rfc/005-lighthouse-release-evidence.md
@@ -0,0 +1,50 @@
+# RFC 005 — Reproducible Lighthouse release evidence
+
+- Status: Accepted
+- Date: 2026-07-22
+- Root RFC coverage: P-15, P-18
+
+## Context and measured evidence
+
+The production Playwright gate measures LCP 188/300 ms, CLS 0, a 16 ms interaction proxy and exact
+initial JavaScript transfer 141,959/221,420 bytes. These direct assertions are stronger than an
+aggregate score, but root RFC P-15 also explicitly requires a Lighthouse report. No reproducible
+Lighthouse artifact or CI assertion currently exists. Official npm metadata reports `lighthouse`
+13.4.1 as current on 2026-07-22; it requires Node 22.19 or newer.
+
+## Proposal
+
+1. Add exact dev dependency `lighthouse@13.4.1` and retain it in the frozen Bun lockfile.
+2. Run three desktop Lighthouse passes against marketing and sign-in under `next start`.
+3. Use the installed Playwright Chromium and pin Node 22.19 in the performance job.
+4. Persist reports as ignored local output and a seven-day GitHub CI artifact; do not upload route
+ output to a Lighthouse SaaS or any provider outside the authorized repository.
+5. Require performance ≥0.80, accessibility ≥0.95, best-practices ≥0.90, SEO ≥0.90, LCP ≤2.5 s
+ and CLS ≤0.10. Direct interaction and JavaScript budgets remain separately mandatory.
+6. Execute Lighthouse inside the existing independent performance CI job after the direct budgets.
+7. Emit representative marketing/workspace JavaScript measurements as a separate bundle report.
+
+## Alternatives considered
+
+- Keep only the custom PerformanceObserver gate: rejected because it leaves the exact P-15
+ Lighthouse acceptance criterion unmet.
+- Manual DevTools export: rejected because it is not reproducible or mandatory in CI.
+- `@lhci/cli` 0.15.1: rejected after `bun audit` exposed high-severity legacy transitive findings
+ absent from current Lighthouse; the hosted/collection features are unnecessary for this gate.
+- Replace direct metrics with Lighthouse scores: rejected because aggregate scores are noisier and do
+ not enforce the explicit transferred-JavaScript or interaction budgets.
+- Upload to LHCI server or paid monitoring: rejected because no approved external service exists.
+
+## Falsification tests
+
+- Frozen install changes the manifest or lockfile.
+- Lighthouse cannot audit the production server or produces fewer than six local reports.
+- The public/editor bundle report or seven-day CI evidence artifact is absent.
+- Either tested URL falls below a category threshold or exceeds LCP/CLS budgets.
+- The performance CI job omits either direct budgets or Lighthouse assertions.
+- Runtime audit gains an unreviewed high/critical vulnerability.
+
+## Consequences
+
+CI time increases by three Lighthouse runs per route. Scores remain lab evidence and do not become
+field-performance or WCAG claims. P-15 requires both the direct budget suite and Lighthouse to pass.
diff --git a/.sinapsi/session.md b/.sinapsi/session.md
new file mode 100644
index 0000000..0de7a94
--- /dev/null
+++ b/.sinapsi/session.md
@@ -0,0 +1,302 @@
+# Session log
+
+Operational changelog, append-only, chronological order — never delete previous entries.
+
+Every entry must include: timestamp, patch goal, changes made + files touched, breaking changes, regressions introduced/removed, validation performed, final status.
+
+Append this first — before rewriting `handoff.md`, and before updating `summary.md` from both.
+
+Sinapsi archives this file on its own once it passes 150 lines (or its token budget): it moves to `archive/`, a fresh log starts, and the fresh log's header names the latest archive. Read that one if you need history — not the whole folder.
+
+
+
+## 2026-07-22T01:34:41+02:00 — Establish measured RFC foundation
+
+- Goal: reproduce containment evidence and accept the architecture before moving public,
+ persistence, authentication or dependency boundaries (P-01–P-09, P-16, P-18).
+- Changed: added RFC 001, immutable ADR 001, the measured engineering baseline and the P-01–P-18
+ closure ledger under `.sinapsi/{rfc,adr}` and `docs/engineering`.
+- Decisions: Next.js 16 modular monolith; Drizzle over libSQL for local/remote parity; Better Auth
+ database sessions; relational jobs and stable application errors.
+- Security: identified non-empty historical MongoDB, Cloudinary and Gemini environment values in
+ commit `8f83cec` without printing them. Rotation is required before release; no current source
+ secret was added.
+- Validation: fetched `origin`; branched from `origin/main`; frozen install failure, 42 type errors,
+ 93 lint errors/39 warnings, bypassed build and 48 audit findings were reproduced.
+- Breaking changes: none; documentation-only decision patch.
+- Regressions: none introduced. Final status: complete.
+
+## 2026-07-22T01:47:42+02:00 — Close Phase 0 containment
+
+- Goal: close P-02, P-03 and current-tree secret containment for P-18.
+- Changed: removed tracked `.env.local`; added `.env.example`, SECURITY and MIT license; upgraded
+ to Next 16.2.11/React 19.2.8; replaced vulnerable dependencies; rebuilt `bun.lock`; enabled
+ strict TypeScript and zero-warning ESLint; removed all build validation bypasses and permissive
+ image/cache/body configuration.
+- Changed: deleted 141 unreachable simulator, MongoDB, Cloudinary, Gemini, Firebase, duplicate UI
+ and parallel-route files (12,201 deleted lines). Added the recruiter marketing entry, bounded
+ constants, unit/E2E/axe smoke tests, current-tree secret scanner, Dependabot and eight independent
+ pinned-action CI gates.
+- API/schema: all unsafe legacy APIs were removed; the durable authenticated API arrives in the
+ accepted Phase 1 schema patch. `/` and `/sign-in` are the only current product routes.
+- Security: runtime audit moved from 48 findings (2 critical/22 high) to zero; current-tree secret
+ scan passes. Historical provider rotation remains required before release.
+- Validation: frozen install, strict typecheck, lint with zero warnings, unit test, integration
+ harness, production build, runtime audit, recruiter E2E and marketing axe test all pass locally.
+- Visual/accessibility: responsive dark editorial landing page retains the established identity;
+ skip link, visible focus, semantic headings and reduced-motion behavior were added.
+- Breaking changes: legacy routes and dependency APIs are intentionally deleted. Rollback is the
+ preceding `83c6689` commit; no data migration exists yet.
+- Regressions: sign-in is intentionally a Phase 0 placeholder until the Phase 1 session slice.
+ Final status: complete.
+
+## 2026-07-22T02:13:25+02:00 — Deliver authenticated durable application core
+
+- Goal: close the Phase 1 exit gate for P-01, P-04, P-05, P-06 and P-16.
+- Changed: added two checksum-verified libSQL migrations and Drizzle schemas for Better Auth,
+ workspaces, memberships, posts, immutable revisions, publications, media, audit events, AI usage,
+ durable jobs and database-backed rate limits. Added validated idempotent five-role seed/setup.
+- Changed: connected Better Auth database sessions, 12-hour HTTP-only cookies, disabled sign-up,
+ persistent login throttling, origin checks, audited login/logout and membership-derived workspace
+ context. Added a complete policy matrix including Author ownership constraints.
+- Changed: implemented one editorial repository/service for list, get, create and conditional save;
+ every save appends a revision and a stale version maps to HTTP 409. Added stable public errors,
+ correlation IDs, structured logs and readiness.
+- Product: replaced the sign-in placeholder with five bounded demo identities and added the
+ responsive workspace/editor, active role/AI mode, seeded selection, create flow, debounced
+ autosave states, conflict controls and recruiter checklist.
+- API/schema: added protected workspace post GET/POST/PATCH routes and Better Auth adapter. JSON
+ payloads never carry trusted workspace/user identity. API, architecture, schema and RBAC docs
+ were added with executable contract references.
+- Security: arbitrary credentials, revoked sessions, cross-workspace reads, Reviewer creation,
+ Author cross-owner edits and anonymous mutations are negatively tested. No provider value is
+ stored or returned.
+- Validation: frozen install, strict typecheck, zero-warning lint, 9 unit tests, 5 integration
+ tests, production build, zero-vulnerability audit and 5 Chromium E2E/axe tests pass.
+- Breaking changes: schema `0000`/`0001` are the new persistence boundary. Rollback requires
+ restoring the prior application commit and removing a new local/remote database; migrations are
+ forward-only and checksum protected.
+- Remaining risk: workflow transitions, history UI, jobs, media and AI adapters are Phase 2/3.
+ Final status: complete.
+
+## 2026-07-22T02:39:02+02:00 — Deliver reliable editorial workflow and publication
+
+- Goal: close P-07, P-08 and P-09 with an executable author-reviewer-publication path.
+- Decision: accepted RFC 002/ADR 002 for a versioned domain state machine, revision-pinned
+ publications and database-leased jobs with three bounded attempts.
+- Changed: implemented six state commands across seven lifecycle states, action-specific RBAC,
+ version preconditions, timestamps, append-only audits and locked review/approval content.
+- Changed: added workspace-scoped revision history, comparison and restore-as-new. Editing or
+ restoring a Published post opens a new Draft while retaining its immutable published pointer.
+- Reliability: scheduling writes a pinned publication and unique job atomically. Claims use a
+ conditional 30-second lease; retry, recovery and repeated execution cannot duplicate the public
+ mutation or audit. Archival cancels only its matching scheduled work.
+- Product/API: added workflow controls, scheduling input, history/compare/restore panels, resilient
+ serialized autosave, public preview and protected transition/revision/job adapters.
+- Testing: added pure transition falsification, restore immutability, stale transition/restore,
+ permission denial, pinned scheduling, malformed-job retry and duplicate-execution coverage.
+- Validation: frozen install, strict typecheck, zero-warning lint, 11 unit tests, 10 integration
+ tests, production build, zero-vulnerability audit and 7 deterministic Chromium E2E/axe tests pass.
+- E2E evidence: Author creates/saves/restores/submits; Reviewer approves; Editor publishes; public
+ preview renders the pinned revision. A second browser writer receives and compares HTTP 409.
+- Breaking changes: post version is explicitly a concurrency token and may have revision gaps after
+ transitions. Deployment scheduling must invoke `jobs:run`; no new migration is required.
+- Remaining risk: media and AI provider boundaries are Phase 3. Final status: complete.
+
+## 2026-07-22T03:07:40+02:00 — Harden media and AI provider boundaries
+
+- Goal: close P-10, P-11 and P-12 without provider credentials or a parallel demo path.
+- Decision: accepted RFC 003/ADR 003 for decoded bounded multipart media, durable libSQL object
+ storage, transactional replacement/compensation and explicit governed AI suggestions.
+- Schema: migration `0002_media_ai_governance.sql` replaces the coupled blob table, constrains one
+ active asset per post and adds durable monthly AI quota reservations.
+- Media: PNG/JPEG/WebP are stream-capped, decoded with pixel/dimension limits, MIME-matched,
+ checksummed and filename-sanitized before storage. Workspace ownership is policy-enforced.
+- Reliability: verified new media is stored before metadata activation. Replacement is atomic;
+ provider/finalization failure preserves the active asset, while leased cleanup jobs retry three
+ times and never delete an active replacement.
+- AI: added one validated adapter contract, deterministic mock and structured Gemini adapter with
+ bounded prompt/output, eight-second abort, persisted rate/quota controls and real usage metadata.
+- Privacy/product: prompts and suggestions are not retained in usage/audit rows. Mock/live mode is
+ labeled; preview is non-mutating and Apply creates a user-authored autosave revision.
+- Scheduler security: a distinct secret with constant-time comparison authorizes machine execution;
+ session-based Owner/Admin operation retains origin and RBAC checks.
+- Tests: forged MIME, size/dimension abuse, cross-workspace access, provider failure, metadata
+ compensation, cleanup recovery, malformed AI output, timeout, quota, rate and anonymous access pass.
+- Validation: frozen install, strict typecheck, zero-warning lint, 17 unit tests, 21 integration
+ tests, production build, current-tree secret scan, zero-vulnerability audit and 10 E2E/axe tests.
+- Breaking changes: forward-only migration `0002` drops unused `media_blobs`; rollback requires
+ restoring the prior commit/database snapshot. Configured Gemini remains opt-in and billable.
+- Remaining risk: demo reset, accessibility depth, performance/visual evidence and release delivery
+ are Phase 4/5. Final status: complete.
+
+## 2026-07-22T03:29:49+02:00 — Deliver bounded demo and measured product quality
+
+- Goal: close P-13, P-14, P-15 and P-17 with repeatable recruiter and quality evidence.
+- Decision: accepted RFC 004/ADR 004 for an idempotent bounded reset, route-semantic caching,
+ targeted accessibility claims, versioned visuals and production-mode performance budgets.
+- Demo: added Owner-only origin-protected reset with durable idempotency and a three/hour database
+ limit. It verifies `is_demo`, deletes only demo objects/relations and preserves identities, sessions
+ and configured workspaces before running the canonical validated seed.
+- Product: checklist progress now derives from persisted version/workflow/publication state; creation
+ focuses the title, role/mode/data-path disclosure is visible and reset requires explicit confirmation.
+- Accessibility: added serious/critical axe scans on marketing, sign-in, workspace and preview;
+ keyboard draft/review flow, focus management and reduced-motion assertions pass.
+- Visual: added six reviewed cross-CI-stable Chromium baselines for desktop/mobile major surfaces and
+ removed 17 unreachable legacy/canned public assets.
+- Performance: accepted LCP ≤2.5 s, CLS ≤0.10, INP/event ≤200 ms and JS 180/320 KiB budgets.
+ Production measurements pass at 180/256 ms LCP, 0 CLS, <16 ms event upper bound, 143 ms workflow
+ response and 141,959/221,420 transferred JavaScript bytes.
+- Caching: marketing remains static; protected JSON is `private, no-store`; authenticated media is
+ private five-minute; public preview retains 60-second revalidation; health remains no-store.
+- CI: separated E2E, accessibility, Windows visual regression and Linux production performance jobs.
+- Validation: frozen install, strict typecheck/lint, 17 unit, 25 integration, 10 E2E, 6 accessibility,
+ 5 visual and 2 performance tests, build, secrets and zero-vulnerability audit pass.
+- Breaking changes: migration `0003` adds immutable reset idempotency. Visual baselines are reviewed
+ on Windows Chromium to control font rasterization. Final status: complete.
+
+## 2026-07-22T03:37:58+02:00 — Replace legacy claims with executable release evidence
+
+- Goal: advance P-18 by making every public product claim map to a reachable path and named proof.
+- Documentation: replaced the legacy simulator README/FEATURES with a recruiter brief, explicit
+ Implemented/Tested/Demo-only/Planned matrix, actual application visuals and guided role workflow.
+- Operations: added deployment, migration, rollback/recovery, known-limitations and 2.0 candidate
+ release notes with the exact external configuration required for durable public operation.
+- Security: added a trust-boundary threat model, control/evidence/residual-risk table and an explicit
+ non-suppressible historical credential rotation gate; expanded the operator security policy.
+- Integrity: added a documentation contract test for local evidence links, all P-01–P-18 ledger rows
+ and superseded claim exclusion.
+- GitHub discovery: authenticated owner is `PatrickDev-it`; repository is public. The current public
+ production deployment is still commit `485f037` and is not presented as AutoBlog 2.0 evidence.
+- Validation: documentation contract 2/2 and zero-warning lint pass; local evidence links resolve.
+- Remaining gate: P-18 stays in progress pending full clean gates, remote PR/CI, owner credential
+ rotation, durable deployment configuration, merge/tag/release and clean-session public verification.
+
+## 2026-07-22T03:48:11+02:00 — Stabilize and execute the complete local release matrix
+
+- Goal: validate the AutoBlog 2.0 candidate from frozen install through production performance and
+ surface any nondeterministic release defect before remote CI.
+- Finding: the first E2E run correctly failed because the fixed `data/e2e.db` retained the intentional
+ hourly demo-reset window across runs. This made repeat execution dependent on prior local state.
+- Fix: Playwright now gives every process an isolated durable libSQL file, optionally overridden by
+ `PLAYWRIGHT_DATABASE_URL`; production rate-limit and idempotency behavior are unchanged.
+- Strictness: removed the only strict TypeScript defect in the new Markdown-link parser without an
+ assertion/cast. The documentation contract remains 2/2 and full unit count is 19.
+- Empty database: migrations `0000`–`0003` and validated demo seed pass from a new file.
+- Browser evidence: 10 E2E, 6 accessibility and 5 visual tests pass on pinned Chromium.
+- Production evidence: build passes; LCP is 156/168 ms, CLS 0, interaction 16 ms, history response
+ 106 ms and transferred JavaScript 141,959/221,420 bytes, within accepted budgets.
+- Security/reproducibility: frozen install, strict typecheck, zero-warning lint, 19 unit, 25 integration,
+ zero-vulnerability runtime audit and current-tree secret scan pass.
+- Remaining release risk is external: historical credential rotation, remote durable configuration,
+ full-history CI, merge/tag/release and clean-session public deployment verification.
+
+## 2026-07-22T03:55:11+02:00 — Publish the candidate and prove the external release boundary
+
+- Goal: complete all authorized GitHub-side release work and distinguish repository failures from
+ actions that require provider/account ownership.
+- Delivery: pushed `feat/rfc-editorial-cms` and opened draft PR
+ `https://github.com/PatrickDev-it/AutoBlog-CMS/pull/3` at candidate commit `3e9bc9a`.
+- CI: frozen install, typecheck, lint, unit, integration, E2E, accessibility, visual regression,
+ performance and production build all pass remotely. Security audit/current-tree scan pass; the
+ required TruffleHog history step fails on the known historical provider finding.
+- Governance: updated the official pinned `actions/checkout` dependency from v4.2.2 to v7.0.1
+ (`3d3c42e5aac5ba805825da76410c181273ba90b1`) to remove the Node 20 deprecation warning. The
+ existing `setup-bun` pin is already the official v2.2.0 release.
+- GitHub metadata: verified `PatrickDev-it`, updated the public repository description and focused
+ topics, removed obsolete `docker`; homepage remains blank because no public v2 URL is verified.
+- Deployment: Vercel built the correct candidate, but anonymous requests redirect to Vercel login.
+ Existing production still points to old commit `485f037`; neither is advertised as public v2.
+- Ledger: P-18 is `Externally blocked`; P-01 through P-17 remain Verified.
+- External actions: account owners must rotate/revoke the named historical credentials, configure
+ remote libSQL/auth/cron and one-minute scheduling, and expose an approved public URL.
+- Validation: documentation contract 2/2 and diff integrity pass. No merge, tag or release created.
+
+## 2026-07-22T04:00:33+02:00 — Enforce the terminal release block
+
+- Goal: make the RFC release gates technically enforceable and leave a stable terminal handoff.
+- Final head `78af05b`: the official `actions/checkout@v7.0.1` pin executes successfully; remote run
+ `29884450920` passes frozen install, typecheck, lint, unit, integration, E2E, accessibility, visual,
+ performance and production build. Only the known full-history finding fails.
+- Protection: `main` now requires all eleven RFC jobs with strict branch freshness, admin enforcement,
+ resolved conversations, no force pushes and no branch deletion.
+- Public repository: an unauthenticated request returns HTTP 200. Description and focused topics are
+ current; homepage remains unset because an accessible v2 deployment does not exist.
+- Deployment: Vercel built `78af05b`, but the anonymous candidate request returns HTTP 302 to Vercel
+ SSO. The clean-session recruiter and runtime persistence gates therefore remain unverified.
+- PR: posted the final evidence and minimum owner actions on draft PR #3. Merge/tag/release are blocked
+ both procedurally and by branch protection; no bypass was used.
+- Terminal status: `EXTERNALLY BLOCKED`. Repository work is complete; credential rotation, remote
+ persistence/scheduler configuration and deployment access require external account ownership.
+
+## 2026-07-22T04:21:56+02:00 — Add reproducible Lighthouse and bundle release evidence
+
+- Goal: close the literal P-15 acceptance gap requiring Lighthouse and bundle reports without
+ weakening the existing direct production budgets.
+- Decision: accepted RFC 005/ADR 005 for six local Lighthouse 13.4.1 reports, a representative
+ public/editor bundle report and seven-day repository-scoped CI artifacts.
+- Dependency review: rejected `@lhci/cli` 0.15.1 after its legacy transitive dependencies introduced
+ additional high-severity development findings. Current Lighthouse adds no runtime finding; the
+ performance job pins its required Node 22.19 and reuses Playwright Chromium.
+- Direct evidence: marketing/workspace LCP 188/300 ms, CLS 0, interaction upper bound 16 ms,
+ workflow response 146 ms and JavaScript 141,959/221,420 bytes all pass accepted budgets.
+- Lighthouse evidence: marketing/sign-in median performance 0.92/0.91, accessibility 1.00,
+ best-practices 1.00, SEO 1.00, LCP 1,718/1,709 ms and CLS 0 across six reports.
+- CI: the independent performance job now requires build, direct measurements, Lighthouse budgets
+ and pinned seven-day report upload. No Lighthouse SaaS, paid service or product data is used.
+- Validation: frozen install, strict typecheck, zero-warning lint, 19 unit, production build, two
+ direct performance tests, six Lighthouse reports, runtime audit and current-tree scan pass.
+- Terminal status remains `EXTERNALLY BLOCKED`: P-01–P-17 are Verified; P-18 still requires provider
+ credential rotation, durable public runtime/scheduler configuration and anonymous deployment.
+
+## 2026-07-22T04:27:00+02:00 — Preserve dot-prefixed performance artifacts in CI
+
+- Goal: make the already-green Lighthouse and bundle outputs downloadable from the protected job.
+- Evidence: remote run `29885689908` passed build, both direct performance tests and all six
+ Lighthouse runs; only artifact upload failed because the action excludes hidden paths by default.
+- Fix: enabled the pinned upload action's `include-hidden-files` option for `.lighthouse/` and
+ `.performance/`; paths, seven-day retention, missing-file failure and all budgets are unchanged.
+- Scope: workflow-only correction. No application, test assertion, dependency or report content
+ changed. Terminal status remains `EXTERNALLY BLOCKED` for the documented external release gates.
+
+## 2026-07-22T11:39:38+02:00 — Restore the executable history-scan gate
+
+- Goal: remove a false-red security result before CTO-facing portfolio review without weakening the
+ required Git-history scan.
+- Evidence: run `29885890517` passed the runtime dependency audit and current-tree secret scan, then
+ TruffleHog exited before scanning because `--fail` was supplied by both the pinned action and
+ `extra_args`.
+- Fix: retained the pinned TruffleHog action and `verified,unknown` result policy while removing only
+ duplicate action-owned `--fail` and `--no-update` arguments.
+- Governance: marked the implemented root RFC Accepted; no application boundary, dependency, test,
+ credential, protection rule or external release gate changed.
+- Local verification: runtime audit and current-tree secret scan pass; remote history-scan rerun is
+ pending on draft PR #3.
+- Terminal status remains `EXTERNALLY BLOCKED`: historical provider rotation, durable public runtime,
+ scheduler, anonymous demo, merge and release remain owner-controlled gates.
+
+## 2026-07-22T11:44:31+02:00 — Verify the protected candidate after history-scan repair
+
+- Remote evidence: PR run `29908929694` passes all eleven required jobs, including the TruffleHog
+ PR-range history scan; the security job completes in 22 seconds instead of aborting on CLI parsing.
+- Security posture: runtime audit and current-tree scanning remain zero-finding. The green range scan
+ does not revoke provider values already known in shared historical commit `8f83cec`; rotation remains
+ a hard external release condition.
+- Presentation: repository description and topics now identify the candidate's actual architectural
+ evidence—database sessions, workspace RBAC, immutable revisions, conflict-safe autosave, libSQL and
+ tested media/AI boundaries.
+- Scope: no application behavior, assertion, dependency, branch protection, credential, release or
+ deployment setting changed.
+- Terminal status remains `EXTERNALLY BLOCKED`; PR #3 stays draft and AutoBlog remains intentionally
+ absent from the recommended profile pins until the public release gates close.
+
+## 2026-07-22T11:53:23+02:00 — Align final protected evidence and release narrative
+
+- Remote evidence: run `29909234038` attempt 2 passes all eleven required jobs; the isolated visual
+ rerun passed all five baselines after the preceding 691-pixel mismatch on a documentation-only SHA.
+- Documentation: replaced the obsolete claim that history scanning must remain red with the actual
+ distinction between an automated zero-finding range scan and owner-confirmed credential revocation.
+- Scope: release evidence and Sinapsi state only; no application behavior, assertion, dependency,
+ credential, protection rule, deployment or release state changed.
+- Terminal status remains `EXTERNALLY BLOCKED`: provider rotation, durable remote libSQL/scheduler
+ configuration, anonymous public verification, merge, deployment and `v2.0.0` remain outstanding.
diff --git a/.sinapsi/summary.md b/.sinapsi/summary.md
new file mode 100644
index 0000000..15fd1f1
--- /dev/null
+++ b/.sinapsi/summary.md
@@ -0,0 +1,134 @@
+# Summary
+
+
+
+
+```
+_client/
+ @inset/
+ @sidebar/
+actions/
+app/
+ (workspace)/
+ @inset/
+ @sidebar/
+ api/
+ preview/
+ sign-in/
+ favicon.ico
+ globals.css
+ icon.png
+ layout.tsx
+ page.tsx
+components/
+ chat/
+ groups/
+ webapp/
+config/
+ css/
+constants/
+data/
+ tests/
+ autoblog.db
+ e2e-2956.db
+ e2e-4868.db
+ e2e-8696.db
+ e2e.db
+ lighthouse.db
+ performance.db
+ release-empty-20260722.db
+docs/
+ engineering/
+ releases/
+ accessibility.md
+ ai-data-handling.md
+ api.md
+ architecture.md
+ authentication-rbac.md
+ data-model.md
+ demo-guide.md
+ deployment.md
+ editorial-workflow.md
+ known-limitations.md
+ media-security.md
+ migrations.md
+ performance.md
+ rollback-recovery.md
+ security-threat-model.md
+drizzle/
+ 0000_editorial_core.sql
+ 0001_auth_rate_limit.sql
+ 0002_media_ai_governance.sql
+ 0003_demo_reset_idempotency.sql
+hooks/
+lib/
+plugins/
+scripts/
+ db/
+ jobs/
+ security/
+src/
+ modules/
+ platform/
+ ui/
+test-results/
+ .last-run.json
+tests/
+ accessibility/
+ e2e/
+ integration/
+ lighthouse/
+ performance/
+ unit/
+ visual/
+types/
+ui/
+utils/
+.env.example
+.gitignore
+.mcp.json
+AGENTS.md
+CMS blog.code-workspace
+… 19 more
+```
+
+
+**Read this first, and usually only this.** It is the cardinal read at the start of every
+patch: the project's shape (above), the last sessions at a glance, and a short recap. Open
+`session.md` or `handoff.md` only when this file leaves your actual question unanswered.
+
+## Recent sessions
+
+
+
+- 2026-07-22T11:53:23+02:00 — Aligned release evidence with the final 11/11 green protected rerun.
+- 2026-07-22T11:44:31+02:00 — Verified all eleven protected PR jobs after the history-scan repair.
+- 2026-07-22T11:39:38+02:00 — Removed duplicate action-owned flags that aborted TruffleHog before scanning.
+- 2026-07-22T04:27:00+02:00 — Preserved dot-prefixed performance artifacts in CI.
+- 2026-07-22T04:21:56+02:00 — Added reproducible Lighthouse and bundle release evidence.
+- 2026-07-22T04:00:33+02:00 — Enforced all release checks and finalized the external-blocker handoff.
+- 2026-07-22T03:55:11+02:00 — Published PR #3, proved CI, metadata and external release blockers.
+- 2026-07-22T03:48:11+02:00 — Stabilized isolated Playwright runs and passed the complete local release matrix.
+- 2026-07-22T03:37:58+02:00 — Replaced legacy product claims with tested release, security and operations evidence.
+- 2026-07-22T03:29:49+02:00 — Delivered isolated demo reset, accessibility, visuals and production performance budgets.
+
+## Where things stand
+
+
+
+- Terminal status is EXTERNALLY BLOCKED; P-01–P-17 Verified, P-18 repository work complete.
+- Draft PR #3; run `29909234038` attempt 2 passes all eleven protected jobs including the history-range scan.
+- Local P-15 now includes two direct budgets, six Lighthouse reports and bundle JSON evidence.
+- Hidden report artifacts are explicitly included with seven-day CI retention.
+- Runtime audit, current-tree scan and corrected TruffleHog PR-range scan pass.
+- `main` requires all eleven checks with admin enforcement and no force-push/deletion.
+- Public GitHub is verified; homepage remains blank because no public v2 is verified.
+- Vercel candidate returns HTTP 302 to SSO; production remains old commit `485f037`.
+- Owner action: rotate credentials, configure durable runtime/scheduler and expose public URL.
diff --git a/.sinapsi/verification.md b/.sinapsi/verification.md
new file mode 100644
index 0000000..6a8876b
--- /dev/null
+++ b/.sinapsi/verification.md
@@ -0,0 +1,21 @@
+# Verification checklist
+
+No patch is complete until every box is ticked:
+
+- [ ] builds / compiles
+- [ ] lint & typecheck clean
+- [ ] existing tests pass
+- [ ] no obvious regressions
+- [ ] architectural consistency & naming
+- [ ] session.md + handoff.md updated
+
+
+
+## Detected stack — run these before ticking the boxes
+
+
+```sh
+npm run build # if defined
+npm test
+npm run lint # if defined
+```
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..319b78f
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,44 @@
+
+This project uses Sinapsi for code context engineering.
+
+Read `.sinapsi/AGENT_INSTRUCTIONS.md` first — it defines the workflow for this repository.
+
+Before you think, plan or open any source file, read one file:
+
+- `.sinapsi/summary.md` — directory tree, the last 10 sessions, and a short recap
+
+That is the cardinal rule. `.sinapsi/session.md` (what happened) and
+`.sinapsi/handoff.md` (current working state) are the sources it is built from — open
+them only when the summary leaves your actual question unanswered.
+
+The last action of every patch, in this order: append `session.md`, rewrite `handoff.md`
+(≤ 150 lines), then update `summary.md` from both — leaving its mechanical tree block
+untouched. A patch that leaves any of the three stale is incomplete.
+
+Start code exploration with Sinapsi MCP — it is the cheaper first move, not a rule
+against searching. Pick the tool by what you already know:
+
+- behaviour only → `query_graph`
+- exact symbol → `get_node`
+- exact symbol + impact → `get_neighbors`
+- project shape → `graph_stats` (once)
+
+The graph is a derived index built from the source: it lags edits and drops what its
+extractors cannot see. The filesystem is the authority — confirm with `grep`/`rg`
+whenever you want to.
+
+Every pack carries four lines. Read them independently; none implies another:
+
+- `RELEVANCE` → how much of your question the top result explains
+- `COVERAGE` → the token budget only, never what was not nominated
+- `STRUCTURE` → compiler-resolved relationships
+- `FRESHNESS` → index staleness
+
+`RELEVANCE: weak` means the answering symbol may be absent: retry with project
+vocabulary, then search the filesystem.
+
+`COVERAGE: partial` means the budget cut something: raise it, narrow the scope, or
+search — `complete` is not a promise that the answer is present.
+
+`STRUCTURE: absent` means relationships are unknown, not absent.
+
diff --git a/FEATURES.md b/FEATURES.md
index 134b783..31a4de0 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -1,449 +1,44 @@
-# Autoblog CMS - Feature Highlights
-
-A modern, AI-powered blog content management system built with Next.js, featuring intelligent content guidance, intuitive editing, and
-professional-grade organization tools.
-
----
-
-## 🤖 AI Assistant Integration
-
-**Gemini-Powered Content Guidance**
-
-The integrated AI assistant provides real-time, intelligent support for every aspect of blogging:
-
-- **Multi-Model Intelligence**: Choose between Gemini 1.5 Flash (fast), Flash 8B (lightweight), Pro (advanced), and 2.0 Flash (latest)
-- **Typewriter Effect Display**: Beautiful character-by-character animation (~15ms per character) makes complex responses easier to digest
-- **Smart Context Awareness**:
- - Content creation strategies and best practices
- - SEO optimization recommendations
- - Editorial quality guidelines
- - Analytics interpretation and insights
- - Publishing workflow guidance
- - Design and branding advice
-- **Session Persistence**: Chat history stored in browser sessionStorage
-- **Conversational**: Full conversation history maintained for context-aware responses
-- **Markdown Formatting**: AI responses render with proper formatting, lists, and emphasis
-- **Responsive Guidance**: Suggestions adapt based on your blog section and content type
-
-**Example Uses:**
-
-- "How do I create compelling blog content?" → Detailed 3-phase writing guide
-- "What are SEO best practices?" → Technical optimization checklist
-- "How to maintain editorial standards?" → Brand voice and quality guidelines
-- Ask anything about blog strategy, CMS features, or content optimization
-
----
-
-## ✏️ Effortless Content Organization
-
-**Intuitive Hierarchical Structure**
-
-Organize content with a simple, three-level hierarchy:
-
-```
-Section (Featured, Design, Culture, Insights, Resources)
-└── Group (e.g., "2025 Highlights", "Digital Design")
- └── Subgroup (e.g., "January", "UI/UX Trends")
- └── Post (individual articles)
-```
-
-### One-Click Renaming
-
-- **Right-click context menu** on any item (group, subgroup, post)
-- **Instant inline editing** with real-time validation
-- **Keyboard shortcuts** for efficiency (Enter to save, Escape to cancel)
-- **Undo capability** - Simply rename again if needed
-- Renames instantly reflected across the entire dashboard
-
-### Bulk Operations
-
-- **Create groups** with a single click
-- **Nested subgroups** for fine-grained organization
-- **Delete hierarchies** with confirmation dialogs
-- **Drag-and-drop** for reordering (when implemented)
-
----
-
-## 📝 Smart Post Creation & Editing
-
-**Rapid Content Creation**
-
-- **Right-click to create**: New group, subgroup, or post from context menu
-- **Modal dialogs** for quick naming without page refresh
-- **Auto-population**: Posts auto-load default template fields:
- - Title, description, publication state (Draft/Published)
- - Date fields with inline date picker
- - Content type classification (Featured, Tutorial, Analysis, etc.)
-- **Rich editor support**: Markdown formatting for complex content
-- **Image management**:
- - Click-to-upload featured images
- - Image library browser
- - Alt text and display name fields
- - Optimized next/image integration for fast loading
-
-### Dual-View Editing
-
-- **Preview mode**: See how content renders to readers
-- **Edit mode**: Full editor with all metadata fields
-- **Tab switching**: Seamlessly toggle between views without losing changes
-
----
-
-## 💾 Autosave & Real-Time Persistence
-
-**Never Lose Your Work**
-
-- **Instant save**: Changes persist immediately to your content store
-- **Auto-recovery**: Session data saved to browser storage
-- **Visual feedback**: Subtle save indicators show state changes
-- **No manual save button required**: Write and the system handles persistence
-- **Undo/Redo support**: Full history of recent changes
-- **Conflict resolution**: Safe handling of simultaneous edits
-
----
-
-## 📱 Mobile-Responsive Design
-
-**Professional Experience Across All Devices**
-
-### Desktop
-
-- Full sidebar navigation with collapsible groups
-- Two-column post editor (metadata + rich editor)
-- 2x2 grid for image management
-- Optimized spacing and typography
-
-### Tablet
-
-- Touch-friendly buttons (48x48px minimum)
-- Optimized grid layouts (1-2 columns)
-- Accessible navigation with tap targets
-- Responsive typography scaling
-
-### Mobile
-
-- Full-screen modal navigation
-- Single-column layouts
-- Thumb-friendly navigation buttons
-- Swipe gestures for sheet navigation
-- Touch-optimized form controls
-- Proper viewport configuration
-
-**Technical Excellence:**
-
-- Next.js Image optimization (lazy loading, WebP support)
-- CSS Grid and Flexbox for responsive layouts
-- Mobile-first CSS approach
-- Tailwind CSS for rapid responsive design
-- Tested across Safari, Chrome, Firefox, Edge
-
----
-
-## 🖼️ Professional Image Management
-
-**Seamless Media Integration**
-
-- **Image Upload**: Click-upload interface with preview
-- **Multiple Image Formats**: Support for JPG, PNG, WebP
-- **Automatic Optimization**:
- - Lazy loading with placeholder backgrounds
- - WebP format support for modern browsers
- - Responsive image sizing based on container
- - SVG fallback for missing images
-- **Image Organization**:
- - Separate images for each content section
- - Hero images for homepage
- - Featured images for blog posts
- - Icon and logo management
-- **Alt Text Support**: Accessibility-first image metadata
-- **Batch Operations**: Manage multiple images simultaneously
-
-### Example Image Locations:
-
-- Hero section: `digital-innovation-01.webp`
-- Featured content: `architecture-design-01.jpg`
-- Culture/Arts: `fashion-exhibition-01.jpg`, `installation-art-01.jpg`
-- Design: `digital-interface-01.png`, `digital-interface-02.png`
-- Community: `community-engagement-01.jpg`
-- Advisory: `board-culture.jpg`
-
----
-
-## 📊 Data Organization & Management
-
-**5 Professional Content Sections**
-
-Each section comes pre-configured with realistic dummy data and professional templates:
-
-### 1. **Featured Stories**
-
-- Monthly highlights and trending content
-- Curated showcase pieces
-- Latest industry updates
-- _Example posts: Digital Revolution, Sustainable Design, Community Impact_
-
-### 2. **Design & Creative**
-
-- Design trends and visual systems
-- UI/UX expertise and tutorials
-- Typography and color theory
-- Brand strategy and identity
-- _Example posts: UX Trends 2025, Accessibility Design, Brand Identity_
-
-### 3. **Culture & Arts**
-
-- Cultural commentary and exhibitions
-- Interview series with creators
-- Event coverage and retrospectives
-- Artistic discourse and analysis
-- _Example posts: Installation Art, Fashion Week, Artist Interviews_
-
-### 4. **Insights & Analysis**
-
-- Industry reports and market analysis
-- Technology forecasts
-- Thought leadership pieces
-- Research and data synthesis
-- _Example posts: Q1 Market Analysis, Technology Forecast, Future of Work_
-
-### 5. **Resources & Guides**
-
-- Technical tutorials and guides
-- Tools and software recommendations
-- Best practices documentation
-- Professional development content
-- _Example posts: Web Performance, Security Best Practices, Designer's Toolkit_
-
-**Automatic Demo Data:**
-
-- Each section pre-loads realistic, multi-level content structure
-- 20+ example posts across all sections
-- Professional titles and descriptions
-- Properly assigned dates and publication states
-- Linked to professional demo images
-
----
-
-## 🎨 Professional UI/UX
-
-**Beautiful, Intuitive Interface**
-
-### Visual Design
-
-- **Dark & Light Themes**: Full theme switcher with system preference detection
-- **Tailwind CSS**: Modern utility-first design system
-- **Shadcn/UI Components**: Production-grade accessible components
-- **Custom Layout System**: Flexible CSS variable system for spacing (--p, --sidebar-p, etc.)
-- **Smooth Animations**: Transitions and micro-interactions for feedback
-
-### Navigation
-
-- **Sidebar with Groups**: Collapse and expand content hierarchies
-- **Breadcrumb Navigation**: Always know where you are in the structure
-- **Quick Search**: Find posts and sections instantly
-- **Smart Links**: Contextual navigation to related content
-
-### Accessibility
-
-- **WCAG AA Compliant**: Color contrast ratios, keyboard navigation
-- **Screen Reader Support**: Semantic HTML and ARIA labels
-- **Keyboard Navigation**: Full keyboard support for all features
-- **Focus Management**: Clear focus indicators for keyboard users
-- **Alt Text**: All images have descriptive alt text
-
----
-
-## 🔄 Advanced Workflow Features
-
-### Draft & Publishing
-
-- **Publication States**: Draft, Scheduled, Published, Archived
-- **Date Scheduling**: Set future publish dates
-- **Status Indicators**: Visual icons show current state
-- **Bulk Operations**: Change multiple posts' states at once
-
-### Editorial Standards
-
-- **Editorial Advisory Panel**: Built-in advisory system for brand consistency
-- **Content Template System**: Default templates for each content type
-- **Quality Checklist**: Pre-publish checklist prevents common issues
-- **Version Control**: Track and restore previous versions
-
-### Analytics Ready
-
-- **Date Tracking**: All content timestamped and sortable
-- **Category Analytics**: Organize by section for performance tracking
-- **Search Integration**: Posts discoverable by title and content
-
----
-
-## 🚀 Performance & Technical Excellence
-
-**Built on Modern Technology Stack**
-
-- **Next.js 14+**: App Router with server components for speed
-- **React 18+**: Latest hooks and concurrent features
-- **TypeScript**: Full type safety across codebase
-- **Tailwind CSS**: Responsive design with minimal CSS
-- **shadcn/ui**: Accessible component library
-- **Markdown Support**: Rich text rendering with react-markdown
-- **Session Storage**: Browser-based state persistence
-- **API Mocking**: Demo mode with full offline functionality
-
-### Performance Features
-
-- **Lazy Loading**: Images load on-demand
-- **Code Splitting**: Automatic route-based code splitting
-- **Optimized Images**: 40-80% size reduction with Next Image optimization
-- **Minimal JS**: Server-rendered components reduce client-side overhead
-- **Caching**: Smart caching strategies for faster page loads
-
----
-
-## 📚 Comprehensive Admin Dashboard
-
-### Sidebar Management
-
-- **Current Section Navigation**: Quick navigation between Featured, Design, Culture, Insights, Resources
-- **Home & Advisory Links**: Access landing page and editorial content
-- **Style Switching**: Environment switcher for different blog modes
-- **Theme Toggle**: Dark/light mode with system preference sync
-
-### Inset Panel Features
-
-- **Chat Assistant**: Right sidebar AI panel (collapsible)
-- **Model Selection**: Switch between different Gemini models on-the-fly
-- **Session Persistence**: Chat history maintained during session
-- **Typing Indicator**: Shows when AI is composing responses
-
-### Context Menu (Right-Click)
-
-- **Create Group**: New content group
-- **Create Subgroup**: Nested organization
-- **Create Post**: Instant post creation
-- **Rename**: Quick inline editing
-- **Delete**: With confirmation dialogs
-
----
-
-## 🎯 Use Cases & Workflows
-
-### Content Creator Workflow
-
-1. Click AI Assistant → Request content strategy
-2. Create new post with right-click menu
-3. Type content while assistant offers suggestions
-4. Upload featured image via click interface
-5. Autosave handles persistence automatically
-6. Switch to preview mode to verify appearance
-7. Set publication state and date
-8. Content live immediately (or scheduled)
-
-### Editorial Manager Workflow
-
-1. Review dashboard for all upcoming content
-2. Use AI assistant to evaluate editorial quality
-3. Bulk edit publication states
-4. Organize by theme with group management
-5. Rename groups to match campaign names
-6. Track analytics by section
-7. Maintain advisory standards
-
-### Content Strategist Workflow
-
-1. Access AI assistant for market trends
-2. Ask for content calendar recommendations
-3. Create multiple themed content groups
-4. Plan cross-section content strategy
-5. Use date scheduling for coordinated releases
-6. Monitor performance by category
-7. Iterate based on engagement metrics
-
----
-
-## 🔒 Data & Security
-
-**Safe, Private Operation**
-
-- **Demo Mode**: All changes stored locally (no external API calls)
-- **Session-Based Storage**: Data persists in browser, not sent to servers
-- **No Real Credentials**: Environment variables sanitized and safe
-- **Type Safety**: Full TypeScript validation prevents common errors
-- **Error Boundaries**: Graceful error handling with clear messaging
-
----
-
-## 📖 Keyboard Shortcuts & Quick Actions
-
-| Action | Method |
-| ----------------- | ------------------------------------ |
-| Create new item | Right-click menu or + button |
-| Rename item | Double-click or right-click → Rename |
-| Save changes | Automatic (Ctrl+S also works) |
-| Switch theme | Theme button in top navigation |
-| Open AI Chat | AI button in top-right corner |
-| Navigate sections | Sidebar menu or breadcrumbs |
-| Quick search | Search icon in navigation |
-
----
-
-## 🌟 Why Choose Autoblog CMS?
-
-✨ **AI-First Approach**: Integrated assistant that learns your writing style and provides contextual guidance
-
-⚡ **Speed**: Create and publish content in seconds, not minutes
-
-🎯 **Organization**: Hierarchical structure keeps content organized at scale
-
-📱 **Responsive**: Works perfectly on any device—no desktop-only limitations
-
-💡 **Intuitive**: Right-click menus and inline editing reduce menu diving
-
-🔄 **Autosave**: Never worry about losing work—changes persist automatically
-
-🎨 **Beautiful**: Professional design with dark/light theme support
-
-📊 **Scalable**: Organize hundreds of posts across multiple sections
-
-🚀 **Modern Stack**: Built with latest web technologies for performance
-
-🔐 **Safe**: Demo mode for testing without external dependencies
-
----
-
-## 🚀 Getting Started
-
-### Installation
-
-```bash
-# Install dependencies
-bun install
-
-# Start development server
-bun dev
-
-# Build for production
-bun build
-```
-
-### First Steps
-
-1. Navigate to any section (Featured, Design, Culture, Insights, Resources)
-2. Right-click in the sidebar to create a group
-3. Click the AI button to ask for writing suggestions
-4. Create your first post and watch it autosave
-5. Upload an image by clicking the image area
-6. Switch to preview mode to see your content
-7. Publish when ready!
-
----
-
-## 📞 Support & Documentation
-
-- **AI Assistant**: Click the "AI" button to ask for help on any feature
-- **Comments & Feedback**: All changes are automatically saved
-- **Demo Data**: Multiple sample posts in each section to learn from
-
----
-
-**Autoblog CMS v1.0** - Built for modern content creators who value speed, intelligence, and elegance.
+# AutoBlog CMS feature evidence
+
+Status is explicit: **Implemented** means a reachable persisted path exists; **Tested** names its
+automated proof; **Demo-only** is bounded and labeled; **Planned** is not represented as available.
+
+| Product capability | Implementation | Automated evidence | Status |
+| --- | --- | --- | --- |
+| Workspace-isolated persistence | one Drizzle repository over local or remote libSQL | empty migration, restart, cross-tenant integration | Implemented, Tested |
+| Database-backed login/logout | Better Auth HTTP-only SameSite sessions, revocation and rate limit | identity integration and anonymous E2E | Implemented, Tested |
+| Owner/Admin/Editor/Author/Reviewer | server permission matrix plus Author ownership | complete role/command unit matrix and denial E2E | Implemented, Tested |
+| Draft autosave | 850 ms debounce, serialized conditional writes | revision integration and reload E2E | Implemented, Tested |
+| Concurrent editing safety | post version precondition and stable `VERSION_CONFLICT` | concurrent writers and stale browser E2E | Implemented, Tested |
+| Immutable revisions | insert-only rows, compare and restore-as-new | trigger, compare/restore integration and E2E | Implemented, Tested |
+| Editorial workflow | Draft, In Review, Changes Requested, Approved, Scheduled, Published, Archived | transition/permission unit tests and flagship E2E | Implemented, Tested |
+| Scheduled publication | transactional job/publication, lease, retry and idempotency | duplicate execution and lease-recovery integration | Implemented, Tested |
+| Immutable public preview | publication pins a reviewed revision | later-draft and publication E2E | Implemented, Tested |
+| Media upload | bounded multipart, decoded PNG/JPEG/WebP verification | MIME, size, dimension, pixel and isolation tests | Implemented, Tested |
+| Safe media replacement | new object verification before atomic activation and cleanup | injected provider/finalization failure tests | Implemented, Tested |
+| AI suggestion preview | authenticated bounded command with explicit Apply | adapter contracts, abuse tests and E2E | Implemented, Tested |
+| Gemini adapter | structured `@google/genai` response with abort and real metadata | stubbed provider contract/malformed/timeout tests | Implemented, Tested; live provider not asserted |
+| Guided recruiter checklist | progress derived from durable version/workflow/publication state | recruiter and role-flow E2E | Demo-only, Tested |
+| Demo reset | Owner-only, origin-checked, idempotent, three operations/hour | isolation/retry/rate integration and E2E | Demo-only, Tested |
+| Responsive product surfaces | desktop and 390×844 layouts | six Chromium visual baselines | Implemented, Tested target |
+| Accessibility targets | labels, focus, keyboard flow, reduced motion | axe on four surfaces plus keyboard/focus tests | Implemented, Tested target |
+| Performance budgets | route-semantic caching, direct budgets and local Lighthouse reports | executable LCP/CLS/interaction/JS plus six Lighthouse runs | Implemented, Tested target |
+| Analytics | no fake metrics or dashboard | absence is deliberate | Planned |
+| Live co-author merging | conflicts are surfaced; automatic merge is not implemented | no claim | Planned |
+| External object store/CDN | current bounded adapter uses durable libSQL objects | no claim | Planned |
+| Hosted v2 demo | repository side is complete; remote persistence/scheduler are external | release verification pending | Planned external gate |
+
+## Primary paths
+
+- Author: create → autosave → history/restore → submit.
+- Reviewer: request changes or approve; cannot mutate or publish.
+- Editor: schedule/publish a pinned revision and manage media.
+- Public reader: view only the immutable published revision.
+- AI: preview a labeled mock or configured suggestion; Apply follows normal autosave rules.
+- Owner: reset only the seeded `ws-demo` workspace without deleting identities or sessions.
+
+## Deliberate non-claims
+
+AutoBlog does not claim blanket WCAG compliance, universal browser support, field performance,
+real-time co-authoring, analytics, live-provider output quality, automatic image conversion or
+high-volume media delivery. Evidence and residual limits are linked from [README.md](./README.md).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..1a5e126
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024-2026 PatrickDev-it
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index 4126714..3108573 100644
--- a/README.md
+++ b/README.md
@@ -1,366 +1,138 @@
-# Autoblog CMS + Gemini AI Assistant
-
-A modern, intelligent blog content management system built with Next.js and Tailwind CSS. Features AI-assisted content creation, intuitive
-hierarchical organization, professional editorial workflows, and beautiful responsive design.
-
-**[📚 Full Feature Guide →](./FEATURES.md)**
-
----
-
-## ✨ Key Strengths
-
-### 🤖 Integrated AI Assistant
-
-- **Gemini 1.5 Integration**: Select from Flash, Flash 8B, Pro, or 2.0 models
-- **Real-Time Guidance**: Get suggestions on content creation, SEO, editorial standards, analytics
-- **Typewriter Effect**: Beautiful character-by-character animations for engaging presentation
-- **Conversation History**: Full session persistence for context-aware responses
-- **Markdown Rendering**: Professional formatting with lists, emphasis, and embedded content
-
-### ✏️ Effortless Content Management
-
-- **Right-Click Organization**: Context menus for creating groups, subgroups, and posts
-- **One-Click Renaming**: Instant inline editing for any content item
-- **Hierarchical Structure**: Section → Group → Subgroup → Post organization
-- **Professional Templates**: Pre-configured content categories (Featured, Design, Culture, Insights, Resources)
-- **Publication States**: Draft, Scheduled, Published, Archived with date scheduling
-
-### 💾 Smart Persistence
-
-- **Autosave Everything**: Changes persist instantly without manual saving
-- **Session Storage**: Secure browser-based data handling
-- **Real-Time Sync**: All edits reflected immediately across dashboard
-- **No Data Loss**: Automatic recovery from browser cache
-
-### 📱 Truly Responsive Design
-
-- **Desktop**: Full-featured sidebar + dual-column editor
-- **Tablet**: Optimized grid layouts and touch targets
-- **Mobile**: Full-screen modals and finger-friendly controls
-- **Tested Across Devices**: Works seamlessly on all modern browsers
-
-### 🖼️ Professional Image Management
-
-- **Click-to-Upload**: Intuitive image selection and preview
-- **Optimized Loading**: Next.js Image optimization with lazy loading
-- **Multiple Formats**: JPG, PNG, WebP with automatic conversion
-- **Responsive Images**: Proper sizing for all device types
-- **8 Professional Demo Images**: Ready-to-use for all content sections
-
-### 🎨 Enterprise-Grade UI
-
-- **Dark & Light Themes**: Full theme switching with system preference detection
-- **Accessible Components**: WCAG AA compliant with keyboard navigation
-- **Smooth Animations**: Typewriter effects, pulsing indicators, hover states
-- **Breadcrumb Navigation**: Always know where you are in the structure
-
-### 📊 Comprehensive Organization
-
-- **5 Content Sections**: Featured, Design, Culture, Insights, Resources
-- **20+ Demo Posts**: Realistic content examples in every section
-- **Multi-Level Groups**: Organize content by themes, months, topics
-- **Editorial Advisory**: Built-in system for maintaining brand standards
-
-### ⚡ Lightning-Fast Performance
-
-- **Server Components**: React 18 with server-side rendering
-- **Lazy Loading**: Images and components load on-demand
-- **Code Splitting**: Automatic optimization by Next.js
-- **Zero External Calls**: Demo mode with full offline functionality
-
----
-
-## 🚀 Quick Start
-
-### Installation
-
-```bash
-# Install dependencies
-bun install
-
-# Start development
-bun dev
-
-# Production build
-bun build
-bun start
-```
-
-### First Steps
-
-1. Navigate to any content section in the sidebar
-2. Right-click to create a new group or post
-3. Click **AI** button (top-right) to ask for content guidance
-4. Write and watch as your content autosaves
-5. Upload featured images with one click
-6. Switch between preview and edit modes
-7. Publish or schedule for later!
-
----
-
-## 📖 Technology Stack
-
-- **Frontend**: Next.js 14+, React 18+, TypeScript
-- **Styling**: Tailwind CSS, shadcn/ui components
-- **State**: React hooks, Context API, Browser SessionStorage
-- **AI**: Google Generative AI (Gemini)
-- **Images**: Next.js Image optimization
-- **Theme**: Dark/Light mode with system preference detection
-
----
-
-## 🎯 Core Features
-
-| Feature | Benefit |
-| -------------------------- | ------------------------------------------------ |
-| **AI Assistant** | Intelligent guidance on every aspect of blogging |
-| **Right-Click Menu** | Create content 3x faster than traditional CMS |
-| **Autosave** | Never lose work again—saves automatically |
-| **Mobile Responsive** | Works perfectly on phone, tablet, desktop |
-| **Image Management** | Optimized uploads with multiple format support |
-| **Editorial Standards** | Advisory panel maintains brand consistency |
-| **Publication Scheduling** | Schedule posts for future release |
-| **Demo Mode** | Test everything without external dependencies |
-| **Dark Theme** | Eye-friendly interface for extended use |
-| **Loading States** | Beautiful skeleton loaders while fetching data |
-
----
-
-## 📁 Project Structure
-
+# AutoBlog CMS
+
+An AI-assisted editorial CMS built to demonstrate durable workflow engineering: real sessions,
+workspace-scoped RBAC, immutable revisions, optimistic concurrency, scheduled publication, bounded
+media and explicit AI suggestions.
+
+[](https://github.com/PatrickDev-it/AutoBlog-CMS/actions/workflows/quality.yml)
+[](./LICENSE)
+
+> AutoBlog 2.0 is release-ready in the repository, but the hosted v2 demo is not advertised yet.
+> Provider credential rotation and remote libSQL/scheduler configuration remain owner actions.
+
+
+
+## Product outcome
+
+AutoBlog takes a post from Draft through review, approval and immutable publication. Every protected
+request resolves a database session and workspace membership before invoking an application service.
+Stale autosaves return HTTP 409, scheduled jobs are leased and idempotent, and generated text remains
+a suggestion until a user applies it.
+
+```mermaid
+flowchart LR
+ Browser --> Adapter[Next.js server adapter]
+ Adapter --> Session[Better Auth session]
+ Session --> Policy[Workspace membership and RBAC]
+ Policy --> Service[Application command or query]
+ Service --> Domain[Editorial invariants]
+ Service --> DB[(Drizzle and libSQL)]
+ Service --> Jobs[(Durable jobs)]
+ Service --> Media[Verified media port]
+ Service --> AI[Bounded AI port]
```
-├── app/ # Next.js app router
-│ ├── @inset/ # Main content editor layout
-│ │ ├── [section]/ # Dynamic section pages
-│ │ ├── [id]/ # Individual post editor
-│ │ ├── home/ # Homepage image management
-│ │ └── advisory/ # Editorial advisory panel
-│ ├── @sidebar/ # Navigation sidebar
-│ ├── api/ # API routes (demo mode)
-│ └── layout.tsx # Root layout
-├── components/ # React components
-│ ├── chat/ # AI assistant interface
-│ ├── groups/ # Group/subgroup components
-│ ├── webapp/ # Post preview components
-│ └── imageLoader.tsx # Image management UI
-├── utils/ # Utility functions
-│ ├── api-fetch.ts # Mock API with demo data
-│ └── revalidate.ts # Cache invalidation
-├── constants/ # Configuration
-│ ├── sections.ts # Content sections
-│ └── post-init-template.ts # Default post fields
-├── hooks/ # Custom React hooks
-│ └── use-typewriter.ts # Typewriter animation
-├── public/demo/ # Demo images (8 professional images)
-└── types/ # TypeScript type definitions
-```
-
----
-
-## 🔧 Configuration
-
-### Environment Variables
-
-All environment variables are pre-configured with safe demo values. No real credentials are committed.
-
-```env
-# Local demo configuration (no external dependencies needed)
-MONGODB_URI=""
-DBNAME=""
-NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=""
-GEMINI_API_KEY=""
-```
-
-### Customization
-
-- **Sections**: Edit `constants/sections.ts` to add categories
-- **Demo Data**: Modify `utils/api-fetch.ts` to change sample content
-- **Styling**: Tailwind CSS with custom CSS variables in `app/globals.css`
-- **Components**: Use shadcn/ui for building new features
-
----
-
-## 🎨 Content Sections
-
-The CMS comes with 5 professionally-organized sections:
-### Featured Stories
+## Guided evaluation
-Curated highlights and trending content
+1. Run the local setup below and open `http://localhost:3000/sign-in`.
+2. Enter as **Author**, create a draft, edit it, reload, inspect history and submit it.
+3. Sign in as **Reviewer** to request changes or approve.
+4. Sign in as **Editor** to publish or schedule the pinned revision, then open public preview.
+5. Request a visibly labeled mock AI suggestion and verify that content changes only after **Apply**.
+6. Sign in as **Owner** to reset only the bounded demo workspace.
-- Examples: Digital Revolution, Sustainable Design, Community Impact
+The application checklist derives its progress from persisted state. Seeded role buttons create
+ordinary HTTP-only sessions; they are not an authentication bypass.
-### Design & Creative
+
-Design trends, visual systems, UI/UX expertise
+## Evidence-backed feature matrix
-- Examples: UX Trends 2025, Accessibility Design, Typography Mastery
+| Capability | Status | Executable evidence |
+| --- | --- | --- |
+| Durable workspace persistence | Implemented, tested | migration-from-empty, restart and isolation integration tests |
+| Real authentication and five-role RBAC | Implemented, tested | complete policy matrix, route denials and E2E role switching |
+| Immutable history, compare and restore | Implemented, tested | append-only triggers, integration tests and flagship E2E |
+| Conflict-safe debounced autosave | Implemented, tested | concurrent writer integration test and HTTP 409 E2E |
+| Review, scheduling and publication | Implemented, tested | state-machine tests, leased job tests and public-preview E2E |
+| Bounded PNG/JPEG/WebP media | Implemented, tested | MIME/size/dimension/isolation and compensation tests |
+| AI suggestions | Implemented, tested | shared mock/Gemini contract, quota/rate/timeout and explicit-Apply E2E |
+| Guided resettable workspace | Demo-only, tested | Owner-only bounded reset integration and E2E |
+| Analytics dashboard | Planned | no product claim or simulated analytics path |
+| High-volume object storage | Planned | current adapter deliberately stores bounded objects in libSQL |
+| Hosted AutoBlog 2.0 demo | Planned external gate | remote database, scheduler and historical credential rotation required |
-### Culture & Arts
+See [FEATURES.md](./FEATURES.md) for the complete claim-to-test map.
-Cultural commentary, exhibitions, interviews
+## Local setup
-- Examples: Installation Art Showcase, Fashion Week, Artist Interviews
+Requirements: Bun 1.3.12 and Node.js 20.9 or later.
-### Insights & Analysis
-
-Industry trends, research, thought leadership
-
-- Examples: Market Analysis, Technology Forecast, Future of Work
-
-### Resources & Guides
-
-Tutorials, guides, tools, and best practices
-
-- Examples: Web Performance, Security Practices, Designer's Toolkit
-
-Each section includes realistic multi-level organization and professional demo content.
-
----
-
-## 📊 Dashboard Features
-
-### Sidebar Navigation
-
-- Content sections with collapsible groups
-- Quick access to Home and Advisory pages
-- Theme switcher for dark/light mode
-- Environment configuration
-
-### Main Editor
-
-- Dual-view: Preview & Edit modes
-- Rich text editing with Markdown support
-- Image upload and management
-- Publication state and scheduling
-- Auto-save indicator
-
-### AI Assistant
-
-- Pulsing "AI" indicator showing availability
-- Model selection for different use cases
-- Conversation history maintained in session
-- Responsive markdown rendering
-- Smart category-based responses
-
-### Loading States
-
-- Skeleton loaders for all async operations
-- Beautiful transitions while fetching
-- Matches actual layout structure
-- Prevents layout shift
-
----
-
-## 🚀 Performance Optimizations
-
-- **Server Components**: React Server Components for reduced JS
-- **Image Optimization**: Next/Image with lazy loading
-- **Code Splitting**: Automatic route-based splitting
-- **Type Safety**: Full TypeScript coverage prevents errors
-- **Responsive Design**: Mobile-first CSS approach
-- **Caching**: Smart cache strategies
-
----
-
-## 🔐 Security & Best Practices
-
-- **Type-Safe**: Full TypeScript validation
-- **Demo Mode**: No real API calls or credentials in code
-- **WCAG AA**: Accessible design for all users
-- **Semantic HTML**: Proper DOM semantics
-- **Environment Variables**: Secure configuration handling
-
----
-
-## 💡 Example Workflows
-
-### Content Creator
-
-```
-1. Ask AI: "How do I write engaging content?"
-2. Right-click → Create Post
-3. Type while AI offers suggestions
-4. Upload featured image
-5. Autosave handles persistence
-6. Preview to verify layout
-7. Publish or schedule
-```
-
-### Editorial Manager
-
-```
-1. Open Dashboard
-2. Ask AI: "Evaluate editorial quality"
-3. Right-click group → Rename for campaign
-4. Update publication states
-5. Set scheduling dates
-6. Monitor analytics by section
-```
-
-### Content Strategist
-
-```
-1. Chat with AI for trend analysis
-2. Request content calendar ideas
-3. Create thematic content groups
-4. Plan cross-section strategy
-5. Schedule coordinated releases
-6. Track performance metrics
+```bash
+cp .env.example .env.local
+bun install --frozen-lockfile
+bun run db:setup
+bun run dev
```
----
-
-## 📚 Additional Resources
-
-- **Features Guide**: See [FEATURES.md](./FEATURES.md) for comprehensive feature documentation
-- **Keyboard Shortcuts**: Right-click context menus + keyboard support
-- **AI Assistance**: Click AI button to learn about any feature
-
----
-
-## 🎯 Use Cases
-
-✅ **Professional Bloggers**: Multi-category blog organization with editorial workflow ✅ **Content Teams**: Collaborative content creation with
-advisory standards ✅ **Marketing Teams**: Campaign-based content organization with scheduling ✅ **Agencies**: Client portfolio and case study
-management ✅ **Thought Leaders**: Publish insights and establish authority ✅ **Educational Platforms**: Course and learning content organization
-
----
-
-## 📝 License
-
-This project is provided as-is for educational and commercial use.
-
----
-
-**Autoblog CMS v1.0** - Where AI meets intelligent content management.
-
-Built with modern web technologies. Optimized for performance. Designed for scale.
-
-- `CLOUDINARY_API_SECRET`
-- `CLOUDINARY_WEBSITE_FOLDER`
-- `GEMINI_API_KEY`
-- `NEXT_PUBLIC_FIREBASE_API_KEY`
-- `NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN`
-- `NEXT_PUBLIC_FIREBASE_PROJECT_ID`
-- `NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET`
-- `NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID`
-- `NEXT_PUBLIC_FIREBASE_APP_ID`
-- `NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID`
-
-## Security and Compliance Notes
-
-- Do not commit real credentials, API keys, tokens, or passwords.
-- Keep `.env.local` environment-specific and secret-scoped.
-- Rotate any credential that was previously committed.
+The default `.env.example` uses a durable local SQLite/libSQL file and deterministic mock AI. Do not
+use a `file:` database in a serverless public deployment.
-## Deployment
+| Variable | Purpose |
+| --- | --- |
+| `NEXT_PUBLIC_APP_URL` | canonical origin used for cookies and origin validation |
+| `DATABASE_URL` | local `file:` URL or remote `libsql:`/`https:` URL |
+| `DATABASE_AUTH_TOKEN` | remote libSQL credential; omit locally |
+| `BETTER_AUTH_SECRET` | unique 32+ character session secret |
+| `DEMO_ENABLED` | installs and permits reset of the bounded demo workspace |
+| `AI_MODE` | `mock` or `gemini` |
+| `GEMINI_API_KEY`, `GEMINI_MODEL` | optional configured AI provider settings |
+| `CRON_SECRET` | distinct 24+ character scheduler credential |
+| `AI_MONTHLY_CHARACTER_QUOTA` | durable workspace AI character budget |
+| `MEDIA_MAX_BYTES` | upload cap, constrained to at most 10 MiB |
-Build and run in production mode:
+## Quality gates
```bash
-npm run build
-npm run start
+bun run typecheck
+bun run lint
+bun run test
+bun run test:integration
+bun run test:e2e
+bun run test:a11y
+bun run test:visual
+bun run build
+bun run test:performance
+bun run test:lighthouse
+bun run audit
+bun run security:secrets
```
-Use your preferred hosting provider (e.g., Vercel) with environment variables configured in the deployment platform.
+The measured local release evidence is 19 unit, 25 integration, 10 E2E, 6 accessibility, 5 visual,
+2 direct performance tests and 6 Lighthouse runs. Current budgets and measurements are recorded in
+[docs/performance.md](./docs/performance.md); these are lab targets, not universal field claims.
+
+## Engineering documentation
+
+- [Architecture and boundaries](./docs/architecture.md)
+- [Data model and migrations](./docs/data-model.md)
+- [Authentication and RBAC](./docs/authentication-rbac.md)
+- [Editorial workflow](./docs/editorial-workflow.md)
+- [API and stable errors](./docs/api.md)
+- [Security threat model](./docs/security-threat-model.md)
+- [Media security](./docs/media-security.md)
+- [AI data handling](./docs/ai-data-handling.md)
+- [Accessibility evidence](./docs/accessibility.md)
+- [Performance evidence](./docs/performance.md)
+- [Deployment](./docs/deployment.md)
+- [Rollback and recovery](./docs/rollback-recovery.md)
+- [Known limitations](./docs/known-limitations.md)
+- [2.0 release notes](./docs/releases/v2.0.0.md)
+
+## Security and license
+
+Current source and runtime dependencies scan clean. An early shared commit contains historical
+MongoDB, Cloudinary and Gemini values; their account owners must rotate or revoke them before public
+v2 release. Values are never reproduced in documentation or fixtures. See [SECURITY.md](./SECURITY.md).
+
+Licensed under the [MIT License](./LICENSE), copyright 2024–2026 PatrickDev-it.
diff --git a/RFC.md b/RFC.md
new file mode 100644
index 0000000..669b26a
--- /dev/null
+++ b/RFC.md
@@ -0,0 +1,385 @@
+# RFC — AI-Assisted Editorial CMS
+
+- **Status:** Accepted — implementation authorized 2026-07-22
+- **Scope:** conversion of the current AutoBlog dashboard into a verifiable portfolio product
+- **Audience:** implementation agents, frontend/full-stack reviewers, product and security reviewers
+- **Decision horizon:** one stable public demo and one production-shaped application core
+- **Out of scope:** Privacy organization and unrelated repositories
+
+## 1. Executive decision
+
+AutoBlog will become an authenticated editorial CMS with one coherent persistence path, explicit workflow
+states and an intentionally designed recruiter demo. The current split between an in-memory demo adapter
+and dormant MongoDB/Cloudinary routes will be removed. A feature is not considered implemented merely
+because a component or route exists; it must be reachable, persisted, authorized and tested.
+
+The product will retain its strongest asset—the visual editing surface—but reduce duplicated UI machinery,
+remove unverifiable enterprise claims and prioritize a reliable editorial workflow over feature volume.
+
+## 2. Product projection
+
+### 2.1 Intended product
+
+A small editorial team should be able to organize posts, edit content, review revisions, schedule
+publication, manage media and request bounded AI assistance while maintaining role-based control and a
+complete audit trail.
+
+### 2.2 Portfolio role
+
+This is the portfolio's product-engineering and full-stack project. It should demonstrate:
+
+- polished responsive UI and accessible interaction design;
+- modern React/Next.js architecture with correct server/client boundaries;
+- authentication, authorization and multi-user workflows;
+- durable persistence and concurrency handling;
+- secure media and AI-provider integrations;
+- end-to-end testing and web performance discipline;
+- product analytics and evidence-driven UX decisions.
+
+### 2.3 Success statement
+
+A recruiter should open the public demo, enter through a documented demo account, complete a guided
+draft-to-publication workflow and understand the architecture and quality evidence without configuring
+private services.
+
+## 3. Verified baseline
+
+The current public deployment responds and presents a visually consistent dark dashboard, but its first
+screen is largely empty and does not explain how to evaluate the product.
+
+The verified engineering baseline is:
+
+- frozen dependency installation fails because `bun.lock` and `package.json` disagree;
+- typecheck produces 42 errors across 16 files;
+- lint produces 57 errors and 26 warnings;
+- production build succeeds only because type and lint validation are disabled;
+- dependency audit reports 55 vulnerabilities, including critical and high runtime findings;
+- no automated tests or project CI exist;
+- most visible data flows use an in-memory `demoStore` that resets with the process;
+- database, image and AI routes are not protected by real authentication;
+- `.env.local` is versioned with empty values; no live secret was observed, but the convention is unsafe;
+- README claims exceed the behavior demonstrably connected to the UI;
+- the public repository lacks license, topics, description and Actions workflow.
+
+## 4. Problem register and decisions
+
+### P-01 — The application has two contradictory backends
+
+- **Severity:** Critical
+- **Evidence:** server-rendered pages call a large in-memory API simulator while MongoDB route handlers
+ implement a separate persistence path.
+- **Impact:** reviewers cannot determine what is real; mutations may disappear after restart.
+- **Decision:** introduce one application service interface and one selected persistence implementation.
+ A demo environment uses seeded durable data, not a separate behavioral codebase.
+- **Acceptance:** all product flows traverse the same commands, policies and repository contracts.
+
+### P-02 — Build configuration conceals defects
+
+- **Severity:** Critical
+- **Evidence:** `ignoreBuildErrors` and `ignoreDuringBuilds` permit deployment despite type and lint failure.
+- **Impact:** a green deployment is not evidence of source quality.
+- **Decision:** remove both bypasses after an explicit defect burn-down.
+- **Acceptance:** typecheck, lint, tests and build are mandatory independent CI jobs.
+
+### P-03 — Dependency state is not reproducible or secure
+
+- **Severity:** Critical
+- **Evidence:** frozen install fails; audit includes critical Next.js and high Cloudinary/Mongoose findings.
+- **Impact:** clean builds and public exposure are unsafe.
+- **Decision:** reconcile the lockfile, upgrade to supported patched versions and define an audit budget of
+ zero unreviewed critical/high runtime vulnerabilities.
+- **Acceptance:** clean frozen install and security audit pass in CI.
+
+### P-04 — Authentication is simulated
+
+- **Severity:** Critical
+- **Evidence:** user check succeeds for any non-empty username and token; API routes have no session guard.
+- **Impact:** database, media and AI operations are publicly mutable if configured.
+- **Decision:** use a maintained server-side authentication system with secure cookies and explicit session
+ validation on every mutation.
+- **Acceptance:** anonymous and insufficient-role requests fail in route and end-to-end tests.
+
+### P-05 — Authorization and editorial roles are undefined
+
+- **Severity:** Critical
+- **Evidence:** UI mentions members and workflow but no consistent server policy exists.
+- **Impact:** the product cannot support collaborative editorial work safely.
+- **Decision:** define `Owner`, `Admin`, `Editor`, `Author` and `Reviewer` capabilities. Authorization lives
+ in application policies, not hidden component conditions.
+- **Acceptance:** a permission matrix and negative test suite cover every command.
+
+### P-06 — Content schema is weak and duplicated
+
+- **Severity:** High
+- **Evidence:** mixed `id`/`_id`, pervasive `any`, ad hoc post shapes and duplicated demo/API mappings.
+- **Impact:** type errors, migration ambiguity and UI condition sprawl.
+- **Decision:** establish canonical schemas for Workspace, User, Post, Revision, MediaAsset and Publication.
+- **Acceptance:** database, API and UI use generated or shared validated types without unsafe casting.
+
+### P-07 — Editorial workflow is presented but not modeled
+
+- **Severity:** High
+- **Evidence:** draft/scheduled/published/archived values exist without transition policy or durable jobs.
+- **Impact:** invalid state changes and misleading product claims.
+- **Decision:** model an explicit state machine with transition permissions, timestamps and audit events.
+- **Acceptance:** illegal transitions fail and scheduled publication is idempotently executed by a worker.
+
+### P-08 — Version control claims are unsupported
+
+- **Severity:** High
+- **Evidence:** no durable revision entity or restore operation is connected to the editor.
+- **Impact:** a headline feature is non-functional.
+- **Decision:** each meaningful content save produces an immutable revision; drafts track a current revision
+ and restore creates another revision rather than mutating history.
+- **Acceptance:** create, diff, list and restore revision flows have integration and E2E coverage.
+
+### P-09 — Autosave lacks concurrency semantics
+
+- **Severity:** High
+- **Evidence:** local/session persistence and generic updates do not protect simultaneous editing.
+- **Impact:** lost updates and false “no data loss” claims.
+- **Decision:** debounce client writes, use revision/version preconditions and surface merge conflicts.
+- **Acceptance:** stale update test returns conflict and UI offers reload or compare behavior.
+
+### P-10 — Image upload boundary is unsafe and inefficient
+
+- **Severity:** Critical
+- **Evidence:** large base64 request bodies, arbitrary remote images, public-id deletion before successful
+ replacement and no visible MIME/dimension policy.
+- **Impact:** memory pressure, SSRF/image abuse and irreversible media loss.
+- **Decision:** use signed direct upload or bounded multipart upload, validate media and finalize DB changes
+ only after provider success. Destructive cleanup becomes compensating background work.
+- **Acceptance:** invalid media is rejected; replacement failure preserves the existing asset.
+
+### P-11 — AI endpoint is unauthenticated and unbounded
+
+- **Severity:** Critical
+- **Evidence:** prompt requests can reach the provider without role, quota, rate limit or usage accounting.
+- **Impact:** cost abuse, provider exhaustion and prompt/data leakage.
+- **Decision:** provider adapter, authenticated server command, per-workspace quota, rate limit, timeout,
+ structured usage log and explicit user confirmation before applying generated content.
+- **Acceptance:** AI calls expose latency/token metadata and never mutate content automatically.
+
+### P-12 — AI product behavior is split between canned and live paths
+
+- **Severity:** High
+- **Evidence:** a large canned-response action coexists with a live provider route.
+- **Impact:** demonstration can imply capabilities that are not being exercised.
+- **Decision:** use one interface with explicit `demo`, `mock` and configured-provider adapters. UI must label
+ the active mode.
+- **Acceptance:** identical contract tests run against mock and provider adapters.
+
+### P-13 — UI architecture contains dead and duplicated code
+
+- **Severity:** High
+- **Evidence:** missing imports, unused components, duplicated sidebar implementations and very large custom
+ files coexist with generated UI primitives.
+- **Impact:** reviewer navigation is slow and type debt grows.
+- **Decision:** delete unreachable legacy branches, isolate generated primitives and split product features
+ by domain rather than page fragment.
+- **Acceptance:** no missing import, dead route or duplicate component ownership remains.
+
+### P-14 — Accessibility claims are unverified
+
+- **Severity:** High
+- **Evidence:** README declares WCAG compliance while lint finds interaction issues and no automated or
+ manual audit artifact exists.
+- **Impact:** compliance language is not credible.
+- **Decision:** state accessibility targets, not compliance, until axe and manual keyboard/screen-reader
+ checks are stored.
+- **Acceptance:** critical flows pass axe, keyboard navigation and focus-order review.
+
+### P-15 — Performance configuration works against the framework
+
+- **Severity:** Medium
+- **Evidence:** global no-cache headers, permissive remote images and broad client-component usage reduce
+ Next.js optimization value.
+- **Impact:** higher JS cost and unclear caching model.
+- **Decision:** define cache behavior per read model, keep authenticated editor dynamic and allow public
+ preview/content to use explicit revalidation.
+- **Acceptance:** Lighthouse and bundle reports establish budgets for LCP, INP, CLS and client JS.
+
+### P-16 — Error handling leaks internal information
+
+- **Severity:** High
+- **Evidence:** generic helper returns raw error messages and logs complete stack details without a public
+ error taxonomy.
+- **Impact:** inconsistent status codes and potential disclosure.
+- **Decision:** map domain/application errors to stable public codes and log internal details with request ID.
+- **Acceptance:** API contract tests verify status, code and non-sensitive message.
+
+### P-17 — Product onboarding is absent
+
+- **Severity:** High
+- **Evidence:** deployment opens to “select a page to edit” with limited guidance or populated navigation.
+- **Impact:** recruiter may abandon the review before discovering features.
+- **Decision:** add a marketing/demo entry, seeded workspace, guided checklist and reset-demo action.
+- **Acceptance:** first-time reviewer completes the main flow without external instructions.
+
+### P-18 — Repository evidence is incomplete
+
+- **Severity:** High
+- **Evidence:** no tests, CI, license, release, concise architecture or truthful feature matrix.
+- **Impact:** visual polish is not supported by engineering proof.
+- **Decision:** add layered tests, CI, screenshots, architecture, security notes and a live-demo badge.
+- **Acceptance:** README claims map to demo steps and automated checks.
+
+## 5. Target architecture
+
+```mermaid
+flowchart LR
+ Browser[Next.js UI] --> BFF[Server actions / route adapters]
+ BFF --> Session[Session + RBAC policy]
+ Session --> App[Application commands and queries]
+ App --> Domain[Editorial domain]
+ App --> Repo[Repository ports]
+ Repo --> DB[(Database)]
+ App --> Jobs[(Job/outbox queue)]
+ Jobs --> Publish[Publication worker]
+ Jobs --> Media[Media cleanup worker]
+ App --> AI[AI provider adapter]
+ App --> Storage[Media provider adapter]
+ BFF --> Telemetry[Errors, traces, product events]
+```
+
+Recommended feature layout:
+
+```text
+app/
+ (marketing)/
+ (auth)/
+ (workspace)/[workspaceId]/...
+src/
+ modules/{identity,editorial,media,ai,analytics}/
+ platform/{db,auth,config,observability}/
+ ui/{primitives,patterns}/
+tests/{unit,integration,e2e,accessibility}/
+```
+
+## 6. Core product invariants
+
+- Every mutable entity belongs to a workspace.
+- Identity and workspace context come from the authenticated session.
+- Only legal role/state combinations can execute a transition.
+- Published content points to an immutable revision.
+- Autosave never silently overwrites a newer revision.
+- AI output is a suggestion until a user explicitly applies it.
+- Media replacement cannot destroy the active asset before success.
+- Demo mode uses the same domain logic as configured production mode.
+
+## 7. What must be simplified
+
+- Delete the 500+ line in-memory route simulator after durable seed infrastructure exists.
+- Delete canned content duplicated across code; store demo fixtures as validated seed data.
+- Consolidate sidebar ownership and eliminate copied primitive implementations.
+- Remove Firebase remnants if Firebase is not an accepted architecture dependency.
+- Remove unused “members”, analytics and workflow UI until their server behavior is real.
+- Prefer a small number of feature modules over `_client`, `actions`, `utils`, `components` catch-all layers.
+- Avoid micro-frontends, event streaming platforms and multi-database architecture for this scope.
+
+## 8. Implementation plan
+
+### Phase 0 — Security and build containment
+
+- Upgrade vulnerable runtime dependencies.
+- Reconcile and freeze lockfile.
+- Fix type/lint errors and remove build bypasses.
+- Replace tracked `.env.local` with `.env.example`; add secret scanning.
+- Disable or guard destructive public routes until real auth exists.
+
+**Exit gate:** frozen install, audit, typecheck, lint and build pass.
+
+### Phase 1 — One coherent application core
+
+- Define canonical schemas and application ports.
+- Replace demoStore with seeded repository implementation.
+- Add session authentication and workspace/role policies.
+- Connect current UI to commands and queries through one boundary.
+
+**Exit gate:** login, list, create and edit persist across process restart and enforce roles.
+
+### Phase 2 — Editorial workflow
+
+- Add revision model, optimistic concurrency and autosave.
+- Add state machine, review transitions and scheduled publication worker.
+- Add durable audit events and public preview.
+
+**Exit gate:** complete author-reviewer-publication E2E flow passes.
+
+### Phase 3 — Media and AI hardening
+
+- Replace base64 flow with validated upload protocol.
+- Add provider adapters, quotas, usage metadata and user-controlled AI application.
+- Add retry/cleanup jobs and integration tests.
+
+**Exit gate:** failure injection proves no media loss and no unbounded AI access.
+
+### Phase 4 — Recruiter experience
+
+- Add landing page, guided demo, reset action and seeded editorial workspace.
+- Add Lighthouse, accessibility and visual regression checks.
+- Publish architecture, threat model, screenshots, video and release notes.
+
+**Exit gate:** public demo and README present the same tested feature set.
+
+## 9. Skill projection
+
+| Skill | Required evidence | Recruiter interpretation |
+|---|---|---|
+| Product engineering | guided workflow and deliberate scope | builds usable outcomes, not component collections |
+| Frontend architecture | feature boundaries and server/client discipline | understands modern React beyond UI styling |
+| UX/accessibility | keyboard, axe and onboarding evidence | treats usability as an engineering requirement |
+| Full-stack design | shared schemas, commands and durable persistence | can own vertical features end to end |
+| Security | real sessions, RBAC, safe media and AI quotas | protects costly and destructive operations |
+| Data consistency | revisions, state machine and optimistic locking | handles collaborative mutation correctly |
+| Testing | unit, integration, E2E, visual and accessibility | validates both behavior and experience |
+| Performance | measured web-vital and bundle budgets | optimizes from evidence rather than slogans |
+
+## 10. Definition of done
+
+- Frozen installation and all CI gates pass.
+- No build validation bypass remains.
+- No unreviewed critical/high runtime vulnerability remains.
+- Real session and permission checks protect every mutation.
+- One durable data path serves both demo and configured environments.
+- Revision, autosave conflict and publication transitions are tested.
+- Media and AI integrations enforce quotas, size limits and failure compensation.
+- Core flows pass Playwright and accessibility checks.
+- Public demo is populated, guided and resettable.
+- README feature table matches tested behavior.
+- GitHub metadata, license, topics, CI and release are complete.
+
+## 11. Agent operating contract
+
+An implementation agent must:
+
+1. read this RFC and map work to one or more problem IDs;
+2. treat authentication and workspace context as mandatory for every mutation;
+3. avoid creating a second demo-only business-logic path;
+4. add a test at the lowest useful layer and an E2E test for changed flagship flows;
+5. preserve existing visual identity unless the task explicitly changes product design;
+6. remove dead code when replacing a path rather than retaining indefinite compatibility branches;
+7. update schemas, API contracts and feature matrix together;
+8. run frozen install, audit, typecheck, lint, tests and build before handoff;
+9. capture screenshots or visual-regression baselines for visible changes;
+10. report changes to authorization, data migration, SEO and accessibility explicitly.
+
+Every patch handoff must include: RFC IDs addressed, user flow affected, schema/API changes, test evidence,
+security impact, visual evidence, rollback/migration notes and remaining risk.
+
+## 12. Rejected alternatives
+
+- **Keep demoStore and Mongo routes side by side:** rejected because behavior divergence is the primary
+ architecture defect.
+- **Fix only lint and presentation:** rejected because authorization and persistence remain fictitious.
+- **Client-only localStorage CMS:** rejected because it cannot demonstrate collaboration, workflow or
+ protected integrations.
+- **Add more AI features first:** rejected because the costly provider boundary is currently unprotected.
+- **Split into microservices:** rejected because module and data contracts are not yet stable.
+
+## 13. Final goal
+
+The completed project should be perceived as the portfolio's strongest visible product: polished but not
+superficial, collaborative rather than simulated, and backed by secure full-stack architecture. Its role is
+to demonstrate product judgment, frontend depth and end-to-end ownership; it should not duplicate the
+backend reliability narrative of the loyalty platform or the model-evaluation narrative of the ML project.
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 0000000..796a746
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,27 @@
+# Security policy
+
+Report vulnerabilities privately through GitHub Security Advisories. Do not open a public issue
+containing exploit details, credentials, session material or user data.
+
+Supported releases receive dependency and application-security fixes on the latest `2.x` release
+line. Security-sensitive mutations require a database-validated session, workspace membership and
+an application permission check. Stable public errors include a correlation ID and exclude stack,
+database and provider details.
+
+## Operational requirements
+
+- Use unique production values for `BETTER_AUTH_SECRET` and `CRON_SECRET`; never reuse a provider key.
+- Use HTTPS and a durable remote libSQL database for serverless deployment.
+- Invoke the leased job runner at least once per minute and alert on repeated failures.
+- Keep `AI_MODE=mock` unless the operator has accepted provider data-processing and billing terms.
+- Run `bun run audit`, `bun run security:secrets` and the full-history CI scan before release.
+- Back up the database before schema deployment; applied migrations are checksum protected.
+
+The detailed assets, trust boundaries, mitigations and residual risks are in
+[docs/security-threat-model.md](./docs/security-threat-model.md).
+
+## Historical credential notice
+
+An early repository commit contains non-empty MongoDB, Cloudinary and Gemini environment values.
+The values are not used by AutoBlog 2.x and must be rotated or revoked by their provider account
+owner. Shared history is intentionally not rewritten.
diff --git a/_client/@inset/( advisory ).tsx b/_client/@inset/( advisory ).tsx
deleted file mode 100644
index ca01966..0000000
--- a/_client/@inset/( advisory ).tsx
+++ /dev/null
@@ -1,87 +0,0 @@
-'use client';
-
-import { useState } from 'react';
-import { ImageLoader } from '@/components/imageLoader';
-import { Button } from '@/ui/button';
-import { ServerProps } from '@/app/@inset/advisory/page';
-import { useToast } from '@/hooks/use-toast';
-
-export default ({ api, data }: ServerProps) => {
- const { toast } = useToast();
- const [about, setAbout] = useState(data.about);
- const [services, setServices] = useState(data.services);
-
- const [image, setImage] = useState<{ display_name: string; public_id: string }>(data.image ?? ({} as any));
- const [file, setFile] = useState(null);
- const [timeout, startTimeout] = useState(null);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- if (file) {
- const reader = new FileReader();
- reader.readAsDataURL(file); // Converte il file in Base64
- reader.onload = async () => {
- const base64 = reader.result;
- if (typeof base64 !== 'string' || !base64) return;
- // Demo mode: Update local state only
- setImage({
- public_id: `demo-image-${Date.now()}`,
- display_name: 'Advisory Image',
- });
-
- toast({
- title: '🖼️ Advisory Image Updated',
- description: 'Cover image changed successfully',
- duration: 3000,
- });
- };
- reader.onerror = () => {
- alert('Error reading file!');
- };
- }
-
- // Demo mode: Local state update only - no API call
- console.log('Demo mode: Advisory updated locally', { about, services });
-
- toast({
- title: '✅ Advisory Panel Saved',
- description: 'About and services information updated',
- duration: 3000,
- });
- };
-
- return (
-
-
-
-
Immagine di copertina
-
-
- Save
-
-
-
-
-
-
About
-
-
-
-
Services Offered
-
-
-
- );
-};
diff --git a/_client/@inset/( home ).tsx b/_client/@inset/( home ).tsx
deleted file mode 100644
index 3d97f34..0000000
--- a/_client/@inset/( home ).tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-'use client';
-
-import { ImageLoader } from '@/components/imageLoader';
-import { ServerProps } from '@/app/@inset/home/page';
-import { useState } from 'react';
-import { useToast } from '@/hooks/use-toast';
-
-export default ({ api, images: initImages }: ServerProps) => {
- const { toast } = useToast();
- const [images, setImages] = useState(initImages ?? []);
-
- const handleChange = async (file: File, image: (typeof initImages)[0]) => {
- if (!file) return;
-
- const reader = new FileReader();
- reader.readAsDataURL(file); // Converte il file in Base64
- reader.onload = async () => {
- const base64 = reader.result as string;
-
- // Demo mode: Update local state only
- const demoImage = {
- ...image,
- public_id: `/demo/section-${image.section}-${Date.now()}.jpg`,
- display_name: `${image.section}-image`,
- };
- // @ts-ignore
- setImages(p => p.map(i => (i.section === image.section ? demoImage : i)));
-
- toast({
- title: '🖼️ Home Image Updated',
- description: `${image.section.charAt(0).toUpperCase() + image.section.slice(1)} section image changed`,
- duration: 3000,
- });
- };
- reader.onerror = () => {
- alert('Error reading file!');
- };
- };
-
- return (
-
-
Images of home sections
-
-
- {
- images.map(image => (
-
-
- {image.section}
-
- handleChange(file, image)} className="size-full" image={image} />
-
- ))
- // Aggiorna l'immagine nel database
- }
-
-
- );
-};
diff --git a/_client/@inset/( posts ) [ id ].tsx b/_client/@inset/( posts ) [ id ].tsx
deleted file mode 100644
index 92bfe32..0000000
--- a/_client/@inset/( posts ) [ id ].tsx
+++ /dev/null
@@ -1,334 +0,0 @@
-'use client';
-
-import { useRef, useState } from 'react';
-import { Input } from '@/ui/input';
-import { Button } from '@/ui/button';
-
-import { DatePicker } from '@/components/data-picker';
-import WebApp from '@/components/webapp/_export';
-import Image from 'next/image';
-
-import type { ServerProps } from '@/app/@inset/[section]/[id]/page';
-
-import { useToast } from '@/hooks/use-toast';
-
-const DEMO_PLACEHOLDER =
- 'data:image/svg+xml;utf8,Demo Image ';
-
-const resolveImageSrc = (value?: string) => {
- if (!value) return DEMO_PLACEHOLDER;
- if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') || value.startsWith('data:')) return value;
-
- return DEMO_PLACEHOLDER;
-};
-
-export default ({ section, post: defaultPost }: ServerProps) => {
- const { toast } = useToast();
-
- const [post, _setPost] = useState(defaultPost);
- const [timeout, startTimeout] = useState(null);
-
- const [page, setPage] = useState<'preview' | 'inner'>('preview');
- const [file, setFile] = useState(null);
-
- const setImage = (file: File) => {
- const reader = new FileReader();
- reader.readAsDataURL(file); // Converts the file to Base64
- reader.onload = async () => {
- const base64 = reader.result;
- if (typeof base64 !== 'string' || !base64) return;
-
- _setPost(prev => ({
- ...prev,
- image: {
- public_id: `demo-${Date.now()}`,
- display_name: prev.name.replace(/[^a-zA-Z0-9]/g, ' ') || 'demo-image',
- },
- }));
-
- toast({
- title: '🖼️ Featured Image Updated',
- description: 'New cover image set successfully',
- duration: 3000,
- });
- };
- reader.onerror = () => {
- alert('Error reading file!');
- };
- setFile(file);
- };
-
- const handleSubmit = async (updatedPost: typeof defaultPost = post) => {
- _setPost(updatedPost);
- toast({
- title: '✅ Post Saved',
- description: 'All changes have been saved successfully',
- duration: 3000,
- });
- };
-
- const setPost = async (callback: Parameters[0]) => {
- clearTimeout(timeout);
-
- const updatedPost = await new Promise(r =>
- _setPost(prev => {
- const updated = typeof callback === 'function' ? callback(prev) : callback;
-
- r(updated);
- return updated;
- }),
- );
-
- startTimeout(
- setTimeout(async () => {
- await handleSubmit(updatedPost as any);
-
- toast({
- title: 'Post autosaved!',
- duration: 10000,
- });
- }, 500),
- );
-
- return updatedPost;
- };
-
- return (
-
-
{
- setImage(fileChange);
- }}
- />
-
-
- );
-};
-
-const ImageLoader = ({
- image: cdlImage,
- onFileChange,
- ...props
-}: React.ComponentProps<'div'> & {
- image: { display_name: string; public_id: string };
- onFileChange: (file: File) => void;
-}) => {
- const fileInput = useRef(null);
-
- const [image, setImage] = useState<{ display_name: string; public_id: string } | null>(cdlImage);
- const [file, setFile] = useState(null);
- const [preview, setPreview] = useState(null); // Preview state
-
- const handleFileChange = () => {
- const file = fileInput.current?.files?.[0];
-
- if (!file) return;
- // Validate the selected file format
- const validFormats = ['image/webp', 'image/png', 'image/jpeg', 'image/jpg'];
- if (!validFormats.includes(file.type)) {
- alert('Unsupported file format. Use WebP, PNG, JPEG, or JPG.');
- setPreview(null);
- setFile(null);
- return;
- }
-
- // Create a temporary preview
- setPreview(URL.createObjectURL(file));
- setFile(file);
- onFileChange(file);
- setImage(null);
- };
-
- return (
-
-
-
-
-
- {image ?
- <>
-
-
setImage(null)}>
- X
-
- >
- : !!preview ?
- <>
-
-
(setPreview(null), setFile(null))}>
- X
-
- >
- :
fileInput.current?.click()}>
- Upload cover image
-
- }
-
-
-
-
- );
-};
diff --git a/_client/@inset/( users ) [ id ].tsx b/_client/@inset/( users ) [ id ].tsx
deleted file mode 100644
index 9ab4154..0000000
--- a/_client/@inset/( users ) [ id ].tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-'use client';
-
-import { cn } from '@/utils/shadcn';
-
-import { MdDelete } from 'react-icons/md';
-import { RiEdit2Fill } from 'react-icons/ri';
-import { BiSolidSave } from 'react-icons/bi';
-
-import { Checkbox } from '@/ui/checkbox';
-import { DetailedHTMLProps, HtmlHTMLAttributes, useEffect, useState } from 'react';
-
-import Copy from '@/components/Copy';
-import { Button } from '@/ui/button';
-import { Separator } from '@/ui/separator';
-
-import type { CheckedState } from '@radix-ui/react-checkbox';
-import type { ServerProps } from '@/app/@inset/users/page';
-import { Input } from '@/ui/input';
-
-export { Sidebar } from './( users )';
-
-export const Main = ({ api, users }: ServerProps) => {
- const [checkAll, setCheckAll] = useState(false);
-
- return (
-
-
-
- setCheckAll(check)}
- />
-
- Username
-
-
-
-
-
-
-
- {users.map(user => (
-
- ))}
-
-
- );
-};
-
-const User = ({
- user: _user,
- checkAll,
- className,
- ...props
-}: {
- user: ServerProps['users'][0];
- checkAll: CheckedState;
-} & Partial, HTMLLabelElement>>) => {
- const [user, setUser] = useState(_user);
- const [edit, setEdit] = useState(false);
- const [checked, setChecked] = useState(false);
-
- useEffect(() => {
- setChecked(checkAll);
- }, [checkAll]);
-
- return (
- span]:flex [&>span]:flex-row [&>span]:items-center [&>span]:gap-[--p]',
- className
- )}
- {...props}>
-
- setChecked(check)}
- />
-
-
- setUser(p => ({ ...p, username: e.target.value }))
- }
- />
-
-
- {user.email}
-
-
-
- {user.password}
-
-
-
- setEdit(p => !p)}
- className="border-input border !bg-background rounded-full aspect-square h-2/3 p-0">
-
-
- setEdit(p => !p)}
- className="border-input border rounded-full aspect-square h-2/3 p-0">
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/_client/@inset/( users ).tsx b/_client/@inset/( users ).tsx
deleted file mode 100644
index 8ae949f..0000000
--- a/_client/@inset/( users ).tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-'use client';
-
-import { ServerProps } from '@/app/@sidebar/users/page';
-import Context from '@/components/groups/context';
-import MembersPage from '@/components/members-page';
-import { Input } from '@/ui/input';
-import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuItem,
- SidebarMenuButton,
- SidebarMenuSub,
-} from '@/ui/sidebar';
-import { SectionIcon } from '@radix-ui/react-icons';
-import Link from 'next/link';
-import { useState } from 'react';
-
-export const Sidebar = ({ api, roles }: ServerProps) => {
- return (
-
-
- Roles
-
-
- {roles.map(role => (
-
- ))}
-
-
- );
-};
-
-export { Main } from './( users ) [ id ]';
-
-const Role = ({
- id,
- name,
-}: {
- children?: React.ReactNode;
- id: string;
- name: string;
- onRename: (name: string) => void;
-}) => {
- const [rename, setRename] = useState(null);
-
- return (
-
- setRename(name) },
- { children: 'Delete', className: 'text-red-400' },
- ]}>
- setRename(null)}>
- {typeof rename === 'string' ? (
- <>
-
-
- setRename(e.target.value)
- }
- onBlur={() => setRename(null)}
- />
- >
- ) : (
-
-
-
- {name}
-
-
- )}
-
-
-
- );
-};
diff --git a/_client/@sidebar/( advisory ).tsx b/_client/@sidebar/( advisory ).tsx
deleted file mode 100644
index 1ffee91..0000000
--- a/_client/@sidebar/( advisory ).tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-'use client';
-
-export default () => null;
diff --git a/_client/@sidebar/( posts ) [ id ].tsx b/_client/@sidebar/( posts ) [ id ].tsx
deleted file mode 100644
index cbbc7e2..0000000
--- a/_client/@sidebar/( posts ) [ id ].tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-'use client';
-
-export { Sidebar } from './( posts )';
diff --git a/_client/@sidebar/( posts ).tsx b/_client/@sidebar/( posts ).tsx
deleted file mode 100644
index a97c66b..0000000
--- a/_client/@sidebar/( posts ).tsx
+++ /dev/null
@@ -1,174 +0,0 @@
-'use client';
-
-import { useCallback, useState } from 'react';
-import { Plus } from 'lucide-react';
-import { CgSortAz } from 'react-icons/cg';
-
-import Group, { EditableGroup } from '@/components/groups/group';
-import SubGroup from '@/components/groups/subGroup';
-import Item from '@/components/groups/item';
-
-import type { ServerProps } from '@/app/@sidebar/[section]/page';
-
-import { useParams } from 'next/navigation';
-import type { Group as GroupType } from '@/types/group';
-import { Post } from '@/types/post';
-import { useToast } from '@/hooks/use-toast';
-
-export const Main = ({ text }: { text: string }) => (
-
-
{text}
-
-);
-
-export const Sidebar = ({ api, groups: initGroups, section }: ServerProps) => {
- const params = useParams();
- const [postId] = [params.id].flat();
- const { toast } = useToast();
-
- const [groups, setGroups] = useState[]>(initGroups as any);
- const [addGroup, setAddGroup] = useState(null);
-
- return (
- <>
-
-
-
Groups
-
-
{
- console.log('addGroup', addGroup);
- setAddGroup('');
- }}>
-
-
-
-
- {typeof addGroup === 'string' && (
- {
- setAddGroup(null);
- if (!name || !name.trim().length) return;
-
- await api.createGroup(name);
-
- setGroups(await api.getGroups());
- toast({
- title: '✨ New Group Created',
- description: `"${name}" added to ${section}`,
- duration: 3000,
- });
- }}
- />
- )}
- {groups?.length > 0 &&
- groups.map(group => (
- {
- await api.createSubGroup({
- group,
- name: subGroupName,
- });
-
- setGroups(await api.getGroups());
- toast({
- title: '📂 Subgroup Created',
- description: `"${subGroupName}" in ${group.name}`,
- duration: 3000,
- });
- }}
- onRename={async ({ id, name }) => {
- await api.renameGroup({ id, name });
- setGroups(await api.getGroups());
- toast({
- title: '✏️ Group Renamed',
- description: `Updated to "${name}"`,
- duration: 2500,
- });
- }}
- onDelete={async id => {
- await api.deleteGroup(id);
- setGroups(await api.getGroups());
- toast({
- title: '🗑️ Group Deleted',
- description: 'Group removed permanently',
- duration: 2500,
- });
- }}>
- {group?.sub_groups?.length > 0 &&
- group.sub_groups.map(subGroup => (
- item.id === postId)}
- {...subGroup}
- onDelete={async subGroupId => {
- await api.deleteSubGroup(subGroupId);
- setGroups(await api.getGroups());
- toast({
- title: '🗑️ Subgroup Deleted',
- description: 'Subgroup removed permanently',
- duration: 2500,
- });
- }}
- onRename={async sub_group => {
- await api.renameSubGroup(sub_group);
- setGroups(await api.getGroups());
- toast({
- title: '✏️ Subgroup Renamed',
- description: `Updated to "${sub_group.name}"`,
- duration: 2500,
- });
- }}
- onCreateNewPost={async name => {
- await api.createPost({
- group,
- sub_group: subGroup,
- name,
- });
- setGroups(await api.getGroups());
- toast({
- title: '📝 New Post Created',
- description: `"${name}" ready for editing`,
- duration: 3000,
- });
- }}>
- {subGroup?.items?.length > 0 &&
- subGroup.items.map(item => (
- - {
- await api.renamePost({
- id: item.id,
- name,
- });
- setGroups(await api.getGroups());
- toast({
- title: '✏️ Post Renamed',
- description: `Title updated to "${name}"`,
- duration: 2500,
- });
- }}
- onDelete={async id => {
- await api.deletePost(id);
- setGroups(await api.getGroups());
- toast({
- title: '🗑️ Post Deleted',
- description: 'Post removed permanently',
- duration: 2500,
- });
- }}
- />
- ))}
-
- ))}
-
- ))}
- >
- );
-};
diff --git a/_client/@sidebar/( users ) [ id ].tsx b/_client/@sidebar/( users ) [ id ].tsx
deleted file mode 100644
index 9ab4154..0000000
--- a/_client/@sidebar/( users ) [ id ].tsx
+++ /dev/null
@@ -1,133 +0,0 @@
-'use client';
-
-import { cn } from '@/utils/shadcn';
-
-import { MdDelete } from 'react-icons/md';
-import { RiEdit2Fill } from 'react-icons/ri';
-import { BiSolidSave } from 'react-icons/bi';
-
-import { Checkbox } from '@/ui/checkbox';
-import { DetailedHTMLProps, HtmlHTMLAttributes, useEffect, useState } from 'react';
-
-import Copy from '@/components/Copy';
-import { Button } from '@/ui/button';
-import { Separator } from '@/ui/separator';
-
-import type { CheckedState } from '@radix-ui/react-checkbox';
-import type { ServerProps } from '@/app/@inset/users/page';
-import { Input } from '@/ui/input';
-
-export { Sidebar } from './( users )';
-
-export const Main = ({ api, users }: ServerProps) => {
- const [checkAll, setCheckAll] = useState(false);
-
- return (
-
-
-
- setCheckAll(check)}
- />
-
- Username
-
-
-
-
-
-
-
- {users.map(user => (
-
- ))}
-
-
- );
-};
-
-const User = ({
- user: _user,
- checkAll,
- className,
- ...props
-}: {
- user: ServerProps['users'][0];
- checkAll: CheckedState;
-} & Partial, HTMLLabelElement>>) => {
- const [user, setUser] = useState(_user);
- const [edit, setEdit] = useState(false);
- const [checked, setChecked] = useState(false);
-
- useEffect(() => {
- setChecked(checkAll);
- }, [checkAll]);
-
- return (
- span]:flex [&>span]:flex-row [&>span]:items-center [&>span]:gap-[--p]',
- className
- )}
- {...props}>
-
- setChecked(check)}
- />
-
-
- setUser(p => ({ ...p, username: e.target.value }))
- }
- />
-
-
- {user.email}
-
-
-
- {user.password}
-
-
-
- setEdit(p => !p)}
- className="border-input border !bg-background rounded-full aspect-square h-2/3 p-0">
-
-
- setEdit(p => !p)}
- className="border-input border rounded-full aspect-square h-2/3 p-0">
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/_client/@sidebar/( users ).tsx b/_client/@sidebar/( users ).tsx
deleted file mode 100644
index 8ae949f..0000000
--- a/_client/@sidebar/( users ).tsx
+++ /dev/null
@@ -1,84 +0,0 @@
-'use client';
-
-import { ServerProps } from '@/app/@sidebar/users/page';
-import Context from '@/components/groups/context';
-import MembersPage from '@/components/members-page';
-import { Input } from '@/ui/input';
-import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuItem,
- SidebarMenuButton,
- SidebarMenuSub,
-} from '@/ui/sidebar';
-import { SectionIcon } from '@radix-ui/react-icons';
-import Link from 'next/link';
-import { useState } from 'react';
-
-export const Sidebar = ({ api, roles }: ServerProps) => {
- return (
-
-
- Roles
-
-
- {roles.map(role => (
-
- ))}
-
-
- );
-};
-
-export { Main } from './( users ) [ id ]';
-
-const Role = ({
- id,
- name,
-}: {
- children?: React.ReactNode;
- id: string;
- name: string;
- onRename: (name: string) => void;
-}) => {
- const [rename, setRename] = useState(null);
-
- return (
-
- setRename(name) },
- { children: 'Delete', className: 'text-red-400' },
- ]}>
- setRename(null)}>
- {typeof rename === 'string' ? (
- <>
-
-
- setRename(e.target.value)
- }
- onBlur={() => setRename(null)}
- />
- >
- ) : (
-
-
-
- {name}
-
-
- )}
-
-
-
- );
-};
diff --git a/actions/check-user.ts b/actions/check-user.ts
deleted file mode 100644
index 166efa0..0000000
--- a/actions/check-user.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-'use server';
-
-export default async (username: string, secretToken: string) => {
- const valid = !!username?.trim?.() && !!secretToken?.trim?.();
-
- return {
- success: valid ? 1 : 0,
- data: { authorized: valid },
- message: valid ? 'Demo mode: access granted.' : 'Demo mode: missing credentials.',
- };
-};
diff --git a/actions/firebase.ts b/actions/firebase.ts
deleted file mode 100644
index 5b27658..0000000
--- a/actions/firebase.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-const app = { mode: 'demo' };
-const auth = { mode: 'demo' };
-
-async function signInWithGoogle() {
- return {
- user: {
- uid: 'demo-user',
- email: 'demo.user@example.com',
- displayName: 'Demo User',
- },
- credential: null,
- };
-}
-
-export { app, auth, signInWithGoogle };
diff --git a/actions/indexed-db.ts b/actions/indexed-db.ts
deleted file mode 100644
index 822f0b8..0000000
--- a/actions/indexed-db.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-'use client';
-
-export const openDatabase = (dbName: string, storeName: string, version = 1): Promise => {
- return new Promise((resolve, reject) => {
- if (typeof window === 'undefined') {
- reject('IndexedDB is not supported in server-side environments.');
- return;
- }
-
- const request = indexedDB.open(dbName, version);
-
- request.onupgradeneeded = event => {
- if (!event.target) return;
- // @ts-ignore
- const db = (event.target as IDBOpenDBRequest).result;
-
- // Creates the object store if it does not exist
- if (!db.objectStoreNames.contains(storeName)) {
- db.createObjectStore(storeName, { autoIncrement: true });
- }
- };
-
- request.onsuccess = event => {
- if (!event.target) return;
- resolve((event.target as IDBOpenDBRequest).result);
- };
-
- request.onerror = event => {
- // @ts-ignore
- reject(`Database open error: ${event.target.error}`);
- };
- });
-};
-
-export const putItem = async (dbName: string, storeName: string, key: string, item: any) => {
- const db = await openDatabase(dbName, storeName);
-
- const transaction = db.transaction(storeName, 'readwrite');
- const store = transaction.objectStore(storeName);
-
- const request = store.put(item, key);
-
- request.onsuccess = event => {
- if (!event.target) return;
- return (event.target as IDBRequest).result;
- };
-
- request.onerror = event => {
- // @ts-ignore
- throw new Error(`Item write error: ${event.target.error}`);
- };
-};
-
-export const getItem = async (dbName: string, storeName: string, itemKey: string) => {
- const db = await openDatabase(dbName, storeName);
-
- return await new Promise((resolve, reject) => {
- const transaction = db.transaction(storeName, 'readonly');
- const store = transaction.objectStore(storeName);
-
- const request = store.get(itemKey);
-
- request.onsuccess = event => {
- if (!event.target) return;
- resolve((event.target as IDBRequest).result);
- };
-
- request.onerror = event => {
- // @ts-ignore
- reject(`Item read error: ${event.target.error}`);
- };
- });
-};
-
-export const deleteItem = async (dbName: string, storeName: string, itemKey: string) => {
- const db = await openDatabase(dbName, storeName);
-
- return await new Promise((resolve, reject) => {
- const transaction = db.transaction(storeName, 'readwrite');
- const store = transaction.objectStore(storeName);
-
- const request = store.delete(itemKey);
-
- request.onsuccess = event => {
- if (!event.target) return;
- resolve((event.target as IDBRequest).result);
- };
-
- request.onerror = event => {
- // @ts-ignore
- reject(`Item delete error: ${event.target.error}`);
- };
- });
-};
diff --git a/actions/send-message.ts b/actions/send-message.ts
deleted file mode 100644
index 0080490..0000000
--- a/actions/send-message.ts
+++ /dev/null
@@ -1,593 +0,0 @@
-'use server';
-
-interface DemoMessage {
- keywords: string[];
- responses: string[];
-}
-
-const demoResponses: DemoMessage[] = [
- {
- keywords: ['content', 'create', 'post', 'write', 'article'],
- responses: [
- `Creating compelling content is at the heart of a successful blog. Here are the key steps to get started:
-
-1. **Choose Your Topic**: Select a subject that resonates with your audience. Consider trending topics, user questions, or gaps in existing coverage related to your niche.
-
-2. **Research Thoroughly**: Gather credible sources, statistics, and expert insights. This adds authority to your content and provides value to readers.
-
-3. **Structure Your Outline**: Organize your thoughts into a logical flow. A strong outline typically includes:
- - Engaging introduction (hook your reader)
- - Clear sections with subheadings
- - Relevant examples and case studies
- - Actionable takeaways
- - Compelling conclusion
-
-4. **Write Your Draft**: Focus on clarity over perfection. Use conversational language, break up long paragraphs, and incorporate relevant keywords naturally.
-
-5. **Optimize for SEO**: Include your primary keyword in the title, introduction, and headers. Use alt text for images and create descriptive slugs.
-
-6. **Review and Edit**: Proofread carefully, check links, and ensure your formatting is consistent. Consider different perspectives and refine your message.
-
-In our Autoblog CMS, you can structure posts within categories and subcategories, add featured images from your media library, and set publish states (Draft, Published, Scheduled). The system supports markdown formatting for rich text editing, making it easy to create polished content.`,
- `Effective content creation starts with understanding your audience and their pain points. Here's a structured approach:
-
-**Pre-Writing Phase**:
-- Define your target reader persona
-- Identify the problem you're solving
-- Research competitor content to find gaps
-
-**Writing Process**:
-- Start with a compelling headline that promises value
-- Use the inverted pyramid: most important info first
-- Include visuals to break up text and improve comprehension
-- Add internal links to related content
-- Use numbered lists and bullet points for scannability
-
-**Post-Writing Optimization**:
-- Fact-check all claims and citations
-- Optimize images for web (compression, alt text)
-- Add a clear call-to-action
-- Create an engaging meta description
-- Test readability across devices
-
-With our Autoblog CMS, you can manage multiple content sections simultaneously. Group related posts, maintain version control through draft status, and schedule publication dates. The typewriter effect in our AI assistant helps you see detailed guidance as it's composed, making complex topics easier to digest.`,
- ],
- },
- {
- keywords: ['design', 'layout', 'template', 'customize', 'styling'],
- responses: [
- `Designing a professional blog is crucial for both aesthetics and user experience. Here's how to approach your design strategy:
-
-**Visual Hierarchy**:
-- Use consistent typography (typically 2-3 font families max)
-- Create clear size distinctions between headings and body text
-- Apply adequate whitespace to improve readability
-- Establish a color palette of 3-5 complementary colors
-
-**Layout Best Practices**:
-- Keep the main content column between 600-800px wide (optimal reading width)
-- Place call-to-action buttons consistently (usually above the fold)
-- Use responsive grids that adapt to mobile and tablet screens
-- Ensure navigation is intuitive and accessible
-
-**Visual Elements**:
-- Feature images with proper aspect ratios (16:9 for headers)
-- Include author avatars and bylines for credibility
-- Use icons to break up lengthy text sections
-- Incorporate brand elements consistently
-
-**Performance Considerations**:
-- Optimize all images for web (WebP format when possible)
-- Use lazy loading for images below the fold
-- Minimize CSS and JavaScript
-- Implement a CDN for faster content delivery
-
-Our Autoblog CMS integrates with Tailwind CSS for rapid styling and Next.js Image optimization. You can upload custom images for each section and maintain visual consistency across your blog without touching code.`,
- `Modern blog design combines aesthetics with functionality. Consider these design principles:
-
-**User Experience Design**:
-- Create user journeys that guide readers to key content
-- Implement breadcrumb navigation for clarity
-- Use consistent button styles and hover states
-- Add search functionality for content discovery
-
-**Mobile-First Approach**:
-- Design for mobile first, then enhance for larger screens
-- Ensure touch targets are at least 48x48px
-- Test all interactive elements on touch devices
-- Minimize redirects and loading times
-
-**Accessibility Standards**:
-- Maintain sufficient color contrast (WCAG AA minimum)
-- Use semantic HTML for screen reader compatibility
-- Provide alt text for all images
-- Ensure keyboard navigation works throughout
-
-**Branding Integration**:
-- Display logo consistently in header and footer
-- Use brand colors throughout the design
-- Maintain a consistent tone in microcopy
-- Include social media links prominently
-
-The Autoblog CMS provides a modular component library with pre-built sections like featured posts, category navigation, and related articles. You can customize colors, spacing, and layouts through the admin interface.`,
- ],
- },
- {
- keywords: ['seo', 'optimize', 'ranking', 'traffic', 'visibility'],
- responses: [
- `Search Engine Optimization is essential for blog visibility. Here's a comprehensive SEO strategy:
-
-**On-Page SEO**:
-- Include target keyword in title (front-loaded ideally)
-- Write compelling meta descriptions (155-160 characters)
-- Use H1, H2, H3 headers logically to structure content
-- Maintain keyword density around 1-2% naturally
-- Include internal links to related articles with descriptive anchor text
-- Optimize URL slugs to include keywords
-
-**Technical SEO**:
-- Ensure fast page load times (target under 3 seconds)
-- Implement SSL/HTTPS for security
-- Create an XML sitemap and submit to search engines
-- Use structured data markup (schema.org) for rich snippets
-- Ensure mobile responsiveness and mobile-first indexing compatibility
-- Fix broken links regularly
-
-**Content Strategy**:
-- Create comprehensive, in-depth content (1500+ words typically performs better)
-- Target long-tail keywords with less competition
-- Update existing content regularly to maintain freshness
-- Create content clusters with pillar pages and supporting articles
-- Build topical authority by covering related subjects deeply
-
-**Link Building**:
-- Earn backlinks from authoritative sites
-- Use internal linking to distribute page authority
-- Create linkable assets (guides, research, tools)
-- Guest post on relevant industry blogs
-- Monitor backlinks regularly
-
-The Autoblog CMS automatically supports URL optimization, image alt text management, and internal linking. Our advisory feature helps you maintain editorial standards for SEO best practices.`,
- `Improving your blog's search engine ranking requires a multi-faceted approach:
-
-**Keyword Research & Strategy**:
-- Use tools like Google Search Console to identify search queries
-- Analyze competitors ranking for your target keywords
-- Target keywords with commercial intent and decent search volume
-- Create topic clusters around core themes
-- Include semantic variations and LSI keywords naturally
-
-**Content Quality Signals**:
-- Write original, authoritative content that demonstrates expertise
-- Back claims with credible sources and citations
-- Include multimedia content (images, videos, infographics)
-- Update content regularly to maintain freshness signals
-- Maintain consistent publishing schedule
-
-**User Experience Signals**:
-- Keep bounce rate low through engaging introductions
-- Improve dwell time with well-structured, readable content
-- Use internal links strategically to keep users engaged
-- Optimize for Core Web Vitals (LCP, FID, CLS)
-- Ensure smooth navigation and intuitive site structure
-
-**Authority Building**:
-- Develop E-E-A-T signals (Experience, Expertise, Authoritativeness, Trustworthiness)
-- Create author bios with credentials and social links
-- Engage with comments and community discussions
-- Establish yourself as a thought leader through original research
-- Build a network of quality backlinks in your niche
-
-Monitor your progress with Google Analytics and Google Search Console. The Autoblog CMS provides fields for optimizing every article's discoverability.`,
- ],
- },
- {
- keywords: ['editorial', 'quality', 'standards', 'guidelines', 'brand'],
- responses: [
- `Maintaining editorial standards is crucial for building a strong, trustworthy blog brand:
-
-**Editorial Guidelines**:
-- Develop a comprehensive brand voice and tone guide
-- Define your target audience and their preferences clearly
-- Create writing standards (grammar, punctuation, style)
-- Establish fact-checking and verification procedures
-- Set clear approval workflows before publication
-
-**Content Quality Checklist**:
-- ✓ Fact-check all claims and statistics
-- ✓ Verify sources are credible and current
-- ✓ Check for plagiarism using detection tools
-- ✓ Ensure consistency with brand voice and style
-- ✓ Proofread for grammar, spelling, and punctuation
-- ✓ Verify all links are working
-- ✓ Optimize images for web and accessibility
-- ✓ Review for SEO best practices
-- ✓ Ensure meta descriptions and titles are compelling
-
-**Consistency Standards**:
-- Use consistent date formats and time zones
-- Maintain uniform heading hierarchy
-- Apply consistent formatting to lists and quotes
-- Use template structures for recurring content types
-- Keep image dimensions and aspect ratios consistent
-
-**Governance**:
-- Assign editorial roles (writer, editor, reviewer)
-- Document approval processes and timelines
-- Maintain content calendar with deadlines
-- Archive editorial decisions and guidelines
-- Regular audits of published content for quality
-
-Our Autoblog CMS includes an editorial advisory section and content staging system, allowing you to maintain draft status, schedule publications, and manage multiple contributors. The sidebar organization helps maintain thematic consistency across your content library.`,
- `A strong editorial strategy ensures your blog maintains credibility and professional standards:
-
-**Voice & Tone Guidelines**:
-- Define primary and secondary tone attributes
-- Create examples of correct and incorrect voice applications
-- Establish guidelines for audience interaction
-- Document how to handle corrections and updates
-- Create templates for common content types
-
-**Content Standards**:
-- Set minimum word counts for comprehensive coverage
-- Define image requirements (resolution, formats, alt text)
-- Establish fact-checking procedures
-- Create citation standards and bibliography practices
-- Document content freshness policies
-
-**Review Process**:
-- Multiple-eye review before publication
-- Subject matter expert validation for technical topics
-- Legal review if covering potentially sensitive areas
-- Performance tracking post-publication
-- A/B testing of headlines and meta descriptions
-
-**Brand Protection**:
-- Consistency guidelines across all sections
-- Approved terminology and language standards
-- Visual identity standards for images and graphics
-- Attribution policies for sourced content
-- Crisis communication procedures
-
-**Author Development**:
-- Onboarding materials for new writers
-- Style guide with examples
-- Feedback mechanisms for continuous improvement
-- Regular training on company standards
-- Performance analytics and optimization feedback
-
-The Autoblog CMS allows you to manage different content sections (Featured, Design, Culture, Insights, Resources) with their own editorial standards, tracked through our demo data structure.`,
- ],
- },
- {
- keywords: ['analytics', 'metrics', 'performance', 'data', 'insights'],
- responses: [
- `Understanding your blog's performance is key to continuous improvement:
-
-**Key Metrics to Track**:
-- **Traffic Metrics**: Page views, unique visitors, traffic sources
-- **Engagement Metrics**: Average session duration, bounce rate, pages per session
-- **Conversion Metrics**: Click-through rates, goal completions, conversion funnels
-- **Search Metrics**: Keywords, rankings, click position, impressions
-- **Content Metrics**: Top performing articles, scroll depth, time on page
-
-**Tools for Analysis**:
-- Google Analytics 4 for comprehensive web analytics
-- Google Search Console for search performance tracking
-- Heatmapping tools (Hotjar, Microsoft Clarity) for user behavior
-- A/B testing platforms for headline and layout optimization
-- Social media analytics for content distribution
-
-**Dashboard Creation**:
-- Set up custom dashboards for different stakeholder needs
-- Monitor KPIs that align with business objectives
-- Create automated reports for regular review
-- Set up alerts for significant changes (positive or negative)
-- Compare metrics across time periods and segments
-
-**Optimization Based on Data**:
-- Identify top-performing content themes
-- Analyze underperforming content for improvements
-- Test variations in headlines, images, and CTAs
-- Optimize publish times based on audience behavior
-- Refine content promotion strategies
-
-**Advanced Analysis**:
-- Cohort analysis to understand user behavior patterns
-- Attribution modeling to understand conversion paths
-- Competitor benchmarking to contextualize performance
-- Predictive analytics for forecasting trends
-- Audience segmentation for personalized content
-
-Implement consistent tracking across your Autoblog CMS to understand which sections, categories, and individual articles drive the most value for your business.`,
- `Data-driven content strategy maximizes your blog's impact:
-
-**Baseline Metrics**:
-- Establish baseline metrics for all key indicators
-- Document current state before implementing changes
-- Set realistic improvement targets
-- Create a measurement timeline
-- Define success criteria clearly
-
-**Traffic Analysis**:
-- Identify which traffic sources convert best
-- Analyze user journey from first click to conversion
-- Track referral sources and affiliate performance
-- Monitor brand vs. non-brand search traffic
-- Analyze geographic and demographic distribution
-
-**Content Performance**:
-- Calculate revenue per article (if monetized)
-- Identify content with highest engagement
-- Track content decay over time (freshness impact)
-- Analyze topic clustering performance
-- Measure impact of internal linking strategies
-
-**User Behavior Insights**:
-- Map user journeys through your content
-- Identify decision points where users drop off
-- Analyze scroll depth to understand engagement
-- Track form submission patterns
-- Monitor search behavior on-site
-
-**Business Impact**:
-- Connect content metrics to business outcomes
-- Calculate customer acquisition cost by channel
-- Track lifetime value of organic traffic
-- Measure brand awareness metrics
-- Quantify thought leadership impact
-
-Use these insights to continuously evolve your Autoblog CMS strategy, focusing resources on content types and topics that drive measurable business value.`,
- ],
- },
- {
- keywords: ['publish', 'schedule', 'launch', 'deploy', 'release'],
- responses: [
- `Publishing your content strategically maximizes its impact:
-
-**Pre-Publication Checklist**:
-- ✓ Final proofreading and fact-checking
-- ✓ Optimize meta description and title tag
-- ✓ Confirm featured image is selected and optimized
-- ✓ Review all internal links are working
-- ✓ Verify formatting on mobile devices
-- ✓ Check social media preview appearances
-- ✓ Assign appropriate categories and tags
-- ✓ Schedule any social media promotion
-
-**Publication Timing Strategy**:
-- Publish when your audience is most active
-- Avoid publishing during major news cycles (unless relevant)
-- Space content to maintain consistent blog activity
-- Consider email list engagement timing
-- Analyze historical data for optimal publish times
-
-**Social Media Promotion**:
-- Create multiple social media variations
-- Pull important quotes for graphics
-- Create teaser content driving to full article
-- Engage with comments and shares actively
-- Schedule follow-up posts for evergreen content
-
-**Email Strategy**:
-- Include article in newsletter campaigns
-- Segment list for targeted promotion
-- Create standalone email series for pillar content
-- Encourage subscriptions throughout article
-- Track email-driven traffic separately
-
-**Post-Publication**:
-- Monitor comments and respond promptly
-- Track initial traffic and performance metrics
-- Share with relevant communities and networks
-- Update internal links across the site
-- Monitor for any technical issues
-- Plan refresh schedule for ongoing updates
-
-The Autoblog CMS allows you to set publish status (Draft/Published), schedule content, and manage multiple sections with coordinated releases.`,
- `Effective publishing workflow ensures consistent, high-quality content delivery:
-
-**Content Calendar Management**:
-- Plan content 2-3 months in advance
-- Balance evergreen and timely content
-- Distribute content types across publishing schedule
-- Account for seasonal trends and events
-- Build flexibility for trending topics
-
-**Workflow Automation**:
-- Set up automated reminders for upcoming deadlines
-- Create templates for different content types
-- Automate social media sharing
-- Set up distribution to multiple channels
-- Configure email notifications for approvals
-
-**Version Control**:
-- Track revisions and editorial changes
-- Document approval workflows
-- Maintain changelog of content updates
-- Archive previous versions for reference
-- Track by-line attributions
-
-**Multi-Channel Distribution**:
-- Repurpose content for different platforms
-- Adapt format for LinkedIn, Twitter, Medium, etc.
-- Create downloadable resources (PDFs, checklists)
-- Distribute via email, RSS, social media
-- Track performance across each channel
-
-**Launch Coordination**:
-- Coordinate with marketing and PR teams
-- Plan influencer outreach and promotion
-- Schedule paid promotion if applicable
-- Time publications to maximize visibility
-- Prepare crisis communication plans if needed
-
-Our Autoblog CMS supports scheduling, draft management, and cross-section organization for coordinated content launches.`,
- ],
- },
- {
- keywords: ['help', 'assist', 'advice', 'recommend', 'suggestion'],
- responses: [
- `I'm here to help you maximize your Autoblog CMS! Here's what I can assist with:
-
-**Content Creation Support**:
-- Help you brainstorm article ideas and topics
-- Provide writing templates and structures
-- Suggest content optimization strategies
-- Recommend relevant keywords and topics
-- Guide you through editorial best practices
-
-**CMS Navigation & Features**:
-- Explain how to use different CMS sections
-- Help organize content into categories and subcategories
-- Guide you through publishing workflows
-- Assist with media library management
-- Support scheduling and planning features
-
-**Strategy & Planning**:
-- Help develop your content calendar
-- Suggest audience-focused topics
-- Recommend content distribution strategies
-- Guide SEO optimization efforts
-- Support analytics interpretation
-
-**Quality & Standards**:
-- Review content against editorial guidelines
-- Suggest improvements for clarity and engagement
-- Help with tone and voice consistency
-- Provide feedback on structure and flow
-- Recommend fact-checking sources
-
-How can I best support your blogging goals today? Feel free to ask about writing techniques, CMS features, SEO strategies, content planning, or anything else related to your Autoblog CMS platform.`,
- `I'm your Autoblog CMS assistant, ready to help with:
-
-**Writing & Content Strategy**:
-- Develop compelling article outlines
-- Improve existing content for better performance
-- Suggest engaging headlines and meta descriptions
-- Create content calendars and posting schedules
-- Research trending topics in your niche
-
-**Technical Guidance**:
-- Navigate CMS features and sections
-- Organize content effectively
-- Manage media and images
-- Optimize for search engines
-- Troubleshoot common issues
-
-**Editorial Excellence**:
-- Maintain quality and consistency
-- Develop brand voice guidelines
-- Create editorial standards
-- Improve readability and engagement
-- Ensure factual accuracy
-
-**Growth & Analytics**:
-- Interpret performance metrics
-- Identify top-performing content
-- Spot opportunities for improvement
-- Develop audience engagement strategies
-- Plan content iterations based on data
-
-Whether you're a new blogger or experienced content creator, I'm here to provide practical, actionable guidance. What would you like to focus on?`,
- ],
- },
- {
- keywords: ['ai', 'assistant', 'gemini', 'intelligence', 'feature'],
- responses: [
- `The AI Assistant in your Autoblog CMS is designed to enhance your content creation workflow intelligently:
-
-**Intelligent Features**:
-- **Smart Ideation**: Generate content ideas based on audience interests and trends
-- **Content Analysis**: Analyze your existing content for performance patterns
-- **SEO Guidance**: Provide keyword suggestions and optimization recommendations
-- **Writing Enhancement**: Suggest improvements for clarity, engagement, and tone
-- **Structure Support**: Help organize thoughts into compelling narratives
-
-**How This Integration Works**:
-The AI assistant learns from your content patterns and audience behavior. Each question you ask helps refine future recommendations. The typewriter effect you see simulates real-time thinking, making complex guidance easier to absorb incrementally rather than all at once.
-
-**Supported Use Cases**:
-- Brainstorming article topics and angles
-- Outlining comprehensive guides and tutorials
-- Optimizing headlines for click-through
-- Improving content readability and flow
-- Developing content strategy and editorial calendars
-- Analyzing performance metrics and insights
-- Creating consistent brand voice and tone
-
-**Learning & Improvement**:
-The more you interact with the AI assistant, the better it understands your:
-- Writing style and preferences
-- Audience and niche focus
-- Content performance patterns
-- Brand voice and values
-- Content creation goals
-
-This creates a personalized assistant that improves recommendations over time.`,
- `Your Autoblog CMS AI Assistant represents the future of blogging technology:
-
-**Why AI Integration Matters**:
-- **Speed**: Generate quality content faster with intelligent suggestions
-- **Quality**: Improve content quality through evidence-based recommendations
-- **Consistency**: Maintain brand voice across all content automatically
-- **Scale**: Handle larger content volumes without proportional effort increase
-- **Learning**: System improves as it learns your preferences and patterns
-
-**Deep Integration Benefits**:
-- Every section of your CMS has AI insights available
-- Recommendations adapt based on your publish history
-- Performance analytics inform future suggestions
-- Content calendar integration for strategic planning
-- Real-time optimization tips as you write
-
-**Building Better Content Together**:
-The AI assistant doesn't replace human creativity—it amplifies it. Think of it as:
-- A brainstorming partner for ideas and angles
-- An editor suggesting improvements
-- A researcher providing data and insights
-- A strategist analyzing performance patterns
-- A guide on best practices and techniques
-
-**Privacy & Control**:
-All interactions are session-based and don't permanently modify your content without approval. You maintain complete control over what gets published. The AI serves as advisor, not decision-maker.
-
-This integration transforms your blogging from manual work into a collaborative, intelligent process designed to maximize impact and efficiency.`,
- ],
- },
-];
-
-export default async (_model: string, _history: any[], message: string) => {
- const normalized = message?.trim?.() ?? '';
-
- if (!normalized.length) {
- return 'Demo mode: no input received.';
- }
-
- // Find matching response category
- for (const category of demoResponses) {
- for (const keyword of category.keywords) {
- if (normalized.toLowerCase().includes(keyword)) {
- // Pick a random response from the category
- const responses = category.responses;
- const randomIndex = Math.floor(Math.random() * responses.length);
- return responses[randomIndex];
- }
- }
- }
-
- // Default response if no keywords match
- return `I appreciate your question: "${normalized}"
-
-In a production environment, I would provide a detailed, intelligent response powered by Gemini AI. The Autoblog CMS integrates with Google's generative AI to provide:
-
-- **Contextual Writing Assistance**: Tailored suggestions based on your type of blog
-- **Content Strategy Guidance**: Data-driven recommendations for better performance
-- **Editorial Review**: Quality checks for tone, clarity, and completeness
-- **SEO Optimization**: Keyword suggestions and ranking optimization tips
-- **Performance Analysis**: Insights on what's working and what needs improvement
-
-Feel free to ask about content creation, blog strategy, CMS features, SEO optimization, editorial standards, analytics insights, publishing workflows, or anything else related to managing and growing your blog.
-
-In demo mode, I'm providing representative responses to showcase these capabilities. With real Gemini integration, I would understand context, follow conversation history, and provide increasingly personalized guidance based on your unique situation.`;
-};
diff --git a/app/(workspace)/workspace/[workspaceId]/page.tsx b/app/(workspace)/workspace/[workspaceId]/page.tsx
new file mode 100644
index 0000000..6a5bd9a
--- /dev/null
+++ b/app/(workspace)/workspace/[workspaceId]/page.tsx
@@ -0,0 +1,24 @@
+import { headers } from 'next/headers';
+import { redirect } from 'next/navigation';
+
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { requireMembership } from '@/src/platform/auth/session';
+import { AppError } from '@/src/platform/observability/errors';
+import { WorkspaceEditor } from '@/src/ui/patterns/workspace-editor';
+
+export const dynamic = 'force-dynamic';
+
+export default async function WorkspacePage({ params }: Readonly<{ params: Promise<{ workspaceId: string }> }>) {
+ const { workspaceId } = await params;
+ let membership: Awaited>;
+ try {
+ membership = await requireMembership(await headers(), workspaceId);
+ } catch (error) {
+ if (error instanceof AppError && error.code === 'UNAUTHENTICATED') redirect('/sign-in');
+ throw error;
+ }
+ const service = getEditorialService();
+ const posts = await service.list(membership);
+ const selected = posts[0] ? await service.get(membership, posts[0].id) : null;
+ return ;
+}
diff --git a/app/@inset/[section]/[id]/loading.tsx b/app/@inset/[section]/[id]/loading.tsx
deleted file mode 100644
index 49ac50e..0000000
--- a/app/@inset/[section]/[id]/loading.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-import { Skeleton } from '@/ui/skeleton';
-
-export default function Loading() {
- return (
-
- {/* Left panel - Image & metadata */}
-
- {/* Header */}
-
-
-
-
-
- {/* Image preview */}
-
-
- {/* Image upload area */}
-
-
-
-
-
- {/* Metadata fields */}
-
-
-
- {/* Right panel - Content tabs */}
-
- {/* Tab buttons */}
-
-
-
-
-
- {/* Tab content - Editor area */}
-
- {/* Title field */}
-
-
-
-
-
- {/* Description field */}
-
-
-
-
-
- {/* Additional fields */}
-
-
- {/* Rich text editor */}
-
-
-
-
-
-
- {/* Action buttons */}
-
-
-
-
-
-
- );
-}
diff --git a/app/@inset/[section]/[id]/page.tsx b/app/@inset/[section]/[id]/page.tsx
deleted file mode 100644
index fe23bf2..0000000
--- a/app/@inset/[section]/[id]/page.tsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import Client from '@/_client/@inset/( posts ) [ id ]';
-import apiFetch, { ApiRes } from '@/utils/api-fetch';
-
-import revalidate from '@/utils/revalidate';
-
-import type { Post } from '@/types/post';
-
-type Params = { section: string; id: string };
-
-export default async function Page({ params }: { params: Promise }) {
- const { id, section } = await params;
- const api = generateApi({ id, section });
-
- const props: ServerProps = {
- section,
- post: await api.getPost(),
- api,
- };
-
- return ;
-}
-
-export const generateApi = ({ id, section }: Params) => ({
- getPost: async () => {
- 'use server';
-
- const { success, ...rest }: ApiRes = await apiFetch(`/inset/${section}/${id}`, {
- method: 'GET',
- });
-
- if (!success) throw new Error(rest.message);
-
- return rest.data;
- },
- updateImage: async (image: {
- _id: string;
- base64: string;
- display_name: string;
- public_id?: string;
- }) => {
- 'use server';
- image._id ??= id;
-
- const { success, ...rest } = await apiFetch(`/inset/${section}/${id}/image`, {
- method: 'PATCH',
- body: image,
- });
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: [section, id] });
- await revalidate({ kind: 'paths', paths: [`/${section}`, `/${section}/${id}`] });
-
- return rest.data;
- },
- updatePost: async (post: Partial) => {
- 'use server';
-
- const { success, ...rest } = await apiFetch(`/inset/${section}/${post._id}/post`, {
- method: 'PATCH',
- body: post,
- });
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: [section, id] });
- await revalidate({ kind: 'paths', paths: [`/${section}`, `/${section}/${id}`] });
-
- return rest.data;
- },
-});
-
-export interface ServerProps {
- post: Post;
- section: string;
- api: ReturnType;
-}
diff --git a/app/@inset/[section]/loading.tsx b/app/@inset/[section]/loading.tsx
deleted file mode 100644
index 6e438a7..0000000
--- a/app/@inset/[section]/loading.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Skeleton } from '@/ui/skeleton';
-
-export default function Loading() {
- return (
-
- {/* Header section */}
-
-
-
-
-
- {/* Group skeletons */}
-
- {[...Array(3)].map((_, groupIdx) => (
-
- {/* Group header */}
-
-
-
-
-
- {/* Subgroups */}
-
- {[...Array(2)].map((_, sgIdx) => (
-
- {/* Subgroup header */}
-
-
-
-
-
- {/* Posts */}
-
- {[...Array(2)].map((_, pIdx) => (
-
- ))}
-
-
- ))}
-
-
- ))}
-
-
- );
-}
diff --git a/app/@inset/[section]/page.tsx b/app/@inset/[section]/page.tsx
deleted file mode 100644
index 52872a6..0000000
--- a/app/@inset/[section]/page.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-export default async function Page({ params }: { params: Promise<{ section: string }> }) {
- const { section } = await params;
- return (
-
-
- Select the {section} you want to edit
-
-
- );
-}
diff --git a/app/@inset/_components/chat.tsx b/app/@inset/_components/chat.tsx
deleted file mode 100644
index d24b3c9..0000000
--- a/app/@inset/_components/chat.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-'use client';
-
-import { Button } from '@/ui/button';
-
-import { Dispatch, SetStateAction, useEffect, useState } from 'react';
-
-import { ReceiverMessage, SenderMessage } from '../_ui/message';
-import { FiArrowUp } from 'react-icons/fi';
-
-import type { Content } from '@google/generative-ai';
-
-export default ({ onSend }: { onSend: (history: Content[]) => Promise }) => {
- const [input, setInput] = useState('');
- const [writing, setWriting] = useState(false);
- const [timeout, setTimeoutState] = useState();
- const [history, setHistory] = useState([]);
-
- const addMessage = (role: 'user' | 'model', text: string) => {
- if (role === 'user') setInput('');
- setHistory(p => {
- const last = p[p.length - 1];
- console.log('last => ', p, history);
- return last?.role !== role ? [...p, { role, parts: [{ text }] }] : [...p.slice(0, -1), { role, parts: [...last.parts, { text }] }];
- });
- };
-
- const updatedState: (setState: Dispatch>) => Promise = async setState => {
- return new Promise(r => {
- setState(p => {
- r(p);
- return p;
- });
- });
- };
-
- useEffect(() => {
- const history = JSON.parse(sessionStorage.getItem('history'));
- if (history) setHistory(history);
- }, []);
-
- useEffect(() => {
- if (!history || !history.length) return;
- sessionStorage.setItem('history', JSON.stringify(history));
- }, [history]);
-
- return (
-
-
- {history.length === 0 && (
-
-
- ✨
-
-
Autoblog CMS AI Assistant
-
- Get intelligent guidance on content creation, SEO strategy, editorial standards, and blog growth.
-
-
-
Try asking about:
-
- setInput('How do I create compelling blog content?')}
- className="text-left px-3 py-2 rounded-md bg-foreground/5 hover:bg-foreground/10 transition-colors text-sm text-foreground/70 hover:text-foreground">
- How do I create compelling blog content?
-
- setInput('What are SEO best practices for my blog?')}
- className="text-left px-3 py-2 rounded-md bg-foreground/5 hover:bg-foreground/10 transition-colors text-sm text-foreground/70 hover:text-foreground">
- What are SEO best practices for my blog?
-
- setInput('How to maintain editorial quality standards?')}
- className="text-left px-3 py-2 rounded-md bg-foreground/5 hover:bg-foreground/10 transition-colors text-sm text-foreground/70 hover:text-foreground">
- How to maintain editorial quality standards?
-
- setInput('How can I track blog performance metrics?')}
- className="text-left px-3 py-2 rounded-md bg-foreground/5 hover:bg-foreground/10 transition-colors text-sm text-foreground/70 hover:text-foreground">
- How can I track blog performance metrics?
-
-
-
-
- )}
- {history.map(({ role, parts }, index) =>
- parts.map(({ text: message }, i) => {
- return role === 'user' ?
-
{message}
- :
{message} ;
- }),
- )}
-
-
e.preventDefault()}
- className="absolute flex flex-col h-fit max-h-40 w-full bottom-0 inset-x-auto bg-sidebar-accent !text-foreground rounded-md">
- {
- setInput(e.target.value);
- setWriting(true);
- clearTimeout(timeout);
- setTimeoutState(setTimeout(() => setWriting(false), 2000));
- }}
- onKeyDown={async e => {
- if (e.key !== 'Enter' || e.shiftKey) return;
- if (!input || !input.trim().length) return;
-
- e.preventDefault();
- addMessage('user', input);
-
- await new Promise(r => setTimeout(r, 2500));
-
- const updatedWriting = await updatedState(setWriting);
-
- if (updatedWriting) return;
- const updatedMessages = await updatedState(setHistory);
-
- const response = await onSend(updatedMessages);
- addMessage('model', response);
- }}
- />
-
-
- {
- if (!input || !input.trim().length) return;
-
- addMessage('user', input);
-
- await new Promise(r => setTimeout(r, 2500));
-
- const updatedWriting = await updatedState(setWriting);
- if (updatedWriting) return;
- const updatedMessages = await updatedState(setHistory);
-
- const response = await onSend(updatedMessages);
- addMessage('model', response);
- }}>
-
-
-
-
-
-
- );
-};
diff --git a/app/@inset/_ui/message.tsx b/app/@inset/_ui/message.tsx
deleted file mode 100644
index 0d98646..0000000
--- a/app/@inset/_ui/message.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useTypewriter } from '@/hooks/use-typewriter';
-import Image from 'next/image';
-import Markdown from 'react-markdown';
-
-export const SenderMessage = ({ children }: { children: string }) => {
- return (
- {children}
- );
-};
-
-export const ReceiverMessage = ({ children }: { children: string }) => {
- const text = useTypewriter(children, 0, 2);
- const isTyping = text.length < children.length;
-
- return (
-
-
-
-
-
-
-
,
- li: ({ node, ...props }) => ,
- }}
- className="self-start mr-auto size-fit bg-transparent text-foreground [&_*]:!select-text">
- {text}
-
- {isTyping && (
-
- )}
-
-
- );
-};
diff --git a/app/@inset/advisory/loading.tsx b/app/@inset/advisory/loading.tsx
deleted file mode 100644
index 594313d..0000000
--- a/app/@inset/advisory/loading.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { Skeleton } from '@/ui/skeleton';
-
-export default function Loading() {
- return (
-
- {/* Left panel - Image */}
-
- {/* Header */}
-
-
-
-
-
- {/* Image preview */}
-
-
- {/* Upload area */}
-
-
-
-
-
-
- {/* Right panel - Content */}
-
- {/* About section */}
-
-
-
-
-
- {/* Services section */}
-
-
-
-
-
- {/* Submit button */}
-
-
-
- );
-}
diff --git a/app/@inset/advisory/page.tsx b/app/@inset/advisory/page.tsx
deleted file mode 100644
index b257284..0000000
--- a/app/@inset/advisory/page.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import Client from '@/_client/@inset/( advisory )';
-import revalidate from '@/utils/revalidate';
-import apiFetch from '@/utils/api-fetch';
-
-import type { ApiRes } from '@/utils/api-fetch';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- const data = await api.getAdvisory();
-
- const props: ServerProps = { data, api };
-
- return ;
-}
-
-export const api = {
- getAdvisory: async () => {
- 'use server';
-
- const { success, ...rest }: ApiRes = await apiFetch(
- '/inset/advisory',
- {
- method: 'GET',
- }
- );
- if (!success) throw new Error(rest.message);
-
- return rest.data;
- },
- updateAdvisory: async (body: Omit, 'image'>) => {
- 'use server';
-
- const { success, ...rest } = await apiFetch('/inset/advisory', {
- method: 'PATCH',
- body,
- });
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: ['advisory'] });
- await revalidate({ kind: 'paths', paths: ['/advisory'] });
-
- return rest.data;
- },
- uploadImage: async (image: { base64: string; public_id?: string; display_name?: string }) => {
- 'use server';
-
- const { success, ...rest } = await apiFetch('/inset/advisory/image', {
- method: 'PATCH',
- body: image,
- });
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: ['advisory'] });
- await revalidate({ kind: 'paths', paths: ['/advisory'] });
-
- return rest.data;
- },
-};
-
-export type ServerProps = {
- api: typeof api;
- data: {
- id: string;
- about: string;
- services: string;
- image: { public_id: string; display_name: string };
- };
-};
diff --git a/app/@inset/default.tsx b/app/@inset/default.tsx
deleted file mode 100644
index 461f67a..0000000
--- a/app/@inset/default.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export default () => null;
diff --git a/app/@inset/error.tsx b/app/@inset/error.tsx
deleted file mode 100644
index 8a18f17..0000000
--- a/app/@inset/error.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Button } from '@/ui/button';
-
-export default ({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) => {
- return (
-
-
Something went wrong!
- reset()
- }>
- Try again
-
-
- );
-};
diff --git a/app/@inset/home/loading.tsx b/app/@inset/home/loading.tsx
deleted file mode 100644
index f1658ff..0000000
--- a/app/@inset/home/loading.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Skeleton } from '@/ui/skeleton';
-
-export default function Loading() {
- return (
-
- {/* Header */}
-
-
-
-
- {/* Grid of image sections */}
-
- {[...Array(4)].map((_, i) => (
-
- {/* Label skeleton */}
-
-
- {/* Image placeholder */}
-
-
- ))}
-
-
- );
-}
diff --git a/app/@inset/home/page.tsx b/app/@inset/home/page.tsx
deleted file mode 100644
index 1169c46..0000000
--- a/app/@inset/home/page.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import Client from '@/_client/@inset/( home )';
-import apiFetch, { ApiRes } from '@/utils/api-fetch';
-import revalidate from '@/utils/revalidate';
-
-export const dynamic = 'force-dynamic';
-
-export default async function Page() {
- const images = await api.getHome();
- const props: ServerProps = { images, api };
-
- return ;
-}
-
-export const api = {
- getHome: async () => {
- 'use server';
- const { success, ...rest }: ApiRes = await apiFetch(
- '/inset/home',
- {
- method: 'GET',
- }
- );
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: ['home'] });
- await revalidate({ kind: 'paths', paths: ['/'] });
-
- return rest.data;
- },
- uploadImage: async ({
- section,
- ...image
- }: ServerProps['images'][number] & { base64: string }) => {
- 'use server';
-
- const { success, ...rest } = await apiFetch(`/inset/home/${section}/image`, {
- method: 'PATCH',
- body: { display_name: 'main', ...image },
- });
-
- if (!success) throw new Error(rest.message);
-
- await revalidate({ kind: 'tags', tags: ['home'] });
- await revalidate({ kind: 'paths', paths: ['/'] });
-
- return rest.data;
- },
-};
-
-export type ServerProps = {
- api: typeof api;
- images: { section: string; public_id: string; display_name: string }[];
-};
diff --git a/app/@inset/layout.tsx b/app/@inset/layout.tsx
deleted file mode 100644
index e6ab738..0000000
--- a/app/@inset/layout.tsx
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Separator } from '@/ui/separator';
-import BreadcrumbNav from '@/components/BreadCrumbNav';
-import { SidebarInset, SidebarRail, SidebarTrigger } from '@/ui/sidebar';
-
-import type { PropsWithChildren } from 'react';
-import ThemeSwitcher from '@/components/theme-switcher';
-import InnerChat, { InnerChatTrigger } from '@/components/chat/inner';
-import { Sheet } from '@/ui/sheet';
-
-export default function Layout({ children }: PropsWithChildren) {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
- {children}
-
-
-
-
- );
-}
diff --git a/app/@inset/loading.tsx b/app/@inset/loading.tsx
deleted file mode 100644
index 8f0b19b..0000000
--- a/app/@inset/loading.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { Skeleton } from '@/ui/skeleton';
-
-export default function Loading() {
- return (
-
-
- {/* Large skeleton for main content area */}
-
-
-
-
-
-
-
-
- );
-}
diff --git a/app/@inset/page.tsx b/app/@inset/page.tsx
deleted file mode 100644
index 972a041..0000000
--- a/app/@inset/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function Page() {
- return (
-
-
Seleziona la pagina da editare
-
- );
-}
diff --git a/app/@sidebar/[section]/[id]/page.tsx b/app/@sidebar/[section]/[id]/page.tsx
deleted file mode 100644
index 2ecfb26..0000000
--- a/app/@sidebar/[section]/[id]/page.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import { Sidebar } from '@/_client/@sidebar/( posts ) [ id ]';
-import { generateApi } from '../page';
-
-import type { Group } from '@/types/group';
-import type { Post } from '@/types/post';
-
-type Params = { section: string; id: string };
-
-export default async function Page({ params }: { params: Promise }) {
- const { section } = await params;
- const api = generateApi({ section });
-
- const groups = await api.getGroups();
- const props: ServerProps = {
- section,
- groups,
- api,
- };
-
- return ;
-}
-
-export interface ServerProps {
- section: string;
- groups: Group[];
- api: ReturnType;
-}
diff --git a/app/@sidebar/[section]/page.tsx b/app/@sidebar/[section]/page.tsx
deleted file mode 100644
index 6e5c941..0000000
--- a/app/@sidebar/[section]/page.tsx
+++ /dev/null
@@ -1,131 +0,0 @@
-import { Sidebar } from '@/_client/@sidebar/( posts )';
-import revalidate from '@/utils/revalidate';
-import apiFetch from '@/utils/api-fetch';
-
-import type { ApiRes } from '@/utils/api-fetch';
-import type { Group } from '@/types/group';
-import type { Post } from '@/types/post';
-
-type Params = { section: string };
-
-export default async function Page({ params }: { params: Promise }) {
- const { section } = await params;
-
- const api = generateApi({ section });
-
- const groups = await api.getGroups();
- const props: ServerProps = {
- section,
- groups,
- api,
- };
-
- return ;
-}
-
-export const generateApi = ({ section }: Params) => ({
- getGroups: async () => {
- 'use server';
- const { success, ...rest }: ApiRes[]> = await apiFetch(
- `/sidebar/${section}`,
- {
- method: 'GET',
- }
- );
-
- if (!success) throw new Error(rest.message);
-
- return rest.data;
- },
- createGroup: async (name: string) => {
- 'use server';
-
- const { data } = await apiFetch(`/sidebar/${section}/group/id`, {
- method: 'POST',
- body: { name },
- });
-
- return data;
- },
- createSubGroup: async (body: { name: string; group: { id: string; name: string } }) => {
- 'use server';
- console.log('body', body);
- const { data } = await apiFetch(`/sidebar/${section}/subgroup/id`, {
- method: 'POST',
- body,
- });
-
- return data;
- },
- createPost: async (body: {
- name: string;
- group: { id: string; name: string };
- sub_group: { id: string; name: string };
- }) => {
- 'use server';
-
- const { data } = await apiFetch(`/sidebar/${section}/id`, {
- method: 'POST',
- body,
- });
-
- return data;
- },
-
- renameGroup: async (body: { id: string; name: string }) => {
- 'use server';
-
- await apiFetch(`/sidebar/${section}/group/${body.id}`, { method: 'PATCH', body });
- },
- renameSubGroup: async (body: { id: string; name: string }) => {
- 'use server';
-
- await apiFetch(`/sidebar/${section}/subgroup/${body.id}`, {
- method: 'PATCH',
- body,
- });
- },
- renamePost: async (body: { id: string; name: string }) => {
- 'use server';
-
- await apiFetch(`/sidebar/${section}/${body.id}`, { method: 'PATCH', body });
- },
-
- deleteGroup: async (_id: string) => {
- 'use server';
-
- await apiFetch(`/sidebar/${section}/group/${_id}`, {
- method: 'DELETE',
- body: { id: _id },
- });
- },
- deleteSubGroup: async (_id: string) => {
- 'use server';
-
- await apiFetch(`/sidebar/${section}/subgroup/${_id}`, {
- method: 'DELETE',
- body: { id: _id },
- });
- },
- deletePost: async (_id: string) => {
- 'use server';
-
- const { success, ...rest } = await apiFetch(`/sidebar/${section}/${_id}`, {
- method: 'DELETE',
- body: { id: _id },
- });
-
- if (!success) throw new Error(rest.message);
-
- revalidate({ kind: 'path', path: `/${section}` });
- revalidate({ kind: 'path', path: `/${section}/${_id}` });
-
- return rest.data;
- },
-});
-
-export interface ServerProps {
- section: string;
- groups: Group[];
- api: ReturnType;
-}
diff --git a/app/@sidebar/default.tsx b/app/@sidebar/default.tsx
deleted file mode 100644
index 04d20f3..0000000
--- a/app/@sidebar/default.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import EnvSwitcher from '@/components/env-switcher';
-import { Sidebar, SidebarHeader } from '@/ui/sidebar';
-
-import { Separator } from '@/ui/separator';
-
-export default () => {
- return (
-
-
-
-
-
-
-
-
-
-
- );
-};
-
-const envs = [
- {
- name: 'Home',
- },
- {
- name: 'Advisory',
- },
- {
- name: 'Journals',
- },
- {
- name: 'Exhibitions',
- },
- {
- name: 'Lifestyle',
- },
-];
diff --git a/app/@sidebar/error.tsx b/app/@sidebar/error.tsx
deleted file mode 100644
index 8a18f17..0000000
--- a/app/@sidebar/error.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-'use client';
-
-import { Button } from '@/ui/button';
-
-export default ({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) => {
- return (
-
-
Something went wrong!
- reset()
- }>
- Try again
-
-
- );
-};
diff --git a/app/@sidebar/layout.tsx b/app/@sidebar/layout.tsx
deleted file mode 100644
index f62ed8a..0000000
--- a/app/@sidebar/layout.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import EnvSwitcher from '@/components/env-switcher';
-import { Sidebar, SidebarHeader } from '@/ui/sidebar';
-
-import type { PropsWithChildren } from 'react';
-import { Separator } from '@/ui/separator';
-import { sections } from '@/constants/sections';
-
-export default ({ children }: PropsWithChildren) => {
- return (
-
-
-
-
-
-
-
-
-
- {children}
-
- );
-};
-
-const envs = [
- {
- name: 'Home',
- },
-
- ...sections.map(section => ({
- name: section.charAt(0).toUpperCase() + section.slice(1),
- })),
-];
diff --git a/app/@sidebar/page.tsx b/app/@sidebar/page.tsx
deleted file mode 100644
index ec30acc..0000000
--- a/app/@sidebar/page.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export default function Page() {
- return <>>;
-}
diff --git a/app/api/auth/[...all]/route.ts b/app/api/auth/[...all]/route.ts
new file mode 100644
index 0000000..2ecc0e7
--- /dev/null
+++ b/app/api/auth/[...all]/route.ts
@@ -0,0 +1,50 @@
+import { eq } from 'drizzle-orm';
+import { toNextJsHandler } from 'better-auth/next-js';
+
+import { auth } from '@/src/platform/auth/auth';
+import { assertRuntimeConfiguration } from '@/src/platform/config/env';
+import { getDatabase } from '@/src/platform/db/client';
+import { auditEvents, memberships } from '@/src/platform/db/schema';
+
+const handlers = toNextJsHandler(auth);
+
+async function recordIdentityEvent(userId: string, action: 'identity.login' | 'identity.logout', requestId: string): Promise {
+ const workspaceRows = await getDatabase().db.select({ workspaceId: memberships.workspaceId }).from(memberships).where(eq(memberships.userId, userId));
+ if (workspaceRows.length === 0) return;
+ await getDatabase().db.insert(auditEvents).values(workspaceRows.map(({ workspaceId }) => ({
+ id: crypto.randomUUID(), workspaceId, actorId: userId, action,
+ targetType: 'session', targetId: null, requestId, metadata: {}, createdAt: new Date(),
+ })));
+}
+
+async function handle(request: Request, method: 'GET' | 'POST'): Promise {
+ const requestId = request.headers.get('x-request-id')?.slice(0, 100) || crypto.randomUUID();
+ try {
+ assertRuntimeConfiguration();
+ } catch {
+ return Response.json({ error: { code: 'PROVIDER_UNAVAILABLE', message: 'Authentication is not configured.', requestId } }, { status: 503, headers: { 'x-request-id': requestId } });
+ }
+
+ const path = new URL(request.url).pathname;
+ const beforeSession = path.endsWith('/sign-out') ? await auth.api.getSession({ headers: request.headers }) : null;
+ const response = await handlers[method](request);
+ response.headers.set('x-request-id', requestId);
+ if (!response.ok) {
+ const code = response.status === 429 ? 'RATE_LIMITED' : response.status === 401 ? 'UNAUTHENTICATED' : response.status < 500 ? 'VALIDATION_FAILED' : 'INTERNAL_FAILURE';
+ const message = code === 'RATE_LIMITED' ? 'Too many authentication attempts. Try again later.' : code === 'UNAUTHENTICATED' ? 'The supplied credentials are invalid.' : code === 'VALIDATION_FAILED' ? 'The authentication request is invalid.' : 'Authentication could not be completed.';
+ return Response.json({ code, message, requestId }, { status: response.status, headers: { 'x-request-id': requestId } });
+ }
+
+ if (response.ok && path.endsWith('/sign-in/email')) {
+ const sessionCookie = response.headers.get('set-cookie')?.split(';', 1)[0];
+ if (sessionCookie) {
+ const session = await auth.api.getSession({ headers: new Headers({ cookie: sessionCookie }) });
+ if (session) await recordIdentityEvent(session.user.id, 'identity.login', requestId);
+ }
+ }
+ if (response.ok && beforeSession && path.endsWith('/sign-out')) await recordIdentityEvent(beforeSession.user.id, 'identity.logout', requestId);
+ return response;
+}
+
+export const GET = (request: Request) => handle(request, 'GET');
+export const POST = (request: Request) => handle(request, 'POST');
diff --git a/app/api/chat/route.ts b/app/api/chat/route.ts
deleted file mode 100644
index 66bbce4..0000000
--- a/app/api/chat/route.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { GoogleGenerativeAI } from '@google/generative-ai';
-import _try from '@/utils/_try';
-
-export const POST = async (req: Request) =>
- await _try(async () => {
- const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
- const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
-
- const { prompt } = await req.json();
-
- const result = await model.generateContent(prompt);
- return result.response.text();
- });
diff --git a/app/api/health/route.ts b/app/api/health/route.ts
new file mode 100644
index 0000000..481695d
--- /dev/null
+++ b/app/api/health/route.ts
@@ -0,0 +1,11 @@
+import { getDatabase } from '@/src/platform/db/client';
+
+export async function GET(): Promise {
+ const requestId = crypto.randomUUID();
+ try {
+ await getDatabase().client.execute('SELECT 1 AS ready');
+ return Response.json({ status: 'ready', requestId }, { headers: { 'cache-control': 'no-store', 'x-request-id': requestId } });
+ } catch {
+ return Response.json({ status: 'unavailable', requestId }, { status: 503, headers: { 'cache-control': 'no-store', 'x-request-id': requestId } });
+ }
+}
diff --git a/app/api/inset/[section]/[id]/image/route.ts b/app/api/inset/[section]/[id]/image/route.ts
deleted file mode 100644
index a6ae118..0000000
--- a/app/api/inset/[section]/[id]/image/route.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import _try from '@/utils/_try';
-import cloudinary from '@/lib/cloudinary';
-import { env } from 'node:process';
-
-// export const POST = async (req: Request) =>
-// await _try(async () => {
-// const doc = await req.json();
-// const conn = await db();
-// const coll = conn.collection(section);
-// return await coll.insertOne(doc);
-// });
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- const { _id, base64, public_id, display_name } = await req.json();
-
- if (public_id) await cloudinary.uploader.destroy(public_id);
-
- const res = await cloudinary.uploader.upload(base64, {
- folder: `${env.CLOUDINARY_WEBSITE_FOLDER}/${section}/${_id}`,
- public_id,
- display_name,
- });
-
- console.log('res', res);
-
- return res;
- });
diff --git a/app/api/inset/[section]/[id]/post/route.ts b/app/api/inset/[section]/[id]/post/route.ts
deleted file mode 100644
index 671bb15..0000000
--- a/app/api/inset/[section]/[id]/post/route.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import { post as ObjectIdCheck } from '@/utils/objectId_check';
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- const { _id, ...body } = await req.json();
-
- const conn = await db();
- const coll = conn.collection(section);
-
- return await coll.updateOne(
- { _id: new Types.ObjectId(_id) },
- { $set: ObjectIdCheck(body) }
- );
- });
diff --git a/app/api/inset/[section]/[id]/route.ts b/app/api/inset/[section]/[id]/route.ts
deleted file mode 100644
index c257ae5..0000000
--- a/app/api/inset/[section]/[id]/route.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import cloudinary from '@/lib/cloudinary';
-import { env } from 'node:process';
-
-export const GET = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { id, section } = await params;
-
- const conn = await db();
- const coll = conn.collection(section);
-
- const {
- resources: [image],
- } = await cloudinary.search
- .expression(`folder:${env.CLOUDINARY_WEBSITE_FOLDER}/${section}/${id}`)
- .max_results(1)
- // Filtra per cartella
- .execute();
- const post = await coll.findOne({ _id: new Types.ObjectId(id) });
-
- return { ...post, image };
- });
diff --git a/app/api/inset/advisory/image/route.ts b/app/api/inset/advisory/image/route.ts
deleted file mode 100644
index 5cf4656..0000000
--- a/app/api/inset/advisory/image/route.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import _try from '@/utils/_try';
-import cloudinary from '@/lib/cloudinary';
-import { env } from 'node:process';
-
-// export const POST = async (req: Request) =>
-// await _try(async () => {
-// const doc = await req.json();
-// const conn = await db();
-// const coll = conn.collection(section);
-// return await coll.insertOne(doc);
-// });
-
-export const PATCH = async (req: Request) =>
- await _try(async () => {
- const { base64, public_id, display_name } = await req.json();
-
- if (public_id) await cloudinary.uploader.destroy(public_id);
-
- return await cloudinary.uploader.upload(base64, {
- folder: `${env.CLOUDINARY_WEBSITE_FOLDER}/advisory`,
- public_id,
- display_name: display_name ?? 'main',
- });
- });
diff --git a/app/api/inset/advisory/route.ts b/app/api/inset/advisory/route.ts
deleted file mode 100644
index 4593f15..0000000
--- a/app/api/inset/advisory/route.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import cloudinary from '@/lib/cloudinary';
-import { env } from 'node:process';
-
-export const GET = async () =>
- await _try(async () => {
- const conn = await db();
- const doc = await conn.collection('advisory').findOne(
- {},
- {
- projection: {
- _id: 0,
- id: { $toString: '$_id' },
- about: 1,
- services: 1,
- },
- }
- );
-
- const { resources } = await cloudinary.search
- .expression(`folder:${env.CLOUDINARY_WEBSITE_FOLDER}/advisory`) // Filtra per cartella
- .execute();
-
- return { ...doc, image: resources[0] };
- });
-
-export const PATCH = async (req: Request) =>
- await _try(async () => {
- const { id, about, services } = await req.json();
-
- const conn = await db();
- const coll = conn.collection('advisory');
- return await coll.updateOne(
- { _id: new Types.ObjectId(id) },
- { $set: { about, services } },
- { upsert: true }
- );
- });
diff --git a/app/api/inset/home/[section]/image/route.ts b/app/api/inset/home/[section]/image/route.ts
deleted file mode 100644
index 51da132..0000000
--- a/app/api/inset/home/[section]/image/route.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import _try from '@/utils/_try';
-import cloudinary from '@/lib/cloudinary';
-import { env } from 'node:process';
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- const { base64, public_id, display_name } = await req.json();
-
- if (public_id) await cloudinary.uploader.destroy(public_id);
-
- return await cloudinary.uploader.upload(base64, {
- folder: `${env.CLOUDINARY_WEBSITE_FOLDER}/home/${section}`,
- public_id,
- display_name: display_name ?? 'main',
- });
- });
diff --git a/app/api/inset/home/route.ts b/app/api/inset/home/route.ts
deleted file mode 100644
index e79db43..0000000
--- a/app/api/inset/home/route.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import _try from '@/utils/_try';
-import cloudinary from '@/lib/cloudinary';
-import { sections } from '@/constants/sections';
-import { env } from 'node:process';
-
-export const GET = async () =>
- await _try(async () => {
- return await Promise.all(
- sections.map(async section => {
- const {
- resources: [image],
- } = await cloudinary.search
- .expression(
- `folder:${env.CLOUDINARY_WEBSITE_FOLDER}/home/${section}`
- )
- .max_results(1) // Filtra per cartella
- .execute();
-
- return { section, ...image };
- })
- );
- });
diff --git a/app/api/jobs/run/route.ts b/app/api/jobs/run/route.ts
new file mode 100644
index 0000000..78df21a
--- /dev/null
+++ b/app/api/jobs/run/route.ts
@@ -0,0 +1,16 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { MediaCleanupWorker } from '@/src/modules/media/cleanup-worker';
+import { DatabaseMediaProvider } from '@/src/modules/media/database-provider';
+import { authorizeJobRunner } from '@/src/platform/auth/job-runner';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+import { getDatabase } from '@/src/platform/db/client';
+
+export async function POST(request: Request): Promise {
+ return withApi(async () => {
+ await authorizeJobRunner(request);
+ const publication = await getEditorialService().runDueJobs();
+ const database = getDatabase();
+ const media = await new MediaCleanupWorker(database, new DatabaseMediaProvider(database)).run();
+ return dataResponse({ publication, media });
+ })(request);
+}
diff --git a/app/api/sidebar/[section]/[id]/route.ts b/app/api/sidebar/[section]/[id]/route.ts
deleted file mode 100644
index ea4f776..0000000
--- a/app/api/sidebar/[section]/[id]/route.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import { sections } from '@/constants/sections';
-import postInitTemplate from '@/constants/post-init-template';
-import { post as ObjectIdCheck } from '@/utils/objectId_check';
-
-export const POST = async (
- req: Request,
- { params }: { params: Promise<{ section: string; id: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
- const { name, group, sub_group } = await req.json();
- console.log('body', { name, group, sub_group });
-
- const conn = await db();
- const coll = conn.collection(section);
- console.log('group', group, sub_group);
- const template = {
- group: {
- name: group.name || 'Group',
- _id: new Types.ObjectId(group.id),
- },
- sub_group: {
- name: sub_group.name || 'Sub group',
- _id: new Types.ObjectId(sub_group.id),
- },
- ...postInitTemplate[section],
- name: name || postInitTemplate[section].name,
- };
- console.log('template', template);
-
- return await coll.insertOne(ObjectIdCheck(template));
- });
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ section: string; id: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const { id, ...body } = await req.json();
- const conn = await db();
- const coll = conn.collection(section);
-
- delete body._id;
- return await coll.updateOne(
- { _id: new Types.ObjectId(id) },
- { $set: ObjectIdCheck(body) }
- );
- });
-
-export const DELETE = async (
- req: Request,
- { params }: { params: Promise<{ section: string; id: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const { id } = await req.json();
- if (!id) throw new Error('Invalid id');
-
- const conn = await db();
- const exercises = conn.collection(section);
-
- return await exercises.deleteMany({ _id: new Types.ObjectId(id) });
- });
diff --git a/app/api/sidebar/[section]/group/[id]/route.ts b/app/api/sidebar/[section]/group/[id]/route.ts
deleted file mode 100644
index be59438..0000000
--- a/app/api/sidebar/[section]/group/[id]/route.ts
+++ /dev/null
@@ -1,90 +0,0 @@
-import { env } from 'node:process';
-
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import cloudinary from '@/lib/cloudinary';
-import { sections } from '@/constants/sections';
-import postInitTemplate from '@/constants/post-init-template';
-import { post as ObjectIdCheck } from '@/utils/objectId_check';
-
-export const POST = async (req: Request, { params }: { params: Promise<{ section: string }> }) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const { name } = await req.json();
-
- const conn = await db();
- const coll = conn.collection(section);
-
- const template = {
- group: {
- name: name || 'Group',
- _id: new Types.ObjectId(),
- },
- sub_group: {
- name: 'Sub group',
- _id: new Types.ObjectId(),
- },
- ...postInitTemplate[section],
- };
-
- return await coll.insertOne(ObjectIdCheck(template));
- });
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
- const { id, name } = await req.json();
-
- const conn = await db();
- const coll = conn.collection(section);
-
- return await coll.updateMany(
- { 'group._id': new Types.ObjectId(id) },
- { $set: { 'group.name': name || '' } }
- );
- });
-
-export const DELETE = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const { id } = await req.json();
- if (!id) throw new Error('Invalid id');
-
- const conn = await db();
- const coll = conn.collection(section);
- const docs = await coll.find({ 'group._id': new Types.ObjectId(id) }).toArray();
-
- const { folders } = await cloudinary.api.sub_folders(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/${section}`
- );
-
- return await Promise.all(
- docs.map(async ({ _id }) => {
- const postId = _id.toString();
- if (folders.some(({ name }) => name === postId)) {
- await cloudinary.api.delete_resources_by_prefix(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/${section}/${postId}/`,
- {
- all: true,
- }
- );
- await cloudinary.api.delete_folder(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/${section}/${postId}`
- );
- }
- return await coll.deleteOne({ _id });
- })
- );
- });
diff --git a/app/api/sidebar/[section]/route.ts b/app/api/sidebar/[section]/route.ts
deleted file mode 100644
index f4d14b6..0000000
--- a/app/api/sidebar/[section]/route.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import { sections } from '@/constants/sections';
-
-export const GET = async (req: Request, { params }: { params: Promise<{ section: string }> }) =>
- await _try(async () => {
- const { section } = await params;
- console.log('from server api section', section);
- if (!sections.includes(section)) return;
-
- const conn = await db();
- const coll = conn.collection(section);
-
- const groups = coll.aggregate([
- {
- $group: {
- _id: {
- group: '$group._id',
- sub_group: '$sub_group._id',
- }, // Raggruppa per group e sub_group
- group: { $first: '$group' },
- sub_group: { $first: '$sub_group' }, // Usa il valore di sub_group come titolo
- items: {
- $push: '$$ROOT',
- },
- },
- },
- {
- $group: {
- _id: '$_id.group', // Raggruppa per group
- id: { $first: '$group._id' },
- name: { $first: '$group.name' },
- sub_groups: {
- $push: {
- id: '$sub_group._id',
- name: '$sub_group.name',
- items: {
- $map: {
- input: '$items',
- as: 'item',
- in: {
- $mergeObjects: [
- {
- id: {
- $toString: '$$item._id',
- },
- },
- {
- $arrayToObject:
- {
- $objectToArray:
- '$$item',
- },
- },
- ],
- },
- },
- },
- },
- },
- },
- },
- {
- // Aggiungi la fase di ordinamento per sub_groups
- $addFields: {
- sub_groups: {
- $sortArray: {
- input: '$sub_groups',
- sortBy: { id: 1 }, // Ordina per id (o _id) in ordine crescente
- },
- },
- },
- },
- {
- $project: {
- _id: 0, // Esclude _id
- },
- },
- ]);
- if (!groups) throw new Error('No groups found');
- return await groups.toArray();
- });
-
-export const PATCH = async (req: Request, { params }: { params: Promise<{ section: string }> }) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const conn = await db();
- const coll = conn.collection(section);
-
- const { id, ...body } = await req.json();
-
- return await coll.updateMany(
- { _id: new Types.ObjectId(body.id) },
- { $set: body },
- { upsert: true }
- );
- });
diff --git a/app/api/sidebar/[section]/subgroup/[id]/route.ts b/app/api/sidebar/[section]/subgroup/[id]/route.ts
deleted file mode 100644
index 97d6a1c..0000000
--- a/app/api/sidebar/[section]/subgroup/[id]/route.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import postInitTemplate from '@/constants/post-init-template';
-import { sections } from '@/constants/sections';
-import db from '@/lib/db';
-import _try from '@/utils/_try';
-import { Types } from 'mongoose';
-import { env } from 'node:process';
-
-export const POST = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const conn = await db();
- const coll = conn.collection(section);
- const { group, name } = await req.json();
-
- const template = {
- group: { name: group.name, _id: new Types.ObjectId(group.id) },
- sub_group: {
- name: name ?? postInitTemplate[section].name,
- _id: new Types.ObjectId(),
- },
- ...postInitTemplate[section],
- };
- const { insertedId } = await coll.insertOne(template);
-
- return {
- id: insertedId,
- name: template.sub_group.name,
- items: [{ id: insertedId.toString(), ...postInitTemplate[section] }],
- };
- });
-
-export const PATCH = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { id, section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const conn = await db();
- const coll = conn.collection(section);
- const { name } = await req.json();
-
- return await coll.updateMany(
- { 'sub_group._id': new Types.ObjectId(id) },
- { $set: { 'sub_group.name': name || '' } }
- );
- });
-
-export const DELETE = async (
- req: Request,
- { params }: { params: Promise<{ id: string; section: string }> }
-) =>
- await _try(async () => {
- const { id, section } = await params;
- if (!sections.includes(section)) throw new Error('Section not found');
-
- const conn = await db();
- const coll = conn.collection(section);
-
- const docs = await coll.find({ 'sub_group._id': new Types.ObjectId(id) }).toArray();
- const { folders } = await cloudinary.api.sub_folders(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/journals`
- );
-
- return await Promise.all(
- docs.map(async ({ _id }) => {
- const postId = _id.toString();
- if (folders.some(({ name }) => name === postId)) {
- await cloudinary.api.delete_resources_by_prefix(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/journals/${postId}/`,
- {
- all: true,
- }
- );
- await cloudinary.api.delete_folder(
- `${env.CLOUDINARY_WEBSITE_FOLDER}/journals/${postId}`
- );
- }
- return await coll.deleteOne({ _id });
- })
- );
- });
diff --git a/app/api/workspaces/[workspaceId]/ai/suggest/route.ts b/app/api/workspaces/[workspaceId]/ai/suggest/route.ts
new file mode 100644
index 0000000..025f1b3
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/ai/suggest/route.ts
@@ -0,0 +1,15 @@
+import { getAIService } from '@/src/modules/ai/service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string }> };
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ return dataResponse(await getAIService().suggest(membership, await request.json(), requestId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/demo/reset/route.ts b/app/api/workspaces/[workspaceId]/demo/reset/route.ts
new file mode 100644
index 0000000..bf2cb20
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/demo/reset/route.ts
@@ -0,0 +1,15 @@
+import { getDemoService } from '@/src/modules/identity/demo-service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string }> };
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ return dataResponse(await getDemoService().reset(membership, await request.json(), requestId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/media/[assetId]/route.ts b/app/api/workspaces/[workspaceId]/media/[assetId]/route.ts
new file mode 100644
index 0000000..f11753f
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/media/[assetId]/route.ts
@@ -0,0 +1,27 @@
+import { getMediaService } from '@/src/modules/media/service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string; assetId: string }> };
+
+export async function GET(request: Request, context: RouteContext): Promise {
+ const { workspaceId, assetId } = await context.params;
+ return withApi(async () => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ const { asset, data } = await getMediaService().read(membership, assetId);
+ return new Response(new Uint8Array(data), {
+ headers: { 'content-type': asset.mimeType, 'content-length': String(asset.byteSize), 'cache-control': 'private, max-age=300', 'x-content-type-options': 'nosniff' },
+ });
+ })(request);
+}
+
+export async function DELETE(request: Request, context: RouteContext): Promise {
+ const { workspaceId, assetId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ await getMediaService().delete(membership, assetId, requestId);
+ return dataResponse({ deleted: true });
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/media/route.ts b/app/api/workspaces/[workspaceId]/media/route.ts
new file mode 100644
index 0000000..ad08a97
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/media/route.ts
@@ -0,0 +1,54 @@
+import { getMediaService } from '@/src/modules/media/service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { getEnvironment } from '@/src/platform/config/env';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+import { AppError } from '@/src/platform/observability/errors';
+
+type RouteContext = { params: Promise<{ workspaceId: string }> };
+
+async function boundedFormData(request: Request, maxBytes: number): Promise {
+ const limit = maxBytes + 256 * 1024;
+ const declared = Number(request.headers.get('content-length') ?? '0');
+ if (declared > limit) throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'size' });
+ const reader = request.body?.getReader();
+ if (!reader) throw new AppError('VALIDATION_FAILED');
+ const chunks: Uint8Array[] = [];
+ let received = 0;
+ while (true) {
+ const { done, value } = await reader.read();
+ if (done) break;
+ received += value.byteLength;
+ if (received > limit) { await reader.cancel(); throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'size' }); }
+ chunks.push(value);
+ }
+ const body = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
+ return new Request(request.url, { method: 'POST', headers: request.headers, body }).formData();
+}
+
+export async function GET(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async () => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ const postId = new URL(request.url).searchParams.get('postId');
+ if (!postId) throw new AppError('VALIDATION_FAILED', { field: 'postId' });
+ return dataResponse(await getMediaService().list(membership, postId));
+ })(request);
+}
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ const form = await boundedFormData(request, getEnvironment().MEDIA_MAX_BYTES);
+ const file = form.get('file');
+ if (!(file instanceof File)) throw new AppError('VALIDATION_FAILED', { field: 'file' });
+ const asset = await getMediaService().upload(membership, {
+ postId: form.get('postId'), replaceAssetId: form.get('replaceAssetId') ?? undefined,
+ altText: form.get('altText') ?? undefined, fileName: file.name,
+ declaredMimeType: file.type, data: Buffer.from(await file.arrayBuffer()),
+ }, requestId);
+ return dataResponse(asset, { status: 201 });
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/restore/route.ts b/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/restore/route.ts
new file mode 100644
index 0000000..279026c
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/restore/route.ts
@@ -0,0 +1,15 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string; postId: string }> };
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId, postId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ return dataResponse(await getEditorialService().restore(membership, postId, await request.json(), requestId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/route.ts b/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/route.ts
new file mode 100644
index 0000000..649eb74
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/posts/[postId]/revisions/route.ts
@@ -0,0 +1,13 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string; postId: string }> };
+
+export async function GET(request: Request, context: RouteContext): Promise {
+ const { workspaceId, postId } = await context.params;
+ return withApi(async () => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ return dataResponse(await getEditorialService().revisions(membership, postId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/posts/[postId]/route.ts b/app/api/workspaces/[workspaceId]/posts/[postId]/route.ts
new file mode 100644
index 0000000..13394e4
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/posts/[postId]/route.ts
@@ -0,0 +1,23 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { requireMembership } from '@/src/platform/auth/session';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string; postId: string }> };
+
+export async function GET(request: Request, context: RouteContext): Promise {
+ const { workspaceId, postId } = await context.params;
+ return withApi(async () => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ return dataResponse(await getEditorialService().get(membership, postId));
+ })(request);
+}
+
+export async function PATCH(request: Request, context: RouteContext): Promise {
+ const { workspaceId, postId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ return dataResponse(await getEditorialService().save(membership, postId, await request.json(), requestId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/posts/[postId]/transitions/route.ts b/app/api/workspaces/[workspaceId]/posts/[postId]/transitions/route.ts
new file mode 100644
index 0000000..8d9b4c8
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/posts/[postId]/transitions/route.ts
@@ -0,0 +1,15 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { requireMembership } from '@/src/platform/auth/session';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string; postId: string }> };
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId, postId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ return dataResponse(await getEditorialService().transition(membership, postId, await request.json(), requestId));
+ })(request);
+}
diff --git a/app/api/workspaces/[workspaceId]/posts/route.ts b/app/api/workspaces/[workspaceId]/posts/route.ts
new file mode 100644
index 0000000..618029c
--- /dev/null
+++ b/app/api/workspaces/[workspaceId]/posts/route.ts
@@ -0,0 +1,25 @@
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { requireMembership } from '@/src/platform/auth/session';
+import { assertTrustedMutationOrigin } from '@/src/platform/auth/origin';
+import { dataResponse, withApi } from '@/src/platform/observability/api';
+
+type RouteContext = { params: Promise<{ workspaceId: string }> };
+
+export async function GET(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ const data = await getEditorialService().list(membership);
+ return dataResponse(data, { headers: { 'x-operation-id': requestId } });
+ })(request);
+}
+
+export async function POST(request: Request, context: RouteContext): Promise {
+ const { workspaceId } = await context.params;
+ return withApi(async (_request, requestId) => {
+ const membership = await requireMembership(request.headers, workspaceId);
+ assertTrustedMutationOrigin(request);
+ const data = await getEditorialService().create(membership, await request.json(), requestId);
+ return dataResponse(data, { status: 201 });
+ })(request);
+}
diff --git a/app/default.tsx b/app/default.tsx
deleted file mode 100644
index 461f67a..0000000
--- a/app/default.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export default () => null;
diff --git a/app/globals.css b/app/globals.css
index d17cc8b..5f3bbdb 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -1,124 +1,272 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+:root {
+ color-scheme: dark;
+ --bg: #090b0d;
+ --surface: #111519;
+ --surface-strong: #171c21;
+ --text: #f5f7f8;
+ --muted: #9ba6af;
+ --line: #293139;
+ --accent: #c8ff5a;
+ --accent-ink: #172004;
+ --danger: #ff857f;
+ --radius: 18px;
+}
+
+* { box-sizing: border-box; }
+
+html { background: var(--bg); scroll-behavior: smooth; }
body {
- font-family: Arial, Helvetica, sans-serif;
+ margin: 0;
+ min-width: 320px;
+ background:
+ radial-gradient(circle at 84% 5%, rgb(200 255 90 / 9%), transparent 28rem),
+ var(--bg);
+ color: var(--text);
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ font-synthesis: none;
+}
+
+a { color: inherit; }
+button, input, textarea, select { font: inherit; }
+
+:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
+
+.skip-link {
+ position: fixed;
+ top: 12px;
+ left: 12px;
+ z-index: 100;
+ transform: translateY(-160%);
+ border-radius: 999px;
+ background: var(--accent);
+ color: var(--accent-ink);
+ padding: 10px 16px;
+ font-weight: 800;
+}
+
+.skip-link:focus { transform: translateY(0); }
+
+.marketing-shell { width: min(1180px, calc(100% - 40px)); margin: 0 auto; }
+
+.marketing-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ min-height: 84px;
+ border-bottom: 1px solid var(--line);
}
-@layer base {
- :root {
- --background: 0 0% 98%;
- --foreground: 0 0% 3.9%;
- --card: 0 0% 100%;
- --card-foreground: 0 0% 3.9%;
- --popover: 0 0% 100%;
- --popover-foreground: 0 0% 3.9%;
- --primary: 0 0% 9%;
- --primary-foreground: 0 0% 98%;
- --secondary: 0 0% 96.1%;
- --secondary-foreground: 0 0% 9%;
- --muted: 0 0% 96.1%;
- --muted-foreground: 0 0% 45.1%;
- --accent: 0 0% 96.1%;
- --accent-foreground: 0 0% 9%;
- --destructive: 0 84.2% 60.2%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 89.8%;
- --input: 0 0% 89.8%;
- --ring: 0 0% 3.9%;
- --chart-1: 12 76% 61%;
- --chart-2: 173 58% 39%;
- --chart-3: 197 37% 24%;
- --chart-4: 43 74% 66%;
- --chart-5: 27 87% 67%;
- --radius: 0.5rem;
- --sidebar-background: 0 0% 93%;
- --sidebar-foreground: 240 5.3% 26.1%;
- --sidebar-primary: 240 5.9% 10%;
- --sidebar-primary-foreground: 0 0% 98%;
- --sidebar-accent: 240 4.8% 95.9%;
- --sidebar-accent-foreground: 240 5.9% 10%;
- --sidebar-border: 220 13% 91%;
- --sidebar-ring: 217.2 91.2% 59.8%;
- }
- .dark {
- --background: 240 5.9% 8% ;
- --foreground: 0 0% 98%;
- --card: 0 0% 3.9%;
- --card-foreground: 0 0% 98%;
- --popover: 0 0% 3.9%;
- --popover-foreground: 0 0% 98%;
- --primary: 0 0% 98%;
- --primary-foreground: 0 0% 9%;
- --secondary: 0 0% 14.9%;
- --secondary-foreground: 0 0% 98%;
- --muted: 0 0% 14.9%;
- --muted-foreground: 0 0% 63.9%;
- --accent: 0 0% 14.9%;
- --accent-foreground: 0 0% 98%;
- --destructive: 0 62.8% 30.6%;
- --destructive-foreground: 0 0% 98%;
- --border: 0 0% 14.9%;
- --input: 0 0% 14.9%;
- --ring: 0 0% 83.1%;
- --chart-1: 220 70% 50%;
- --chart-2: 160 60% 45%;
- --chart-3: 30 80% 55%;
- --chart-4: 280 65% 60%;
- --chart-5: 340 75% 55%;
- --sidebar-background: 0 0% 3.90%;
- --sidebar-foreground: 240 4.8% 95.9%;
- --sidebar-primary: 224.3 76.3% 48%;
- --sidebar-primary-foreground: 0 0% 100%;
- --sidebar-accent: 240 3.7% 15.9%;
- --sidebar-accent-foreground: 240 4.8% 95.9%;
- --sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
- }
-
- * {
- -webkit-touch-callout: none; /* Safari */
- -webkit-user-select: none; /* Chrome */
- -moz-user-select: none; /* Firefox */
- -ms-user-select: none; /* Internet Explorer/Edge */
- user-select: none;
-
- /* disable all kind of selection text */
- @apply border-border select-none;
- }
-
- body {
- @apply bg-background text-foreground;
- }
-
+.brand { display: inline-flex; align-items: center; gap: 12px; font-weight: 850; letter-spacing: -.03em; }
+.brand-mark { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 11px; background: var(--accent); color: var(--accent-ink); }
+.nav-links { display: flex; align-items: center; gap: 24px; color: var(--muted); font-size: .92rem; }
+.nav-links a { text-decoration: none; }
+
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-height: 46px;
+ border: 1px solid transparent;
+ border-radius: 999px;
+ background: var(--accent);
+ color: var(--accent-ink);
+ padding: 0 20px;
+ font-weight: 800;
+ text-decoration: none;
}
+.button.secondary { border-color: var(--line); background: transparent; color: var(--text); }
+
+.hero { display: grid; grid-template-columns: 1.15fr .85fr; gap: 72px; align-items: center; min-height: 680px; padding: 72px 0; }
+.eyebrow { color: var(--accent); font-size: .78rem; font-weight: 850; letter-spacing: .12em; text-transform: uppercase; }
+h1 { margin: 18px 0 22px; max-width: 800px; font-size: clamp(3rem, 7vw, 6.8rem); line-height: .92; letter-spacing: -.07em; }
+.hero-copy { max-width: 650px; color: var(--muted); font-size: clamp(1.05rem, 2vw, 1.28rem); line-height: 1.65; }
+.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 34px; }
+.proof { display: flex; flex-wrap: wrap; gap: 20px; margin: 42px 0 0; padding: 0; list-style: none; color: var(--muted); font-size: .86rem; }
+.proof strong { display: block; color: var(--text); font-size: 1.12rem; }
+
+.editor-preview { border: 1px solid var(--line); border-radius: 28px; background: rgb(17 21 25 / 92%); padding: 14px; box-shadow: 0 36px 90px rgb(0 0 0 / 45%); }
+.preview-toolbar { display: flex; align-items: center; justify-content: space-between; padding: 10px 10px 20px; color: var(--muted); font-size: .8rem; }
+.preview-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 18px var(--accent); }
+.preview-canvas { min-height: 420px; border: 1px solid var(--line); border-radius: 18px; background: linear-gradient(145deg, #151a1f, #0d1013); padding: 24px; }
+.preview-state { display: inline-flex; border-radius: 999px; background: rgb(200 255 90 / 10%); color: var(--accent); padding: 7px 11px; font-size: .72rem; font-weight: 800; }
+.preview-title { max-width: 340px; margin: 74px 0 18px; font-size: 2.5rem; line-height: 1.02; letter-spacing: -.05em; }
+.preview-lines { display: grid; gap: 10px; }
+.preview-lines span { height: 8px; border-radius: 999px; background: #2a3239; }
+.preview-lines span:last-child { width: 62%; }
+.preview-footer { display: flex; justify-content: space-between; margin-top: 64px; border-top: 1px solid var(--line); padding-top: 18px; color: var(--muted); font-size: .76rem; }
+.capabilities { padding: 100px 0 120px; border-top: 1px solid var(--line); }
+.section-heading { display: flex; justify-content: space-between; gap: 40px; align-items: end; margin-bottom: 36px; }
+.section-heading h2 { margin: 0; max-width: 620px; font-size: clamp(2rem, 5vw, 4.2rem); line-height: 1; letter-spacing: -.055em; }
+.section-heading p { max-width: 400px; color: var(--muted); line-height: 1.65; }
+.capability-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
+.capability-card { min-height: 230px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); padding: 26px; }
+.capability-card span { color: var(--accent); font: 800 .75rem/1 ui-monospace, monospace; }
+.capability-card h3 { margin: 56px 0 10px; font-size: 1.25rem; }
+.capability-card p { margin: 0; color: var(--muted); line-height: 1.6; }
+.site-footer { display: flex; justify-content: space-between; gap: 24px; border-top: 1px solid var(--line); padding: 30px 0 44px; color: var(--muted); font-size: .84rem; }
-@keyframes fade-in {
- 0% {
- transform: translateX(50px);
- }
+.auth-shell { display: grid; grid-template-columns: minmax(0, 1.1fr) minmax(420px, .9fr); min-height: 100svh; }
+.auth-intro { display: flex; flex-direction: column; justify-content: space-between; gap: 80px; padding: clamp(28px, 5vw, 72px); border-right: 1px solid var(--line); }
+.auth-intro h1 { max-width: 880px; font-size: clamp(3rem, 6vw, 6.4rem); }
+.auth-note, .privacy-note { color: var(--muted); font-size: .82rem; line-height: 1.55; }
+.auth-panel { display: flex; flex-direction: column; justify-content: center; padding: clamp(24px, 5vw, 72px); background: rgb(17 21 25 / 72%); }
+.panel-heading h2 { margin: 10px 0 28px; font-size: 2rem; letter-spacing: -.04em; }
+.role-list { display: grid; gap: 10px; }
+.role-card { display: flex; align-items: center; justify-content: space-between; width: 100%; min-height: 76px; border: 1px solid var(--line); border-radius: 16px; background: var(--surface); color: var(--text); padding: 14px 18px; text-align: left; cursor: pointer; }
+.role-card:hover { border-color: #4a5742; background: var(--surface-strong); }
+.role-card:disabled { cursor: wait; opacity: .7; }
+.role-card strong, .role-card small { display: block; }
+.role-card small { margin-top: 5px; color: var(--muted); }
+.form-error { border: 1px solid rgb(255 133 127 / 45%); border-radius: 12px; background: rgb(255 133 127 / 8%); color: var(--danger); padding: 12px; }
+.privacy-note { margin-top: 24px; }
- to {
- transform: translateX(0);
- }
+.workspace-shell { display: grid; grid-template-columns: 290px minmax(0, 1fr); min-height: 100svh; background: var(--bg); }
+.workspace-sidebar { position: sticky; top: 0; display: flex; flex-direction: column; gap: 28px; height: 100svh; overflow-y: auto; border-right: 1px solid var(--line); background: #0c0f12; padding: 24px 18px; }
+.workspace-label { display: grid; gap: 4px; border-radius: 12px; background: var(--surface); padding: 13px; }
+.workspace-label span, .post-nav-heading { color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .08em; }
+.workspace-label strong { font-size: .9rem; }
+.workspace-label small { color: var(--muted); font-size: .66rem; line-height: 1.4; }
+.post-nav { display: grid; gap: 7px; }
+.post-nav-heading { display: flex; align-items: center; justify-content: space-between; padding: 0 8px 8px; }
+.post-nav-heading button { display: grid; place-items: center; width: 30px; height: 30px; border: 1px solid var(--line); border-radius: 9px; background: transparent; color: var(--text); cursor: pointer; }
+.post-nav > button { display: grid; gap: 5px; border: 1px solid transparent; border-radius: 12px; background: transparent; color: var(--text); padding: 11px; text-align: left; cursor: pointer; }
+.post-nav > button:hover, .post-nav > button[aria-current="page"] { border-color: var(--line); background: var(--surface); }
+.post-nav > button span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .86rem; font-weight: 700; }
+.post-nav > button small { color: var(--muted); font-size: .7rem; }
+.guided-checklist { margin-top: auto; border-top: 1px solid var(--line); padding: 22px 8px 0; }
+.guided-checklist ol { display: grid; gap: 9px; margin: 16px 0 0; padding-left: 18px; color: var(--muted); font-size: .76rem; }
+.guided-checklist li.done { color: var(--text); }
+.guided-checklist li.done::marker { color: var(--accent); }
+.reset-demo { width: 100%; min-height: 38px; margin-top: 18px; border: 1px solid var(--line); border-radius: 999px; background: transparent; color: var(--muted); font-size: .7rem; cursor: pointer; }
+.reset-demo.armed { border-color: rgb(255 133 127 / 55%); color: var(--danger); }
+
+.editor-shell { min-width: 0; }
+.editor-topbar { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; justify-content: space-between; min-height: 72px; border-bottom: 1px solid var(--line); background: rgb(9 11 13 / 88%); padding: 0 28px; backdrop-filter: blur(18px); }
+.editor-topbar > div { display: flex; align-items: center; gap: 10px; }
+.role-pill, .mode-pill, .save-state { display: inline-flex; align-items: center; min-height: 28px; border-radius: 999px; padding: 0 10px; font-size: .72rem; font-weight: 800; }
+.role-pill { background: var(--accent); color: var(--accent-ink); }
+.mode-pill, .save-state { border: 1px solid var(--line); color: var(--muted); }
+.save-state.conflict, .save-state.error { border-color: rgb(255 133 127 / 50%); color: var(--danger); }
+.text-button { border: 0; background: transparent; color: var(--muted); cursor: pointer; }
+.editor-grid { display: grid; grid-template-columns: minmax(0, 1fr) 310px; min-height: calc(100svh - 72px); }
+.editor-form { min-width: 0; padding: clamp(24px, 5vw, 72px); }
+.document-meta { display: flex; flex-wrap: wrap; gap: 9px; margin-bottom: 28px; color: var(--muted); font: 700 .72rem/1 ui-monospace, monospace; }
+.document-meta span { border-right: 1px solid var(--line); padding-right: 9px; }
+.editor-form label { display: block; margin: 18px 0 8px; color: var(--muted); font-size: .75rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em; }
+.editor-form textarea { width: 100%; resize: vertical; border: 1px solid transparent; border-radius: 12px; background: transparent; color: var(--text); padding: 10px; }
+.editor-form textarea:hover, .editor-form textarea:focus { border-color: var(--line); background: var(--surface); }
+.editor-form textarea:disabled { opacity: .75; }
+.title-input { min-height: 124px; font-size: clamp(2.2rem, 4.5vw, 5.2rem); font-weight: 850; line-height: .98; letter-spacing: -.055em; }
+.excerpt-input { min-height: 90px; color: var(--muted) !important; font-size: 1.15rem; line-height: 1.55; }
+.content-input { min-height: 460px; font: 1rem/1.75 ui-monospace, "SFMono-Regular", Consolas, monospace !important; }
+.workflow-panel { display: flex; align-items: end; justify-content: space-between; gap: 24px; margin-top: 30px; border-top: 1px solid var(--line); padding-top: 24px; }
+.workflow-panel h2 { margin: 8px 0 0; font-size: 1.35rem; }
+.workflow-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; }
+.workflow-actions > span { display: flex; align-items: end; gap: 8px; }
+.workflow-actions label { display: grid; gap: 5px; color: var(--muted); font-size: .7rem; }
+.workflow-actions input { min-height: 40px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--text); padding: 0 9px; color-scheme: dark; }
+.workflow-actions button, .history-toggle, .revision-history button, .preview-link { display: inline-flex; align-items: center; justify-content: center; min-height: 40px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--text); padding: 0 14px; font-size: .76rem; font-weight: 750; text-decoration: none; cursor: pointer; }
+.workflow-actions button:first-of-type { border-color: rgb(200 255 90 / 45%); }
+.workflow-actions button:disabled, .revision-history button:disabled { cursor: not-allowed; opacity: .45; }
+.ai-panel { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 18px; align-items: end; margin-top: 28px; border: 1px solid var(--line); border-radius: 18px; background: linear-gradient(145deg, rgb(200 255 90 / 4%), var(--surface)); padding: 20px; }
+.ai-panel h2 { margin: 7px 0 0; font-size: 1.35rem; }
+.ai-panel > label { grid-column: 1 / -1; display: grid; gap: 7px; color: var(--muted); font-size: .72rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em; }
+.ai-panel textarea { min-height: 72px; border-color: var(--line); background: #0c0f12; font-size: .88rem; }
+.ai-panel > button, .suggestion-preview button, .media-form button, .danger-button { min-height: 42px; border: 1px solid rgb(200 255 90 / 45%); border-radius: 999px; background: var(--accent); color: var(--accent-ink); padding: 0 15px; font-size: .78rem; font-weight: 800; cursor: pointer; }
+.ai-panel button:disabled, .media-panel button:disabled { cursor: not-allowed; opacity: .45; }
+.ai-panel > .panel-message { margin: 0; text-align: right; }
+.suggestion-preview { grid-column: 1 / -1; min-width: 0; border-top: 1px solid var(--line); padding-top: 18px; }
+.suggestion-preview > span, .suggestion-preview small { color: var(--muted); font-size: .68rem; }
+.suggestion-preview h3 { margin: 12px 0 6px; font-size: 1.4rem; }
+.suggestion-preview pre { max-height: 260px; overflow: auto; white-space: pre-wrap; border-radius: 12px; background: #0c0f12; color: var(--muted); padding: 12px; }
+.suggestion-preview button { display: block; margin-top: 16px; }
+.panel-message, .panel-empty { color: var(--muted); font-size: .72rem; line-height: 1.5; }
+.editor-message { color: var(--muted); font-size: .82rem; }
+.editor-message.conflict, .editor-message.error { color: var(--danger); }
+.conflict-actions { display: flex; flex-wrap: wrap; gap: 10px; }
+.conflict-actions button { min-height: 42px; border: 1px solid var(--line); border-radius: 999px; background: var(--surface); color: var(--text); padding: 0 15px; cursor: pointer; }
+.compare-panel { margin-top: 24px; border: 1px solid var(--line); border-radius: 16px; padding: 18px; }
+.compare-panel > div { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
+.compare-panel article { min-width: 0; border-radius: 12px; background: var(--surface); padding: 14px; }
+.compare-panel pre { overflow: auto; white-space: pre-wrap; color: var(--muted); }
+.evidence-panel { border-left: 1px solid var(--line); background: #0c0f12; padding: 38px 24px; }
+.evidence-panel h2 { margin: 12px 0 32px; font-size: 1.6rem; letter-spacing: -.04em; }
+.evidence-panel dl { display: grid; gap: 18px; }
+.evidence-panel dl div { display: grid; gap: 5px; border-bottom: 1px solid var(--line); padding-bottom: 14px; }
+.evidence-panel dt { color: var(--muted); font-size: .72rem; text-transform: uppercase; letter-spacing: .06em; }
+.evidence-panel dd { margin: 0; font-size: .85rem; word-break: break-word; }
+.evidence-panel > p:last-child { margin-top: 30px; color: var(--muted); font-size: .82rem; line-height: 1.65; }
+.preview-link, .history-toggle { width: 100%; margin-top: 10px; }
+.history-toggle { background: transparent; }
+.revision-history { display: grid; gap: 8px; margin-top: 14px; }
+.revision-history article { border: 1px solid var(--line); border-radius: 12px; background: var(--surface); padding: 11px; }
+.revision-history article > div { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
+.revision-history p { margin: 9px 0; font-size: .76rem; }
+.revision-history small { color: var(--muted); font-size: .62rem; }
+.revision-history button { min-height: 30px; padding: 0 9px; font-size: .66rem; }
+.history-comparison { margin-top: 12px; border: 1px solid var(--line); border-radius: 12px; padding: 11px; }
+.history-comparison h3 { margin: 0 0 9px; font-size: .8rem; }
+.history-comparison pre { max-height: 220px; overflow: auto; white-space: pre-wrap; color: var(--muted); font-size: .7rem; }
+.media-panel { margin-top: 24px; border-top: 1px solid var(--line); padding-top: 20px; }
+.media-panel h3 { margin: 8px 0 14px; }
+.asset-card { display: grid; gap: 6px; border-radius: 12px; background: var(--surface); padding: 12px; }
+.asset-card strong { overflow: hidden; text-overflow: ellipsis; }
+.asset-card span { color: var(--muted); font-size: .68rem; }
+.media-form { display: grid; gap: 10px; margin-top: 12px; }
+.media-form label { display: grid; gap: 6px; color: var(--muted); font-size: .68rem; }
+.media-form input { width: 100%; min-height: 38px; border: 1px solid var(--line); border-radius: 9px; background: var(--surface); color: var(--text); padding: 7px; }
+.media-form input[type="file"] { font-size: .66rem; }
+.danger-button { width: 100%; margin-top: 8px; border-color: rgb(255 133 127 / 45%); background: transparent; color: var(--danger); }
+
+.public-article { width: min(920px, calc(100% - 32px)); margin: 0 auto; padding-bottom: 100px; }
+.public-article nav { display: flex; align-items: center; justify-content: space-between; min-height: 76px; border-bottom: 1px solid var(--line); color: var(--muted); font-size: .8rem; }
+.public-article nav a { color: var(--text); font-weight: 850; text-decoration: none; }
+.public-article article { padding: clamp(70px, 12vw, 140px) 0; }
+.public-article h1 { max-width: 900px; font-size: clamp(3.2rem, 9vw, 7rem); }
+.public-standfirst { max-width: 720px; color: var(--muted); font-size: clamp(1.1rem, 2vw, 1.45rem); line-height: 1.6; }
+.public-content { margin-top: 70px; white-space: pre-wrap; color: #dce2e6; font: 1.05rem/1.85 ui-monospace, Consolas, monospace; }
+.public-article footer { margin-top: 80px; border-top: 1px solid var(--line); padding-top: 22px; color: var(--muted); font-size: .76rem; }
+
+@media (max-width: 860px) {
+ .nav-links a:not(.button) { display: none; }
+ .hero { grid-template-columns: 1fr; gap: 36px; min-height: auto; padding: 80px 0; }
+ .editor-preview { max-width: 620px; }
+ .capability-grid { grid-template-columns: 1fr; }
+ .section-heading { align-items: start; flex-direction: column; }
+ .auth-shell { grid-template-columns: 1fr; }
+ .auth-intro { min-height: 64svh; border-right: 0; border-bottom: 1px solid var(--line); }
+ .workspace-shell { grid-template-columns: 1fr; }
+ .workspace-sidebar { position: static; height: auto; max-height: 46svh; border-right: 0; border-bottom: 1px solid var(--line); }
+ .guided-checklist { display: none; }
+ .editor-grid { grid-template-columns: 1fr; }
+ .evidence-panel { border-top: 1px solid var(--line); border-left: 0; }
}
-@keyframes fade {
- from {
- opacity: 0;
- }
- to {
- opacity: 1;
- }
+@media (max-width: 520px) {
+ .marketing-shell { width: min(100% - 24px, 1180px); }
+ .brand-word { display: none; }
+ h1 { font-size: clamp(3rem, 16vw, 5rem); }
+ .hero-actions .button { width: 100%; }
+ .preview-canvas { min-height: 340px; }
+ .preview-title { margin-top: 50px; font-size: 2rem; }
+ .site-footer { flex-direction: column; }
+ .auth-intro, .auth-panel { padding: 24px 18px; }
+ .auth-intro { min-height: 58svh; }
+ .editor-topbar { align-items: flex-start; min-height: 90px; padding: 14px; }
+ .editor-topbar > div { align-items: flex-start; flex-direction: column; gap: 5px; }
+ .editor-form { padding: 24px 14px; }
+ .workflow-panel { align-items: stretch; flex-direction: column; }
+ .workflow-actions { justify-content: flex-start; }
+ .ai-panel { grid-template-columns: 1fr; }
+ .ai-panel > .panel-message { text-align: left; }
+ .title-input { min-height: 96px; }
+ .compare-panel > div { grid-template-columns: 1fr; }
}
-@keyframes backToPosition {
- to {
- transform: translate(0);
- opacity: 1;
- }
-}
\ No newline at end of file
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after { scroll-behavior: auto !important; transition-duration: .01ms !important; animation-duration: .01ms !important; animation-iteration-count: 1 !important; }
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 25c94a4..5b7aca5 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,50 +1,31 @@
-import '@/config/_export';
-
import './globals.css';
-import '@/lib/cloudinary';
-
-import { Toaster } from '@/ui/toaster';
-import { SidebarProvider } from '@/ui/sidebar';
-import { ThemeProvider } from '@/hooks/use-theme';
-
-import type { ReactNode } from 'react';
import type { Metadata, Viewport } from 'next';
+import type { ReactNode } from 'react';
export const metadata: Metadata = {
- title: 'BT - Dashboard',
- description: 'Developed by PatrickDev',
- robots: { index: false, follow: false },
+ title: {
+ default: 'AutoBlog CMS — Editorial control for AI-assisted teams',
+ template: '%s | AutoBlog CMS',
+ },
+ description:
+ 'An authenticated editorial CMS with immutable revisions, role-based workflow and bounded AI assistance.',
+ metadataBase: new URL(process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000'),
+ robots: { index: true, follow: true },
};
export const viewport: Viewport = {
width: 'device-width',
initialScale: 1,
- maximumScale: 1,
- userScalable: false,
- themeColor: '#000',
+ themeColor: '#090b0d',
};
-export default function RootLayout({ sidebar, inset }: Layouts) {
+export default function RootLayout({ children }: Readonly<{ children: ReactNode }>) {
return (
-
-
-
-
-
-
- {sidebar}
- {inset}
-
-
-
-
-
-
+
+
+ Skip to content
+ {children}
+
);
}
-
-export interface Layouts {
- inset: ReactNode;
- sidebar: ReactNode;
-}
diff --git a/app/page.tsx b/app/page.tsx
new file mode 100644
index 0000000..6327d8b
--- /dev/null
+++ b/app/page.tsx
@@ -0,0 +1,75 @@
+import Link from 'next/link';
+
+const capabilities = [
+ ['01', 'Immutable by design', 'Every meaningful save becomes a revision; published content remains pinned to an auditable snapshot.'],
+ ['02', 'Policy before pixels', 'Owner, Admin, Editor, Author and Reviewer permissions execute on the server, not in hidden buttons.'],
+ ['03', 'AI with a boundary', 'Suggestions are quota-bound, mode-labeled and applied only after an explicit editorial decision.'],
+] as const;
+
+export default function MarketingPage() {
+ return (
+
+
+
+
+
+
+
Editorial systems, made defensible
+
Publish with evidence.
+
+ AutoBlog gives AI-assisted teams one durable workflow for drafting, review,
+ approval and publication—without silent overwrites or simulated security.
+
+
+
+ 5 roles server-enforced
+ 409 stale-write contract
+ 1 path demo to production
+
+
+
+
+
+ autoblog / spring-edition
+
+
+
+
IN REVIEW · REV 12
+
Designing the next editorial operating system
+
+
Reviewer: Maya Chen AI mode: Mock
+
+
+
+
+
+
+
A polished surface over explicit invariants.
+
One modular monolith owns identity, editorial state, media and AI boundaries. Every advertised path is designed to be persisted, authorized and tested.
+
+
+ {capabilities.map(([number, title, copy]) => (
+
+ {number} {title} {copy}
+
+ ))}
+
+
+
+
+
AutoBlog CMS · portfolio engineering release Next.js · TypeScript · libSQL
+
+ );
+}
diff --git a/app/preview/[workspaceSlug]/[postSlug]/page.tsx b/app/preview/[workspaceSlug]/[postSlug]/page.tsx
new file mode 100644
index 0000000..8d08350
--- /dev/null
+++ b/app/preview/[workspaceSlug]/[postSlug]/page.tsx
@@ -0,0 +1,30 @@
+import { notFound } from 'next/navigation';
+import Link from 'next/link';
+
+import { getEditorialService } from '@/src/modules/editorial/service';
+import { AppError } from '@/src/platform/observability/errors';
+
+export const revalidate = 60;
+
+export default async function PublicPreviewPage({ params }: Readonly<{ params: Promise<{ workspaceSlug: string; postSlug: string }> }>) {
+ const { workspaceSlug, postSlug } = await params;
+ let post;
+ try {
+ post = await getEditorialService().publicPost(workspaceSlug, postSlug);
+ } catch (error) {
+ if (error instanceof AppError && error.code === 'NOT_FOUND') notFound();
+ throw error;
+ }
+ return (
+
+ AutoBlogPublished preview
+
+ {post.workspaceName}
+ {post.title}
+ {post.excerpt}
+ {post.content}
+ Immutable revision {post.revisionId} · Published {post.publishedAt.toLocaleDateString('en-GB')}
+
+
+ );
+}
diff --git a/app/sign-in/page.tsx b/app/sign-in/page.tsx
new file mode 100644
index 0000000..1f6733f
--- /dev/null
+++ b/app/sign-in/page.tsx
@@ -0,0 +1,27 @@
+import Link from 'next/link';
+
+import { DEMO_IDENTITIES } from '@/src/modules/identity/demo';
+import { getEnvironment } from '@/src/platform/config/env';
+import { SignInForm } from '@/src/ui/patterns/sign-in-form';
+
+export const dynamic = 'force-dynamic';
+
+export default function SignInPage() {
+ const environment = getEnvironment();
+ return (
+
+
+
+ A AutoBlog CMS
+
+
+
Bounded recruiter demo
+
Choose a seat at the editorial table.
+
Each role is a real database identity with a revocable HTTP-only session. The demo workspace is isolated from every configured workspace.
+
+ Demo data uses the production command, policy and repository path.
+
+
+
+ );
+}
diff --git a/app/template.tsx b/app/template.tsx
deleted file mode 100644
index 422a3e8..0000000
--- a/app/template.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-"use client";
-import switchTheme from "@/utils/switch-theme";
-import { PropsWithChildren, useEffect } from "react";
-
-export default function Template({ children }: PropsWithChildren) {
- useEffect(() => {
- switchTheme(false);
- }, []);
- return children;
-}
diff --git a/auto_cleaner_restore.bat b/auto_cleaner_restore.bat
deleted file mode 100644
index 5fdd18f..0000000
--- a/auto_cleaner_restore.bat
+++ /dev/null
@@ -1 +0,0 @@
-npm install
diff --git a/bun.lock b/bun.lock
index 32c7f85..3ea14c8 100644
--- a/bun.lock
+++ b/bun.lock
@@ -5,103 +5,190 @@
"": {
"name": "dashboard",
"dependencies": {
- "@google/generative-ai": "^0.21.0",
- "@radix-ui/react-alert-dialog": "^1.1.6",
- "@radix-ui/react-avatar": "^1.1.3",
- "@radix-ui/react-checkbox": "^1.1.4",
- "@radix-ui/react-collapsible": "^1.1.2",
- "@radix-ui/react-context-menu": "^2.2.4",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.6",
- "@radix-ui/react-icons": "^1.3.2",
- "@radix-ui/react-label": "^2.1.2",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-radio-group": "^1.2.3",
- "@radix-ui/react-scroll-area": "^1.2.3",
- "@radix-ui/react-select": "^2.1.4",
- "@radix-ui/react-separator": "^1.1.2",
- "@radix-ui/react-slot": "^1.1.2",
- "@radix-ui/react-switch": "^1.1.2",
- "@radix-ui/react-tabs": "^1.1.3",
- "@radix-ui/react-toast": "^1.2.4",
- "@radix-ui/react-toggle": "^1.1.2",
- "@radix-ui/react-toggle-group": "^1.1.2",
- "@radix-ui/react-tooltip": "^1.1.6",
- "@vercel/speed-insights": "^1.1.0",
- "class-variance-authority": "^0.7.1",
- "cloudinary": "^2.5.1",
- "clsx": "^2.1.1",
- "cmdk": "^1.0.0",
- "date-fns": "^3.6.0",
- "framer-motion": "^12.4.1",
- "html2canvas": "^1.4.1",
- "lucide-react": "^0.469.0",
- "mongoose": "^8.9.3",
- "next": "^15.1.6",
- "next-cloudinary": "^6.16.0",
- "react": "^19.0.0",
- "react-day-picker": "^8.10.1",
- "react-dom": "^19.0.0",
- "react-icons": "^5.4.0",
- "react-loading-indicators": "^1.0.0",
- "react-markdown": "^9.0.3",
- "tailwind-merge": "^2.6.0",
- "tailwindcss-animate": "^1.0.7",
- "vaul": "^1.1.2",
+ "@google/genai": "2.13.0",
+ "@libsql/client": "0.17.4",
+ "better-auth": "1.6.23",
+ "drizzle-orm": "0.45.2",
+ "next": "16.2.11",
+ "react": "19.2.8",
+ "react-dom": "19.2.8",
+ "sharp": "0.35.3",
+ "zod": "4.4.3",
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "15.1.1",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
- "typescript": "^5",
+ "@axe-core/playwright": "4.12.1",
+ "@playwright/test": "1.61.1",
+ "@types/bun": "1.3.14",
+ "@types/node": "24.10.13",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3",
+ "drizzle-kit": "0.31.10",
+ "eslint": "9.39.5",
+ "eslint-config-next": "16.2.11",
+ "lighthouse": "13.4.1",
+ "typescript": "6.0.3",
+ "vitest": "4.1.10",
},
},
},
+ "overrides": {
+ "esbuild": "0.28.1",
+ "picomatch": "4.0.5",
+ "postcss": "8.5.21",
+ "sharp": "0.35.3",
+ },
"packages": {
- "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
+ "@apm-js-collab/code-transformer": ["@apm-js-collab/code-transformer@0.18.0", "", { "dependencies": { "@types/estree": "^1.0.8", "astring": "^1.9.0", "esquery": "^1.7.0", "meriyah": "^6.1.4", "semifies": "^1.0.0", "source-map": "^0.6.0" }, "bin": { "code-transformer": "cli.js" } }, "sha512-aN3Oq8r1J3gPJtCwErP664gM0+HhM1I1lujPr9TMTCcEl/joQQbpGpeMdts9B1+W2wHMsvioDMv5F4PvMWE6gw=="],
+
+ "@apm-js-collab/code-transformer-bundler-plugins": ["@apm-js-collab/code-transformer-bundler-plugins@0.7.2", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.18.0", "es-module-lexer": "^2.1.0", "magic-string": "^0.30.21", "module-details-from-path": "^1.0.4" } }, "sha512-GvpKWDmzBFzbtVElU+tEDMDYyMY8SnMH4NNwwlUQioNHZvE6sSuHEqNkfU3iYhWL+/XO5Ej+MzhVJoqM10NYCQ=="],
+
+ "@apm-js-collab/tracing-hooks": ["@apm-js-collab/tracing-hooks@0.13.0", "", { "dependencies": { "@apm-js-collab/code-transformer": "^0.18.0", "debug": "^4.4.1", "module-details-from-path": "^1.0.4" } }, "sha512-mTvWz9rnQwx1U3h0XPTHaX7bgfkpipLLTQyjlC2cdhQpQEuoLT0AGzoydeoq2NxfEVv6fWOOETcSbb2nptleyw=="],
+
+ "@axe-core/playwright": ["@axe-core/playwright@4.12.1", "", { "dependencies": { "axe-core": "~4.12.1" }, "peerDependencies": { "playwright-core": ">= 1.0.0" } }, "sha512-rMd7xriptqKpP+w5265i4Hdkv2X5kbu6uiBi/B2I7uf3hieRBM3qDCfaKPtxfiYb2mKXfF+yLODJwIx+Jv1GDw=="],
+
+ "@babel/code-frame": ["@babel/code-frame@7.29.7", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw=="],
+
+ "@babel/compat-data": ["@babel/compat-data@7.29.7", "", {}, "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg=="],
+
+ "@babel/core": ["@babel/core@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="],
+
+ "@babel/generator": ["@babel/generator@7.29.7", "", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="],
+
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.29.7", "", { "dependencies": { "@babel/compat-data": "^7.29.7", "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g=="],
+
+ "@babel/helper-globals": ["@babel/helper-globals@7.29.7", "", {}, "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA=="],
+
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g=="],
+
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.29.7", "", { "dependencies": { "@babel/helper-module-imports": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7", "@babel/traverse": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg=="],
+
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.29.7", "", {}, "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw=="],
+
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.29.7", "", {}, "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg=="],
+
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.29.7", "", {}, "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw=="],
+
+ "@babel/helpers": ["@babel/helpers@7.29.7", "", { "dependencies": { "@babel/template": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg=="],
+
+ "@babel/parser": ["@babel/parser@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" }, "bin": "./bin/babel-parser.js" }, "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg=="],
+
+ "@babel/template": ["@babel/template@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg=="],
+
+ "@babel/traverse": ["@babel/traverse@7.29.7", "", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-globals": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/types": "^7.29.7", "debug": "^4.3.1" } }, "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw=="],
+
+ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
+
+ "@better-auth/core": ["@better-auth/core@1.6.23", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.39.0", "@standard-schema/spec": "^1.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@cloudflare/workers-types": ">=4", "@opentelemetry/api": "^1.9.0", "better-call": "1.3.7", "jose": "^6.1.0", "kysely": "^0.28.5 || ^0.29.0", "nanostores": "^1.0.1" }, "optionalPeers": ["@cloudflare/workers-types", "@opentelemetry/api"] }, "sha512-beEhOs0uVeOxYOZKUfIEBd/nQV2Bd4/6wyLxZ0OFkn6CMTK2Vi+hXuZLnyPBeB6RdHpebEoJWiHqwHxBIxgPDQ=="],
+
+ "@better-auth/drizzle-adapter": ["@better-auth/drizzle-adapter@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2", "drizzle-orm": "^0.45.2" }, "optionalPeers": ["drizzle-orm"] }, "sha512-2+/PTVfIP9E7iz6af8TB3lhnowHUj9ljC66kECmHaFEdUqPgzHoWux9epotKwO7XDg2ui4ttWQ8CMeNFLvQeKQ=="],
+
+ "@better-auth/kysely-adapter": ["@better-auth/kysely-adapter@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2", "kysely": "^0.28.17 || ^0.29.0" }, "optionalPeers": ["kysely"] }, "sha512-zbNJsMbG09exfkGyvFqBLLqWoMPAUWjxCuUnEK5AsjbYoZeIjj/QGZgdf4CapVWryKxjA9Q6Jlr6fbiPpC3VAg=="],
+
+ "@better-auth/memory-adapter": ["@better-auth/memory-adapter@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2" } }, "sha512-krIiR0pIVkaKlAzm690n5bcMW4NGbqeMg0HQSD9fz/KcQF/eWLqcq9gG/BhHTj2i/y96qH+W5JWPmaSOS5iTgQ=="],
+
+ "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-7+QdevitGlKBbP6JbiSk5SBnzPsKV/mDrQBGBn8hwByQLeJwqpqbuBPw7ZI8vzUlFfAAnyFiqwP3Eb8mxnp7pA=="],
+
+ "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-2qSdzidq4tkb1eS5TTqb4Nzg0mdZWm3Qky9SYeXeb8PpVQbC2sxqJhEM5mK7y12uU6I8hc64wO9f7AFVNL+6UQ=="],
+
+ "@better-auth/telemetry": ["@better-auth/telemetry@1.6.23", "", { "peerDependencies": { "@better-auth/core": "^1.6.23", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1" } }, "sha512-/R2Kb+z2BpDOOWwVHqOk+c0VNpuwfCv4Hp5Yr9003WIZPax/zyNraGLB9CFE8qF2gZW8Dsz419k4I8CPrGzpDA=="],
+
+ "@better-auth/utils": ["@better-auth/utils@0.4.2", "", { "dependencies": { "@noble/hashes": "^2.0.1" } }, "sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A=="],
+
+ "@better-fetch/fetch": ["@better-fetch/fetch@1.3.1", "", {}, "sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g=="],
+
+ "@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
+
+ "@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
+
+ "@emnapi/runtime": ["@emnapi/runtime@1.11.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA=="],
+
+ "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
+
+ "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
+
+ "@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
+
+ "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
+
+ "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
+
+ "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="],
+
+ "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="],
+
+ "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="],
+
+ "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="],
+
+ "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="],
+
+ "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="],
+
+ "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="],
+
+ "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="],
+
+ "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="],
+
+ "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="],
+
+ "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="],
+
+ "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="],
+
+ "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="],
- "@cloudinary-util/types": ["@cloudinary-util/types@1.5.10", "", {}, "sha512-n5lrm7SdAXhgWEbkSJKHZGnaoO9G/g4WYS6HYnq/k4nLj79sYfQZOoKjyR8hF2iyLRdLkT+qlk68RNFFv5tKew=="],
+ "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="],
- "@cloudinary-util/url-loader": ["@cloudinary-util/url-loader@5.10.4", "", { "dependencies": { "@cloudinary-util/types": "1.5.10", "@cloudinary-util/util": "3.3.2", "@cloudinary/url-gen": "1.15.0", "zod": "^3.22.4" } }, "sha512-gHkdvOaV+rlcwuIT7Vqd0ts/H5bsH4+bwFten/gIZ8oRjzdTBvgIY3R6F8bbJt0pFIEfpFEQLe4rPkl0NNqEWg=="],
+ "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="],
- "@cloudinary-util/util": ["@cloudinary-util/util@4.0.0", "", {}, "sha512-S4xcou/3A7l5o+bcKlw2VHBNgwups7/0lbVDT/cO5YmtrcEYXgj6LGmwnjvpTm/x571VPVN8x5jWdT3rLZiKJQ=="],
+ "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="],
- "@cloudinary/transformation-builder-sdk": ["@cloudinary/transformation-builder-sdk@1.16.1", "", { "dependencies": { "@cloudinary/url-gen": "^1.7.0" } }, "sha512-Mh1qYMkoDxSAzbt0qY9NJaZrdH/vFBcrpeVWmbTXbPVDZHLaaLyJ2+RDFGger5lycbrehPLoNp2hh22BvhkvbQ=="],
+ "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="],
- "@cloudinary/url-gen": ["@cloudinary/url-gen@1.15.0", "", { "dependencies": { "@cloudinary/transformation-builder-sdk": "^1.10.0" } }, "sha512-bjU67eZxLUgoRy/Plli4TQio7q6P31OYqnEgXxeN9TKXrzr6h0DeEdIUhKI9gy3HkEBWXWWJIPh7j7gkOJPnyA=="],
+ "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="],
- "@emnapi/runtime": ["@emnapi/runtime@1.3.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw=="],
+ "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="],
- "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.4.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA=="],
+ "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="],
- "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="],
+ "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="],
- "@eslint/config-array": ["@eslint/config-array@0.19.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w=="],
+ "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="],
- "@eslint/core": ["@eslint/core@0.10.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw=="],
+ "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="],
- "@eslint/eslintrc": ["@eslint/eslintrc@3.2.0", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w=="],
+ "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="],
- "@eslint/js": ["@eslint/js@9.19.0", "", {}, "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ=="],
+ "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="],
- "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="],
+ "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
- "@eslint/plugin-kit": ["@eslint/plugin-kit@0.2.5", "", { "dependencies": { "@eslint/core": "^0.10.0", "levn": "^0.4.1" } }, "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A=="],
+ "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="],
- "@floating-ui/core": ["@floating-ui/core@1.6.9", "", { "dependencies": { "@floating-ui/utils": "^0.2.9" } }, "sha512-uMXCuQ3BItDUbAMhIXw7UPXRfAlOAvZzdK9BWpE60MCn+Svt3aLn9jsPTi/WNGlRUu2uI0v5S7JiIUsbsvh3fw=="],
+ "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
- "@floating-ui/dom": ["@floating-ui/dom@1.6.13", "", { "dependencies": { "@floating-ui/core": "^1.6.0", "@floating-ui/utils": "^0.2.9" } }, "sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w=="],
+ "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
- "@floating-ui/react-dom": ["@floating-ui/react-dom@2.1.2", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A=="],
+ "@eslint/eslintrc": ["@eslint/eslintrc@3.3.6", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA=="],
- "@floating-ui/utils": ["@floating-ui/utils@0.2.9", "", {}, "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg=="],
+ "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="],
- "@google/generative-ai": ["@google/generative-ai@0.21.0", "", {}, "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg=="],
+ "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
+
+ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
+
+ "@formatjs/ecma402-abstract": ["@formatjs/ecma402-abstract@2.3.6", "", { "dependencies": { "@formatjs/fast-memoize": "2.2.7", "@formatjs/intl-localematcher": "0.6.2", "decimal.js": "^10.4.3", "tslib": "^2.8.0" } }, "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw=="],
+
+ "@formatjs/fast-memoize": ["@formatjs/fast-memoize@2.2.7", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ=="],
+
+ "@formatjs/icu-messageformat-parser": ["@formatjs/icu-messageformat-parser@2.11.4", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/icu-skeleton-parser": "1.8.16", "tslib": "^2.8.0" } }, "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw=="],
+
+ "@formatjs/icu-skeleton-parser": ["@formatjs/icu-skeleton-parser@1.8.16", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "tslib": "^2.8.0" } }, "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ=="],
+
+ "@formatjs/intl-localematcher": ["@formatjs/intl-localematcher@0.6.2", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA=="],
+
+ "@google/genai": ["@google/genai@2.13.0", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-GM7C8Kaomvjz05x5JEO6+l3d/pciL9LxAG9dUjJLD7nTPZ9X0Cfsf2Z7eET6UjgWyUmxXCHtYnQoQ77F9+ZIOQ=="],
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
@@ -109,269 +196,311 @@
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
- "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.1", "", {}, "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA=="],
+ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
+
+ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
+
+ "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.2" }, "os": "darwin", "cpu": "arm64" }, "sha512-RMnFX7YQsMoh7lWfcM4NEHHymBX/rLuKNPVM84XE9ONPcaSCDgE7CHIHpSgPcO2xcRthgBy1HfNO319mwhIAkg=="],
+
+ "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.2" }, "os": "darwin", "cpu": "x64" }, "sha512-Xo+5uFBtLN0BKqieTxiFzFPQAUlBbbH5iBKyRX/z1JrbnYsHTfKJnUfL8+p2TPXr1pXqao4eeL4Rl144uDpK9w=="],
+
+ "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "os": "freebsd" }, "sha512-lUxcqWIj2wMQ9BrwNjngcr1gWUr5xgaGThBRqPPalIC2n67Cqj1uPh8NnA/ZhAg8hUbKl+kVHKwgUIwe6ZYPrg=="],
+
+ "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-9J6ypZFpQBj4YnePGoq/S38w6nz+vqg5WZLrLGY4YuSemdMq47GMLBPO42MzwdGwpg/agZ7xzZcFHa48xlywfg=="],
+
+ "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-m2pW1n6cns9VaubNwsZ+c3CRYjxNQWgJ5gPlnL1nbBcpkBvFm6SCFN5o0psFHI8w9n11NKhFkeEDns98tiqbEw=="],
- "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.0.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ=="],
+ "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.2", "", { "os": "linux", "cpu": "arm" }, "sha512-1eMLzy92I4J6rmi4mAT8yC3HxOtniyGELlzGbNMLLeqe052ahFQ0h6LFq+lh5DsDIdYViIDst08abvSbcEdLXQ=="],
- "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.0.4" }, "os": "darwin", "cpu": "x64" }, "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q=="],
+ "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dqVSFynCox4C/J8kT16V7SIFAns0IjgLwkvYT7p8LQVmJ5OS5b6tI9IGflxTeuBS//zXeFIUbwt5dwxyZ17cnA=="],
- "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg=="],
+ "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3z0NHDxD6n5I9gc05U1eW1AyRm+Gznzq3naMrthPNqE6oYykcogW0l/jfpJdjYnuNl8R7yI9pNbE1XiUeyq0Aw=="],
- "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.0.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ=="],
+ "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.2", "", { "os": "linux", "cpu": "none" }, "sha512-bsb4rI+NldGOsXuej2r8OdSS8+zXDVaCWxyWrcv6kneTOlgAHtZABRzBBCwdsPiD90J4myNJuHpg6kA20ImW/w=="],
- "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.0.5", "", { "os": "linux", "cpu": "arm" }, "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g=="],
+ "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-/ABshyj8gCpyIrNXnHn4LorDJ0HHm1VhXPBlxZ8zAtfVPAaSafXPGn+sUSIRiwaSBy0mmFjSjiXI5mkcwdChKQ=="],
- "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA=="],
+ "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ITPEtgffGJ0S6G9dRyw/366tJQqFRcHWPHhC+Stpg3Z8AEMrDrTr2lhdz4f/Y/HMbRh//7Z5mBzEpVdi62Oc3w=="],
- "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.0.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA=="],
+ "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-zE9EdiUzUmg5mDT5a1rk5fYJ6GWPloTwWBYDS14naqHsL+EaMpDj1AWnpLgh3u0YCORv2Tt50wrcrpYqkP97Kw=="],
- "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw=="],
+ "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m0lrLiUt+lBYnCFr8qV/65yMR4E/c7/wf78I5eKTdkEakFAlZ9QlzEM3QIhhAwVeUhLAHLcCq7a7Vszq/oFNZQ=="],
- "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.0.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA=="],
+ "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.2" }, "os": "linux", "cpu": "arm" }, "sha512-affVWCTLooy8TSxbDx2qkzuDeaWLNVBA+P//FNBirHsXpP2fuBhk5AuboYUnrDnzoXes8GFjpTx0SBFOCRg+FA=="],
- "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.0.4", "", { "os": "linux", "cpu": "x64" }, "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw=="],
+ "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-QgKDspHPnrU+GQ55XPhGwyhC8acLVOOSyAvo1oVfFmrIXLkDNmGWzAfDZ4xK8oSA1qBQrALcHX0G5UZni/SuFQ=="],
- "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.0.5" }, "os": "linux", "cpu": "arm" }, "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ=="],
+ "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.2" }, "os": "linux", "cpu": "ppc64" }, "sha512-sMd8rDxmpLOwv/7N44klFjOD5DUO7FLdjiXDI0hoxYaf7Ar262dQIEkosE98bps+5HPLtp/EvNqeqQtOycP/IA=="],
- "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA=="],
+ "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.2" }, "os": "linux", "cpu": "none" }, "sha512-0Eob78yjlYPfL5vMNWAW55l3R9Y6BQS/gOfe0ZcP9mEz9ohhKSt4im1hayiknXgf8AWrFqMvJcKIdmLmEe7yeQ=="],
- "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.0.4" }, "os": "linux", "cpu": "s390x" }, "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q=="],
+ "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.2" }, "os": "linux", "cpu": "s390x" }, "sha512-KgAxQ0DxpNOq1rG2t5cgTgShJFGSuU7XO45cqC+1NVOuZnP6tlgZRuSYOfNupGkHID0o3cJOsw4DVeJpMovcGw=="],
- "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA=="],
+ "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-8pqvxubL2PGdhlPy6GLqzDYMUjyRmKAwKHYKixpdJYBUK7PJ0C029XdsnpFIdgRZG68fZiGdHVWcKPvtiPB4cA=="],
- "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" }, "os": "linux", "cpu": "arm64" }, "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g=="],
+ "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.2" }, "os": "linux", "cpu": "arm64" }, "sha512-Vz0iQjzzcSX3HCbfwFfCSG/9SCIqyO0mH2sXyiHaAYfBk0cRsCWXRyQYX0ovCK/PAQBbTzQ0dsPQHh5MAFL59w=="],
- "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.33.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.0.4" }, "os": "linux", "cpu": "x64" }, "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw=="],
+ "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.3", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.2" }, "os": "linux", "cpu": "x64" }, "sha512-6O1NPKcDVj9QEdg7Hx549EX8U0rp6yXQERqru6yRN7fGBn32UvIRJUlWnk+8xDCiG76hXVBbX82NZ/ZKr0euIg=="],
- "@img/sharp-wasm32": ["@img/sharp-wasm32@0.33.5", "", { "dependencies": { "@emnapi/runtime": "^1.2.0" }, "cpu": "none" }, "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg=="],
+ "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.3", "", { "dependencies": { "@emnapi/runtime": "^1.11.1" } }, "sha512-cZ0XkcYGpHZkqW6iCkqTcmUC0CD9DhD5d/qeZlZkfRBn6GnHniZXLUo5+9xw8Iv76YE6LQFN9YNBlKREcCG76w=="],
- "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.33.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ=="],
+ "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.3", "", { "dependencies": { "@img/sharp-wasm32": "0.35.3" }, "cpu": "none" }, "sha512-2rnq7bX3NzeR2T4YWgz8qiG4h3TSdMe+vN1iQXpJleSJ3SM5zQ8Fy2SyyXAWlbxpEZ2Y+Z4u1BePgJEYbSy80Q=="],
- "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.33.5", "", { "os": "win32", "cpu": "x64" }, "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg=="],
+ "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-4bPwFdMbeC4JQ8L8LOyWp6nsHcboP5fxkp6iPOXz2Vg49R42TuMs2whkJ5OAP4/Ul035qOzy0AecOF9VOscn4w=="],
- "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
+ "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-r53mXsBN6lFUDiST764SvgwUdHAqM4rPAiDzAmf4fLoB6X/rkfyTrLCg6+g17wJJiCmB3JYgHuUldCWUIRFSXw=="],
- "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
+ "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.3", "", { "os": "win32", "cpu": "x64" }, "sha512-D4y1vNeZrIIJCN+uHaWVtH86B+aCrdMYYjicy9pXHvbGZeGYLLSd3wdVuC37FxVXlU1ARsk84eKWfWMXGYEqvA=="],
+
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
+
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
- "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
- "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
- "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
+ "@libsql/client": ["@libsql/client@0.17.4", "", { "dependencies": { "@libsql/core": "^0.17.4", "@libsql/hrana-client": "^0.10.0", "js-base64": "^3.7.5", "libsql": "^0.5.28", "promise-limit": "^2.7.0" } }, "sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw=="],
- "@mongodb-js/saslprep": ["@mongodb-js/saslprep@1.1.9", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw=="],
+ "@libsql/core": ["@libsql/core@0.17.4", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ=="],
- "@next/env": ["@next/env@15.1.6", "", {}, "sha512-d9AFQVPEYNr+aqokIiPLNK/MTyt3DWa/dpKveiAaVccUadFbhFEvY6FXYX2LJO2Hv7PHnLBu2oWwB4uBuHjr/w=="],
+ "@libsql/darwin-arm64": ["@libsql/darwin-arm64@0.5.29", "", { "os": "darwin", "cpu": "arm64" }, "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A=="],
- "@next/eslint-plugin-next": ["@next/eslint-plugin-next@15.1.1", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-yACipsS2HI9ktcfz/1UsO0/sDbVjXWKDE/fzzMLnYES+K4KJyqHChyBQeaxiK7/NDnxrdk7Ow2i9LRm3ZTAWow=="],
+ "@libsql/darwin-x64": ["@libsql/darwin-x64@0.5.29", "", { "os": "darwin", "cpu": "x64" }, "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ=="],
- "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-u7lg4Mpl9qWpKgy6NzEkz/w0/keEHtOybmIl0ykgItBxEM5mYotS5PmqTpo+Rhg8FiOiWgwr8USxmKQkqLBCrw=="],
+ "@libsql/hrana-client": ["@libsql/hrana-client@0.10.0", "", { "dependencies": { "@libsql/isomorphic-ws": "^0.1.5", "js-base64": "^3.7.5" } }, "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw=="],
- "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.1.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-x1jGpbHbZoZ69nRuogGL2MYPLqohlhnT9OCU6E6QFewwup+z+M6r8oU47BTeJcWsF2sdBahp5cKiAcDbwwK/lg=="],
+ "@libsql/isomorphic-ws": ["@libsql/isomorphic-ws@0.1.5", "", { "dependencies": { "@types/ws": "^8.5.4", "ws": "^8.13.0" } }, "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg=="],
- "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-jar9sFw0XewXsBzPf9runGzoivajeWJUc/JkfbLTC4it9EhU8v7tCRLH7l5Y1ReTMN6zKJO0kKAGqDk8YSO2bg=="],
+ "@libsql/linux-arm-gnueabihf": ["@libsql/linux-arm-gnueabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ=="],
- "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.1.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-+n3u//bfsrIaZch4cgOJ3tXCTbSxz0s6brJtU3SzLOvkJlPQMJ+eHVRi6qM2kKKKLuMY+tcau8XD9CJ1OjeSQQ=="],
+ "@libsql/linux-arm-musleabihf": ["@libsql/linux-arm-musleabihf@0.5.29", "", { "os": "linux", "cpu": "arm" }, "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg=="],
- "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-SpuDEXixM3PycniL4iVCLyUyvcl6Lt0mtv3am08sucskpG0tYkW1KlRhTgj4LI5ehyxriVVcfdoxuuP8csi3kQ=="],
+ "@libsql/linux-arm64-gnu": ["@libsql/linux-arm64-gnu@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w=="],
- "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.1.6", "", { "os": "linux", "cpu": "x64" }, "sha512-L4druWmdFSZIIRhF+G60API5sFB7suTbDRhYWSjiw0RbE+15igQvE2g2+S973pMGvwN3guw7cJUjA/TmbPWTHQ=="],
+ "@libsql/linux-arm64-musl": ["@libsql/linux-arm64-musl@0.5.29", "", { "os": "linux", "cpu": "arm64" }, "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg=="],
- "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.1.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-s8w6EeqNmi6gdvM19tqKKWbCyOBvXFbndkGHl+c9YrzsLARRdCHsD9S1fMj8gsXm9v8vhC8s3N8rjuC/XrtkEg=="],
+ "@libsql/linux-x64-gnu": ["@libsql/linux-x64-gnu@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg=="],
- "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.1.6", "", { "os": "win32", "cpu": "x64" }, "sha512-6xomMuu54FAFxttYr5PJbEfu96godcxBTRk1OhAvJq0/EnmFU/Ybiax30Snis4vdWZ9LGpf7Roy5fSs7v/5ROQ=="],
+ "@libsql/linux-x64-musl": ["@libsql/linux-x64-musl@0.5.29", "", { "os": "linux", "cpu": "x64" }, "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w=="],
- "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+ "@libsql/win32-x64-msvc": ["@libsql/win32-x64-msvc@0.5.29", "", { "os": "win32", "cpu": "x64" }, "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg=="],
- "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
+ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
- "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
+ "@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="],
- "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
+ "@next/env": ["@next/env@16.2.11", "", {}, "sha512-0do5A3BJ2gxWr0ZCMcD6BhW+e595jyxdTl3rXTS6lOtD8ektMiW6CO+EPwt1Eca1DBnm90r/7GdiKWBKxH++DA=="],
- "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
+ "@next/eslint-plugin-next": ["@next/eslint-plugin-next@16.2.11", "", { "dependencies": { "fast-glob": "3.3.1" } }, "sha512-vMEf/aXOpzFFdtIvFYOnIDPKb0xBbrXONsz83CcKdRrekfxNdL8PNkq5qHqAHSXVlIifnX68LOMaxr3z5PkeLQ=="],
- "@radix-ui/number": ["@radix-ui/number@1.1.0", "", {}, "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="],
+ "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wryL4pjKmDwGv2ox6+GZDFxvmtSRLqApBR8kL1j4+vhB7Z5vJC/zAnXpiR9Xkfzl0AS8WLMnsuGV/UKI67/rrw=="],
- "@radix-ui/primitive": ["@radix-ui/primitive@1.1.1", "", {}, "sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA=="],
+ "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-aZl2j4f/fLyjQvOhv0Oe9UaMAQHolYpKhctsoYzplSumKJKPUmgjcf6545aBtysLTcu994TREd0+pSgNE4ohmg=="],
- "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dialog": "1.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p4XnPqgej8sZAAReCAKgz1REYZEBLR8hU9Pg27wFnCWIMc8g1ccCs0FjBcy05V15VTu8pAePw/VDYeOm/uZ6yQ=="],
+ "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-5jEriyEnH/LWFy27L2ZG0XaLlyEJIjhsImEsiS9P563PKEVp2BVups/xfOucIrsvVntp11oNcZwjHvaDPYVB5g=="],
- "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg=="],
+ "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-eIjcpx2fnnFSSkZDbTxy74KnokUXDjfoLClpWelfgHLf621aTqswhwXQ7GkD5K5rplrS6LZ/Bj+mVuvzluBOEg=="],
- "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.3", "", { "dependencies": { "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Paen00T4P8L8gd9bNsRMw7Cbaz85oxiv+hzomsRZgFm2byltPFDtfcoqlWJ8GyZlIBWgLssJlzLCnKU0G0302g=="],
+ "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-8WgzpaWMs46qJT9kiV47cje86L0x/Mu9t8/Gwj+pnbgW3rETVfCnaScPjlYUwNScpOozdcIMHWmAvuZJUonR2w=="],
- "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wP0CPAHq+P5I4INKe3hJrIa1WoNqqrejzW+zoU0rOvo1b9gDEJJFl2rYfO1PYJUQCc2H1WZxIJmyv9BS8i5fLw=="],
+ "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.11", "", { "os": "linux", "cpu": "x64" }, "sha512-I3UgPds7G4ZYnTb/H+5GBGuUT2DhAk6j0mL6A4s63RjFs74wB2hOWP0vaxsK+3NJraExt3eYEPQ/UtT0x/64Nw=="],
- "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PliMB63vxz7vggcyq0IxNYk8vGDrLXVWw4+W4B8YnwI1s18x7YZYqlG9PLX7XxAJUi0g2DxP4XKJMFHh/iVh9A=="],
+ "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-n89CjtcThnjrwgJMAiI5xbqwLY51zvwC9tSlArmVndAJLYVl9T9UAdlkXTmZvE++idoXe8KdglQlhNRdUp1c6g=="],
- "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA=="],
+ "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.11", "", { "os": "win32", "cpu": "x64" }, "sha512-md8CLNggS1Dx9pUgApzps5uAf+N8GN9xywzmNx9vHAWo94HtBwCCqkSnhIrdfQe83Dhz8Lfo/20Nb1Zxal092w=="],
+
+ "@noble/ciphers": ["@noble/ciphers@2.2.0", "", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="],
+
+ "@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
+
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
+
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
- "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw=="],
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
- "@radix-ui/react-context": ["@radix-ui/react-context@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q=="],
+ "@nolyfill/is-core-module": ["@nolyfill/is-core-module@1.0.39", "", {}, "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA=="],
- "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-menu": "2.1.5", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MY5PFCwo/ICaaQtpQBQ0g19AyjzI0mhz+a2GUWA2pJf4XFkvglAdcgDV2Iqm+lLbXn8hb+6rbLgcmRtc6ImPvg=="],
+ "@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
- "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/IVhJV5AceX620DUJ4uYVMymzsipdKBzo3edo+omeskCKGm9FRHM0ebIdbPnlQVJqyuHbuBltQUOG2mOTq2IYw=="],
+ "@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.220.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-CmVa4ImJ+ynfrPMNaAXHET6Bhb44SwzmfyVJFq9ni2jgXJR/l7C6gfVFddNmHP+ZOkP9cf4f9DBe68qVLTHc9w=="],
- "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg=="],
+ "@opentelemetry/core": ["@opentelemetry/core@2.10.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-/wNZ8twnEQQA4HoHu22+vcsdru6pWPWxW+7w+FlxT6Id7PE/WIbZmVKkte+PF72e0F2dnImFeHD2syyE1Mw6MQ=="],
- "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg=="],
+ "@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.220.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.220.0", "import-in-the-middle": "^3.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-xQx3E2WxP1mDvKzxLxX+CTCtNLa560YJZ3087qYHerl2YmiKpv7AH+dAy7vmx+eVrZ5BwhfWUAVoKOoxCNHcpw=="],
- "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-menu": "2.1.6", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-no3X7V5fD487wab/ZYSHXq3H37u4NVeLDKI/Ks724X/eEFSSEFYZxWgsIlr1UBeEyDaM29HM5x9p1Nv8DuTYPA=="],
+ "@opentelemetry/resources": ["@opentelemetry/resources@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-q6MMm2zhggzsHVNbabYwut+a6nbuQQe3URUoxaojM/8K1IBfwwPzvxIjNi2/lI1TFe+fMHMW9MWhrtDLEXEnkA=="],
- "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg=="],
+ "@opentelemetry/sdk-trace": ["@opentelemetry/sdk-trace@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-MfQGq3GRmTh5fM/y+OjaO0vj6+luCB1XO2gfXCalKCfgKw0eHL++sm75DNweC6ohlp+aFvACqeE0fYayqdRaoQ=="],
- "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA=="],
+ "@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.10.0", "", { "dependencies": { "@opentelemetry/core": "2.10.0", "@opentelemetry/resources": "2.10.0", "@opentelemetry/sdk-trace": "2.10.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-GuYQQT7QD2EeO8lcZLRQzcbOyhqAzL+6WWTKTU9mSUBYBazkEDl+VrQcXQhbB08OWM9anD1aHleVadzulpOaUQ=="],
- "@radix-ui/react-icons": ["@radix-ui/react-icons@1.3.2", "", { "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g=="],
+ "@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.43.0", "", {}, "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg=="],
- "@radix-ui/react-id": ["@radix-ui/react-id@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA=="],
+ "@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="],
- "@radix-ui/react-label": ["@radix-ui/react-label@2.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zo1uGMTaNlHehDyFQcDZXRJhUPDuukcnHz0/jnrup0JA6qL+AFpAnty+7VKa9esuU5xTblAZzTGYJKSKaBxBhw=="],
+ "@paulirish/trace_engine": ["@paulirish/trace_engine@0.0.65", "", { "dependencies": { "legacy-javascript": "latest", "third-party-web": "latest" } }, "sha512-Qsm6F5C8xf6ZzQXbQc2+wcpe6sggfs/gvc/ytqSurdvYg3kyW0ECHCqE0CWBKZpqgjVfPNX9c7SCS3r2nEIRGg=="],
- "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-roving-focus": "1.1.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uH+3w5heoMJtqVCgYOtYVMECk1TOrkUn0OG0p5MqXC0W2ppcuVeESbou8PTHoqAjbdTEK19AGXBWcEtR5WpEQg=="],
+ "@playwright/test": ["@playwright/test@1.61.1", "", { "dependencies": { "playwright": "1.61.1" }, "bin": { "playwright": "cli.js" } }, "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig=="],
- "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg=="],
+ "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
- "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.2", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA=="],
+ "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
- "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.4", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA=="],
+ "@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
- "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg=="],
+ "@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
- "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.2", "", { "dependencies": { "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w=="],
+ "@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
- "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.2.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xtCsqt8Rp09FK50ItqEqTJ7Sxanz8EM8dnkVIhJrc/wkMMomSmXHvYbhv3E7Zx4oXh98aaLt9W679SUYXg4IDA=="],
+ "@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
- "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zgMQWkNO169GtGqRvYrzb0Zf8NhMHS2DuEB/TiEmVnpr5OqPU3i8lfbxaAmC2J/KYuIQxyoQQ6DxepyXp61/xw=="],
+ "@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
- "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.3", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l7+NNBfBYYJa9tNqVcP2AGvxdE3lmE6kFTBXdvHgUaZuy+4wGCL1Cl2AfaR7RKyimj7lZURGLwFO59k4eBnDJQ=="],
+ "@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
- "@radix-ui/react-select": ["@radix-ui/react-select@2.1.5", "", { "dependencies": { "@radix-ui/number": "1.1.0", "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-eVV7N8jBXAXnyrc+PsOF89O9AfVgGnbLxUtBb0clJ8y8ENMWLARGMI/1/SBRLz7u4HqxLgN71BJ17eono3wcjA=="],
+ "@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
- "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oZfHcaAp2Y6KFBX6I5P1u7CQoy4lheCGiYj+pGFrHy8E/VNRb5E39TkTr3JrV520csPBTZjkuKFdEsjS5EUNKQ=="],
+ "@puppeteer/browsers": ["@puppeteer/browsers@3.0.6", "", { "dependencies": { "modern-tar": "^0.7.6", "yargs": "^18.0.0" }, "peerDependencies": { "proxy-agent": ">=8.0.1", "yauzl": "^2.10.0 || ^3.4.0" }, "optionalPeers": ["proxy-agent", "yauzl"], "bin": { "browsers": "lib/main-cli.js" } }, "sha512-B/gKoqlFkzhvzsI6jo9K1cZz9o5ypviVv/xu8CwA4grZzyVwN+XfkT+tu8T1zrauuEXv6VhS2oGX+6NL95WcKA=="],
- "@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ=="],
+ "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="],
- "@radix-ui/react-switch": ["@radix-ui/react-switch@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-previous": "1.1.0", "@radix-ui/react-use-size": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g=="],
+ "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="],
- "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng=="],
+ "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="],
- "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZzUsAaOx8NdXZZKcFNDhbSlbsCUy8qQWmzTdgrlrhhZAOx2ofLtKrBDW9fkqhFvXgmtv560Uj16pkLkqML7SHA=="],
+ "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="],
- "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lntKchNWx3aCHuWKiDY+8WudiegQvBpDRAYL8dKLRvKEH8VOpl0XX6SSU/bUBqIRJbcTy4+MW06Wv8vgp10rzQ=="],
+ "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="],
- "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-toggle": "1.1.2", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JBm6s6aVG/nwuY5eadhU2zDi/IwYS0sDM5ZWb4nymv/hn3hZdkw+gENn0LP4iY1yCd7+bgJaCwueMYJIU3vk4A=="],
+ "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="],
- "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.1", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ss0s80BC0+g0+Zc53MvilcnTYSOi4mSuFWBPYPuTOFGjx+pUU+ZrmamMNwS56t8MTFlniA5ocjd4jYm/CdhbOg=="],
+ "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="],
- "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw=="],
+ "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="],
- "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw=="],
+ "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="],
- "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.0", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw=="],
+ "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="],
- "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w=="],
+ "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="],
- "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.0", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og=="],
+ "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="],
- "@radix-ui/react-use-rect": ["@radix-ui/react-use-rect@1.1.0", "", { "dependencies": { "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ=="],
+ "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="],
- "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.0", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw=="],
+ "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="],
- "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg=="],
+ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="],
- "@radix-ui/rect": ["@radix-ui/rect@1.1.0", "", {}, "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="],
+ "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
- "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.10.5", "", {}, "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A=="],
+ "@sentry/conventions": ["@sentry/conventions@0.16.0", "", {}, "sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ=="],
+
+ "@sentry/core": ["@sentry/core@10.67.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0" } }, "sha512-b6U3pJ8AUvN9aouq0vl+VZI8KT8RslBsfGMFuNwRr313zOmdmFJBZqTiUw9VGgJ2jGKxLO9alm9rlxBfX4hf+w=="],
+
+ "@sentry/node": ["@sentry/node@10.67.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.1", "@opentelemetry/instrumentation": "^0.220.0", "@opentelemetry/sdk-trace-base": "^2.9.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0", "@sentry/node-core": "10.67.0", "@sentry/opentelemetry": "10.67.0", "@sentry/server-utils": "10.67.0", "import-in-the-middle": "^3.0.0" } }, "sha512-SFKpZGqOCEFSmP93NdDP6ikZp4NS7A/JR8+2ofK3jF6Y9Vyox7pX0pxdOnPLpFcPtMybMahfWqSAWJvsFs4RmA=="],
+
+ "@sentry/node-core": ["@sentry/node-core@10.67.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0", "@sentry/opentelemetry": "10.67.0", "import-in-the-middle": "^3.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/exporter-trace-otlp-http": ">=0.57.0 <1", "@opentelemetry/instrumentation": ">=0.57.1 <1", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/core", "@opentelemetry/exporter-trace-otlp-http", "@opentelemetry/instrumentation", "@opentelemetry/sdk-trace-base"] }, "sha512-dBHHRwZyan1pOnFJ+sNBvR8TkXbZAfZU/jpxmALS3JZ2/8AGR7cQKL+b7SleKuJ7iUDZyklN3Nqi0i5JkcA+HA=="],
+
+ "@sentry/opentelemetry": ["@sentry/opentelemetry@10.67.0", "", { "dependencies": { "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0" }, "peerDependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/core": "^1.30.1 || ^2.1.0", "@opentelemetry/sdk-trace-base": "^1.30.1 || ^2.1.0" } }, "sha512-oLTOrAK1rOqmYRktOJZwz37B1seXPx1W2FTMVtzTVNjMFA/LZwGzePeZzhUOgzZgfLHixMd/ceWtGqoxAndcjQ=="],
+
+ "@sentry/server-utils": ["@sentry/server-utils@10.67.0", "", { "dependencies": { "@apm-js-collab/code-transformer-bundler-plugins": "^0.7.1", "@apm-js-collab/tracing-hooks": "^0.13.0", "@sentry/conventions": "^0.16.0", "@sentry/core": "10.67.0" } }, "sha512-GQ9t+RSTx5s3b/aZrLFuL4nrwPLMah5NiZk5cjxJmgmOSgm3nMdO8gdqCedDch5B13F3YsxtaQYjSJnzLz8M5A=="],
- "@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
+ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
- "@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
+ "@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
- "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
+ "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
- "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
+ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
- "@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
+ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
+
+ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
"@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
- "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
+ "@types/node": ["@types/node@24.10.13", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg=="],
- "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
+ "@types/pg": ["@types/pg@8.6.1", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w=="],
- "@types/node": ["@types/node@20.17.16", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-vOTpLduLkZXePLxHiHsBLp98mHGnl8RptV4YAO3HfKO5UHjDvySGbxKtpYfy8Sx5+WKcgc45qNreJJRVM3L6mw=="],
+ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
- "@types/react": ["@types/react@19.0.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="],
+ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
- "@types/react-dom": ["@types/react-dom@19.0.3", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA=="],
+ "@types/retry": ["@types/retry@0.12.0", "", {}, "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA=="],
- "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
+ "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
- "@types/webidl-conversions": ["@types/webidl-conversions@7.0.3", "", {}, "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="],
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.65.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/type-utils": "8.65.0", "@typescript-eslint/utils": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA=="],
- "@types/whatwg-url": ["@types/whatwg-url@11.0.5", "", { "dependencies": { "@types/webidl-conversions": "*" } }, "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ=="],
+ "@typescript-eslint/parser": ["@typescript-eslint/parser@8.65.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA=="],
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.22.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/type-utils": "8.22.0", "@typescript-eslint/utils": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw=="],
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.65.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.65.0", "@typescript-eslint/types": "^8.65.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q=="],
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.22.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ=="],
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0" } }, "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg=="],
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0" } }, "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ=="],
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.65.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg=="],
- "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.22.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "8.22.0", "@typescript-eslint/utils": "8.22.0", "debug": "^4.3.4", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA=="],
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g=="],
- "@typescript-eslint/types": ["@typescript-eslint/types@8.22.0", "", {}, "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A=="],
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.65.0", "", {}, "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg=="],
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.0.0" }, "peerDependencies": { "typescript": ">=4.8.4 <5.8.0" } }, "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w=="],
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.65.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.65.0", "@typescript-eslint/tsconfig-utils": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/visitor-keys": "8.65.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg=="],
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.22.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.22.0", "@typescript-eslint/types": "8.22.0", "@typescript-eslint/typescript-estree": "8.22.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg=="],
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.65.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.65.0", "@typescript-eslint/types": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA=="],
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.22.0", "", { "dependencies": { "@typescript-eslint/types": "8.22.0", "eslint-visitor-keys": "^4.2.0" } }, "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w=="],
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.65.0", "", { "dependencies": { "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A=="],
- "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
+ "@vitest/expect": ["@vitest/expect@4.1.10", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA=="],
- "@vercel/speed-insights": ["@vercel/speed-insights@1.1.0", "", { "peerDependencies": { "@sveltejs/kit": "^1 || ^2", "next": ">= 13", "react": "^18 || ^19 || ^19.0.0-rc", "svelte": ">= 4", "vue": "^3", "vue-router": "^4" }, "optionalPeers": ["@sveltejs/kit", "next", "react", "svelte", "vue", "vue-router"] }, "sha512-rAXxuhhO4mlRGC9noa5F7HLMtGg8YF1zAN6Pjd1Ny4pII4cerhtwSG4vympbCl+pWkH7nBS9kVXRD4FAn54dlg=="],
+ "@vitest/mocker": ["@vitest/mocker@4.1.10", "", { "dependencies": { "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow=="],
- "acorn": ["acorn@8.14.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA=="],
+ "@vitest/pretty-format": ["@vitest/pretty-format@4.1.10", "", { "dependencies": { "tinyrainbow": "^3.1.0" } }, "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q=="],
- "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
+ "@vitest/runner": ["@vitest/runner@4.1.10", "", { "dependencies": { "@vitest/utils": "4.1.10", "pathe": "^2.0.3" } }, "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg=="],
- "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
+ "@vitest/snapshot": ["@vitest/snapshot@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" } }, "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw=="],
- "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
+ "@vitest/spy": ["@vitest/spy@4.1.10", "", {}, "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw=="],
- "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+ "@vitest/utils": ["@vitest/utils@4.1.10", "", { "dependencies": { "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA=="],
- "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
+ "acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
- "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
+ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
- "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
- "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
+ "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
+
+ "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
- "aria-hidden": ["aria-hidden@1.2.4", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A=="],
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+ "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="],
- "array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
+ "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="],
"array.prototype.findlast": ["array.prototype.findlast@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ=="],
- "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.5", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-shim-unscopables": "^1.0.2" } }, "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ=="],
+ "array.prototype.findlastindex": ["array.prototype.findlastindex@1.2.6", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-shim-unscopables": "^1.1.0" } }, "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ=="],
"array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="],
@@ -381,133 +510,135 @@
"arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="],
+ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="],
+
"ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
+ "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
+
"async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
+ "atomically": ["atomically@2.1.1", "", { "dependencies": { "stubborn-fs": "^2.0.0", "when-exit": "^2.1.4" } }, "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ=="],
+
"available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
- "axe-core": ["axe-core@4.10.2", "", {}, "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w=="],
+ "axe-core": ["axe-core@4.12.1", "", {}, "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
- "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
-
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
- "base64-arraybuffer": ["base64-arraybuffer@1.0.2", "", {}, "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ=="],
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.11.0", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-oCu2wfipvX3AePSgmOuKkIywOu+8n9psz7hXYmk56ghpu3+7KzNIBopaOs4c9BrtdnTtW30unG9GTfHo7EwERQ=="],
- "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
+ "better-auth": ["better-auth@1.6.23", "", { "dependencies": { "@better-auth/core": "1.6.23", "@better-auth/drizzle-adapter": "1.6.23", "@better-auth/kysely-adapter": "1.6.23", "@better-auth/memory-adapter": "1.6.23", "@better-auth/mongo-adapter": "1.6.23", "@better-auth/prisma-adapter": "1.6.23", "@better-auth/telemetry": "1.6.23", "@better-auth/utils": "0.4.2", "@better-fetch/fetch": "1.3.1", "@noble/ciphers": "^2.1.1", "@noble/hashes": "^2.0.1", "better-call": "1.3.7", "defu": "^6.1.4", "jose": "^6.1.3", "kysely": "^0.28.17 || ^0.29.0", "nanostores": "^1.1.1", "zod": "^4.3.6" }, "peerDependencies": { "@lynx-js/react": "*", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "@sveltejs/kit": "^2.0.0", "@tanstack/react-start": "^1.0.0", "@tanstack/solid-start": "^1.0.0", "better-sqlite3": "^12.0.0", "drizzle-kit": ">=0.31.4", "drizzle-orm": "^0.45.2", "mongodb": "^6.0.0 || ^7.0.0", "mysql2": "^3.0.0", "next": "^14.0.0 || ^15.0.0 || ^16.0.0", "pg": "^8.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "solid-js": "^1.0.0", "svelte": "^4.0.0 || ^5.0.0", "vitest": "^2.0.0 || ^3.0.0 || ^4.0.0", "vue": "^3.0.0" }, "optionalPeers": ["@lynx-js/react", "@prisma/client", "@sveltejs/kit", "@tanstack/react-start", "@tanstack/solid-start", "better-sqlite3", "drizzle-kit", "drizzle-orm", "mongodb", "mysql2", "next", "pg", "prisma", "react", "react-dom", "solid-js", "svelte", "vitest", "vue"] }, "sha512-4vOaRd9UiKGKm9R+ej0jjU1es3MiJIiNc9Qq3VCnYqOZ4/nb5272QqTxWYoDxyUXl5x6A2x2we5KZKQO9teTQQ=="],
+
+ "better-call": ["better-call@1.3.7", "", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-Al51/hjp2SSp6CRTa3F2ptcx4yQVS1xWKoY6jcVXqNYOap6mHFP2jUBn5EwIL4iIed1/Sq4hlQ+Umm6EflZG+w=="],
+
+ "bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
"brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="],
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
- "bson": ["bson@6.10.2", "", {}, "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg=="],
+ "browserslist": ["browserslist@4.28.7", "", { "dependencies": { "baseline-browser-mapping": "^2.10.44", "caniuse-lite": "^1.0.30001806", "electron-to-chromium": "^1.5.393", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw=="],
+
+ "buffer-equal-constant-time": ["buffer-equal-constant-time@1.0.1", "", {}, "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="],
- "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
+ "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.1", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g=="],
- "call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+ "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
- "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
-
"caniuse-lite": ["caniuse-lite@1.0.30001696", "", {}, "sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ=="],
- "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
+ "chai": ["chai@6.2.2", "", {}, "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
- "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
-
- "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
-
- "character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
+ "chrome-launcher": ["chrome-launcher@1.2.1", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-qmFR5PLMzHyuNJHwOloHPAHhbaNglkfeV/xDtt5b7xiFFyU1I+AZZX0PYseMuhenJSSirgxELYIbswcoc+5H4A=="],
- "character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
+ "chromium-bidi": ["chromium-bidi@16.0.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-J63PGu/9PpeCwLIcKYyzWP6yaVL5pxuBc0shlYCYM8BaAkmlwiQboXO1iNbOgSDbVklEyYFfNEcHD8oOAWacUA=="],
- "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
-
- "class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
+ "cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
- "cloudinary": ["cloudinary@2.5.1", "", { "dependencies": { "lodash": "^4.17.21", "q": "^1.5.1" } }, "sha512-CNg6uU53Hl4FEVynkTGpt5bQEAQWDHi3H+Sm62FzKf5uQHipSN2v7qVqS8GRVqeb0T1WNV+22+75DOJeRXYeSQ=="],
-
- "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
-
- "cmdk": ["cmdk@1.0.4", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.0", "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg=="],
-
- "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
- "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
-
- "comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
- "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
+ "configstore": ["configstore@7.1.0", "", { "dependencies": { "atomically": "^2.0.3", "dot-prop": "^9.0.0", "graceful-fs": "^4.2.11", "xdg-basedir": "^5.1.0" } }, "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg=="],
- "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
- "css-line-break": ["css-line-break@2.1.0", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w=="],
-
- "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
+ "csp_evaluator": ["csp_evaluator@1.1.8", "", {}, "sha512-EwOnfYuNbTytvbMKsLixTrRgnjOa0WZCxGy8A9nnSYAicrdwn+T/epU/yjgymmOxlgKnvH+8wXt+7p/8ak5Feg=="],
- "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
"damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="],
+ "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="],
+
"data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="],
"data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="],
"data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="],
- "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
- "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
-
- "decode-named-character-reference": ["decode-named-character-reference@1.0.2", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg=="],
+ "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="],
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
+ "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
+
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
- "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
+ "defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
- "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
- "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
+ "devtools-protocol": ["devtools-protocol@0.0.1663043", "", {}, "sha512-33aOY3ZnBP1dgZsshgaL+/XlsQleiFZgyUaDtdZkEa1nbZhVY1MoDeWjk+wxg25fU924l1ZJfoGNmjjeA/5s1w=="],
- "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
+ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
- "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
+ "dot-prop": ["dot-prop@9.0.0", "", { "dependencies": { "type-fest": "^4.18.2" } }, "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ=="],
- "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
+ "drizzle-kit": ["drizzle-kit@0.31.10", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
- "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
+ "drizzle-orm": ["drizzle-orm@0.45.2", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-kY0BSaTNYWnoDMVoyY8uxmyHjpJW1geOmBMdSSicKo9CIIWkSxMIj2rkeSR51b8KAPB7m+qysjuHme5nKP+E5Q=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
- "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
+ "ecdsa-sig-formatter": ["ecdsa-sig-formatter@1.0.11", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ=="],
+
+ "electron-to-chromium": ["electron-to-chromium@1.5.395", "", {}, "sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA=="],
"emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"enhanced-resolve": ["enhanced-resolve@5.18.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ=="],
- "es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+ "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
+
+ "es-abstract": ["es-abstract@1.24.2", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
@@ -515,55 +646,63 @@
"es-iterator-helpers": ["es-iterator-helpers@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", "iterator.prototype": "^1.1.4", "safe-array-concat": "^1.1.3" } }, "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w=="],
+ "es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
+
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
- "es-shim-unscopables": ["es-shim-unscopables@1.0.2", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw=="],
+ "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="],
"es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="],
+ "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="],
+
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
- "eslint": ["eslint@9.19.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", "@eslint/js": "9.19.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA=="],
+ "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="],
- "eslint-config-next": ["eslint-config-next@15.1.1", "", { "dependencies": { "@next/eslint-plugin-next": "15.1.1", "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.31.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-St2CvkRaqwbEXot9XdivAqeznlZt5bACFyOy821k67SVJEgw4ansmrwhtlFUTRdYGmgmqapFKy4RN2yz/znHcg=="],
+ "eslint-config-next": ["eslint-config-next@16.2.11", "", { "dependencies": { "@next/eslint-plugin-next": "16.2.11", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", "eslint-plugin-react-hooks": "^7.0.0", "globals": "16.4.0", "typescript-eslint": "^8.46.0" }, "peerDependencies": { "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "optionalPeers": ["typescript"] }, "sha512-FIpbK/dUyxUExchDB7eBg3k+VU8R2iR/Cx9/kqTBUTFv2bOIR9aRrpno4rvAQ9VhiPQAyFKNA2NlZwouGWtclA=="],
"eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.9", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.13.0", "resolve": "^1.22.4" } }, "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g=="],
"eslint-import-resolver-typescript": ["eslint-import-resolver-typescript@3.7.0", "", { "dependencies": { "@nolyfill/is-core-module": "1.0.39", "debug": "^4.3.7", "enhanced-resolve": "^5.15.0", "fast-glob": "^3.3.2", "get-tsconfig": "^4.7.5", "is-bun-module": "^1.0.2", "is-glob": "^4.0.3", "stable-hash": "^0.0.4" }, "peerDependencies": { "eslint": "*", "eslint-plugin-import": "*", "eslint-plugin-import-x": "*" }, "optionalPeers": ["eslint-plugin-import", "eslint-plugin-import-x"] }, "sha512-Vrwyi8HHxY97K5ebydMtffsWAn1SCR9eol49eCd5fJS4O1WV7PaAjbcjmbfJJSMz/t4Mal212Uz/fQZrOB8mow=="],
- "eslint-module-utils": ["eslint-module-utils@2.12.0", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg=="],
+ "eslint-module-utils": ["eslint-module-utils@2.14.0", "", { "dependencies": { "debug": "^3.2.7" } }, "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig=="],
- "eslint-plugin-import": ["eslint-plugin-import@2.31.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.8", "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.0", "hasown": "^2.0.2", "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.0", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A=="],
+ "eslint-plugin-import": ["eslint-plugin-import@2.32.0", "", { "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", "array.prototype.findlastindex": "^1.2.6", "array.prototype.flat": "^1.3.3", "array.prototype.flatmap": "^1.3.3", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", "eslint-module-utils": "^2.12.1", "hasown": "^2.0.2", "is-core-module": "^2.16.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "object.groupby": "^1.0.3", "object.values": "^1.2.1", "semver": "^6.3.1", "string.prototype.trimend": "^1.0.9", "tsconfig-paths": "^3.15.0" }, "peerDependencies": { "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA=="],
"eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="],
"eslint-plugin-react": ["eslint-plugin-react@7.37.4", "", { "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "sha512-BGP0jRmfYyvOyvMoRX/uoUeW+GqNj9y16bPQzqAHf3AYII/tDs+jMN0dBVkl88/OZwNGwrVFxE7riHsXVfy/LQ=="],
- "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.1.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw=="],
+ "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.1.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g=="],
- "eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="],
+ "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
- "eslint-visitor-keys": ["eslint-visitor-keys@4.2.0", "", {}, "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw=="],
+ "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
- "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
+ "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
- "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
+ "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="],
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
- "estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
+ "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
+ "expect-type": ["expect-type@1.4.0", "", {}, "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA=="],
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
- "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
+ "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
@@ -571,6 +710,10 @@
"fastq": ["fastq@1.19.0", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA=="],
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
+
+ "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
+
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
@@ -583,11 +726,9 @@
"for-each": ["for-each@0.3.4", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-kKaIINnFpzW6ffJNDjjyjrk21BkDx38c0xa/klsT8VzLCaMEefv4ZTacrcVR4DmgTeBra++jMDAfS/tS799YDw=="],
- "foreground-child": ["foreground-child@3.3.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "signal-exit": "^4.0.1" } }, "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg=="],
+ "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="],
- "framer-motion": ["framer-motion@12.4.1", "", { "dependencies": { "motion-dom": "^12.0.0", "motion-utils": "^12.0.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-5Ijbea3topSZjadQ0hgc/TcWj2ldMZmNREM7RvAhvsThYOA1HHOA8TT1yKvMu1YXP3jWaFwoZ6Vo9Nw+DUZrzA=="],
-
- "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+ "fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -595,9 +736,17 @@
"functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
- "get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+ "gaxios": ["gaxios@7.2.0", "", { "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "node-fetch": "^3.3.2" } }, "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg=="],
+
+ "gcp-metadata": ["gcp-metadata@8.1.2", "", { "dependencies": { "gaxios": "^7.0.0", "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" } }, "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg=="],
+
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
- "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
+ "get-east-asian-width": ["get-east-asian-width@1.6.0", "", {}, "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA=="],
+
+ "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
@@ -605,20 +754,20 @@
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
- "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
-
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
- "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
+ "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="],
"globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="],
+ "google-auth-library": ["google-auth-library@10.9.0", "", { "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^7.1.4", "gcp-metadata": "8.1.2", "google-logging-utils": "1.1.3", "jws": "^4.0.0" } }, "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg=="],
+
+ "google-logging-utils": ["google-logging-utils@1.1.3", "", {}, "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA=="],
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
- "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
-
"has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
@@ -633,38 +782,34 @@
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
- "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.2", "", { "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": "^6.0.0", "space-separated-tokens": "^2.0.0", "style-to-object": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg=="],
+ "hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
- "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
+ "hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
- "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
+ "http-link-header": ["http-link-header@1.1.4", "", {}, "sha512-xT3GPW6/ZbGuw4UvwHqErSCEjNUlwbQJuZn9/q5U4WEKfp2kENVCAlousG1zLxHeaQ/ffOHUNpWamvkbBW0eNw=="],
- "html2canvas": ["html2canvas@1.4.1", "", { "dependencies": { "css-line-break": "^2.1.0", "text-segmentation": "^1.0.3" } }, "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA=="],
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
- "import-fresh": ["import-fresh@3.3.0", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw=="],
+ "image-ssim": ["image-ssim@0.2.0", "", {}, "sha512-W7+sO6/yhxy83L0G7xR8YAc5Z5QFtYEXXRV6EaE8tuYBZJnA3gVgp3q7X7muhLZVodeb9UfvjSbwt9VJwjIYAg=="],
- "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
+ "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
- "inline-style-parser": ["inline-style-parser@0.2.4", "", {}, "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q=="],
+ "import-in-the-middle": ["import-in-the-middle@3.3.2", "", { "dependencies": { "cjs-module-lexer": "^2.2.0", "es-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-jTd2FfOgOWOdgjkHuk/1Ms8VKFXkPs15ymYBETw1sAOrO/dY3XeGVRWir9qBbw7pXr0T2eTFwfCZ+N02HmiNGA=="],
- "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
- "is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="],
+ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
- "is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="],
+ "intl-messageformat": ["intl-messageformat@10.7.18", "", { "dependencies": { "@formatjs/ecma402-abstract": "2.3.6", "@formatjs/fast-memoize": "2.2.7", "@formatjs/icu-messageformat-parser": "2.11.4", "tslib": "^2.8.0" } }, "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
- "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
-
"is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
"is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="],
- "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
-
"is-boolean-object": ["is-boolean-object@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng=="],
"is-bun-module": ["is-bun-module@1.3.0", "", { "dependencies": { "semver": "^7.6.3" } }, "sha512-DgXeu5UWI0IsMQundYb5UAOzm6G2eVnarJ0byP6Tm55iZNKceD59LNPA2L4VvsScTtHcw0yEkVwSf7PC+QoLSA=="],
@@ -677,7 +822,7 @@
"is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="],
- "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
+ "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
@@ -689,16 +834,14 @@
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
- "is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="],
-
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
+ "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
"is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
- "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
-
"is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
"is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="],
@@ -713,23 +856,33 @@
"is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="],
- "is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+ "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="],
"is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="],
+ "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
+
"isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
- "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
+ "jose": ["jose@6.2.4", "", {}, "sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA=="],
- "jiti": ["jiti@1.21.7", "", { "bin": { "jiti": "bin/jiti.js" } }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
+ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="],
+
+ "js-base64": ["js-base64@3.9.1", "", {}, "sha512-U73qptcvf/HIOauFOmqT3a0mDUp0MYlfd15oqoe9kqZt5XhiXVb+HG09sLvI9PQ9tZIBFS4nlErai8zbWazP0g=="],
+
+ "js-library-detector": ["js-library-detector@6.7.0", "", {}, "sha512-c80Qupofp43y4cJ7+8TTDN/AsDwLi5oOm/plBrWI+iQt485vKXCco+yVmOwEgdo9VOdsYTuV0UlTeetVPTriXA=="],
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
- "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
+ "js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
+
+ "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
+ "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="],
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
@@ -741,139 +894,109 @@
"jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
- "kareem": ["kareem@2.6.3", "", {}, "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q=="],
+ "jwa": ["jwa@2.0.1", "", { "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", "safe-buffer": "^5.0.1" } }, "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg=="],
+
+ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="],
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
+ "kysely": ["kysely@0.29.4", "", {}, "sha512-y5mVgQNkMbs1eK9Xyc0pmNdabN2wHhRYY/5r4W5HrUT1rYCEPeVNSj1RUJeSDKT3U0p+mXCvLgkrFuIafYI6BA=="],
+
"language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
"language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
- "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
-
- "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
-
- "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
-
- "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
-
- "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
-
- "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
-
- "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
-
- "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
-
- "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
-
- "lucide-react": ["lucide-react@0.469.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw=="],
+ "legacy-javascript": ["legacy-javascript@0.0.1", "", {}, "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg=="],
- "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
-
- "mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "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" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
-
- "mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "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" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
-
- "mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "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" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
+ "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
- "mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "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" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
+ "libsql": ["libsql@0.5.29", "", { "dependencies": { "@neon-rs/load": "^0.0.4", "detect-libc": "2.0.2" }, "optionalDependencies": { "@libsql/darwin-arm64": "0.5.29", "@libsql/darwin-x64": "0.5.29", "@libsql/linux-arm-gnueabihf": "0.5.29", "@libsql/linux-arm-musleabihf": "0.5.29", "@libsql/linux-arm64-gnu": "0.5.29", "@libsql/linux-arm64-musl": "0.5.29", "@libsql/linux-x64-gnu": "0.5.29", "@libsql/linux-x64-musl": "0.5.29", "@libsql/win32-x64-msvc": "0.5.29" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "arm", "x64", "arm64", ] }, "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg=="],
- "mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
+ "lighthouse": ["lighthouse@13.4.1", "", { "dependencies": { "@paulirish/trace_engine": "0.0.65", "@sentry/node": "^10.0.0", "axe-core": "^4.12.1", "chrome-launcher": "^1.2.1", "configstore": "^7.0.0", "csp_evaluator": "1.1.8", "devtools-protocol": "0.0.1663043", "enquirer": "^2.3.6", "http-link-header": "^1.1.1", "intl-messageformat": "^10.5.3", "jpeg-js": "^0.4.4", "js-library-detector": "^6.7.0", "lighthouse-logger": "^2.0.2", "lighthouse-stack-packs": "1.12.3", "lodash-es": "^4.17.21", "lookup-closest-locale": "6.2.0", "open": "^8.4.0", "puppeteer-core": "^25.3.0", "robots-parser": "^3.0.1", "speedline-core": "^1.4.3", "third-party-web": "^0.29.2", "tldts-icann": "^7.4.9", "web-features": "^3.34.0", "ws": "^7.0.0", "yargs": "^17.3.1", "yargs-parser": "^21.0.0" }, "bin": { "lighthouse": "cli/index.js", "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js", "chrome-debug": "core/scripts/manual-chrome-launcher.js" } }, "sha512-fDu8lt3QLK/lTqIxtp1HkzQNJ32rsFHhbadYOepcMZFLgA8oINhxutMbMv8XXnpTOvZ0TXCo4JCk1LDTWaRLnA=="],
- "mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "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" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
+ "lighthouse-logger": ["lighthouse-logger@2.0.2", "", { "dependencies": { "debug": "^4.4.1", "marky": "^1.2.2" } }, "sha512-vWl2+u5jgOQuZR55Z1WM0XDdrJT6mzMP8zHUct7xTlWhuQs+eV0g+QL0RQdFjT54zVmbhLCP8vIVpy1wGn/gCg=="],
- "mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "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" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
+ "lighthouse-stack-packs": ["lighthouse-stack-packs@1.12.3", "", {}, "sha512-d8IsOpE83kbANgnM+Tp8+x6HcMpX9o2ITBiUERssgzAIFdZCQzs/f4k6D0DLQTE59enml9mbAOU52Wu35exWtg=="],
- "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
+ "lightningcss": ["lightningcss@1.33.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.33.0", "lightningcss-darwin-arm64": "1.33.0", "lightningcss-darwin-x64": "1.33.0", "lightningcss-freebsd-x64": "1.33.0", "lightningcss-linux-arm-gnueabihf": "1.33.0", "lightningcss-linux-arm64-gnu": "1.33.0", "lightningcss-linux-arm64-musl": "1.33.0", "lightningcss-linux-x64-gnu": "1.33.0", "lightningcss-linux-x64-musl": "1.33.0", "lightningcss-win32-arm64-msvc": "1.33.0", "lightningcss-win32-x64-msvc": "1.33.0" } }, "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA=="],
- "memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.33.0", "", { "os": "android", "cpu": "arm64" }, "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg=="],
- "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.33.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg=="],
- "micromark": ["micromark@4.0.1", "", { "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" } }, "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw=="],
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.33.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ=="],
- "micromark-core-commonmark": ["micromark-core-commonmark@2.0.2", "", { "dependencies": { "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" } }, "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w=="],
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.33.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg=="],
- "micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.33.0", "", { "os": "linux", "cpu": "arm" }, "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ=="],
- "micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg=="],
- "micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.33.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ=="],
- "micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "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" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg=="],
- "micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "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" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.33.0", "", { "os": "linux", "cpu": "x64" }, "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw=="],
- "micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.33.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA=="],
- "micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.33.0", "", { "os": "win32", "cpu": "x64" }, "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA=="],
- "micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
+ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
- "micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
+ "lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
- "micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
+ "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
- "micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "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" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
+ "long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
- "micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
+ "lookup-closest-locale": ["lookup-closest-locale@6.2.0", "", {}, "sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ=="],
- "micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
- "micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
+ "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
- "micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
+ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
- "micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
+ "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
- "micromark-util-subtokenize": ["micromark-util-subtokenize@2.0.4", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ=="],
+ "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
- "micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
- "micromark-util-types": ["micromark-util-types@2.0.1", "", {}, "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ=="],
+ "meriyah": ["meriyah@6.1.4", "", {}, "sha512-Sz8FzjzI0kN13GK/6MVEsVzMZEPvOhnmmI1lU5+/1cGOiK3QUahntrNNtdVeihrO7t9JpoH75iMNXg6R6uWflQ=="],
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
- "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+ "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
- "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
-
- "mongodb": ["mongodb@6.12.0", "", { "dependencies": { "@mongodb-js/saslprep": "^1.1.9", "bson": "^6.10.1", "mongodb-connection-string-url": "^3.0.0" }, "peerDependencies": { "@aws-sdk/credential-providers": "^3.188.0", "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", "snappy": "^7.2.2", "socks": "^2.7.1" }, "optionalPeers": ["@aws-sdk/credential-providers", "@mongodb-js/zstd", "gcp-metadata", "kerberos", "mongodb-client-encryption", "snappy", "socks"] }, "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA=="],
+ "mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
- "mongodb-connection-string-url": ["mongodb-connection-string-url@3.0.2", "", { "dependencies": { "@types/whatwg-url": "^11.0.2", "whatwg-url": "^14.1.0 || ^13.0.0" } }, "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA=="],
+ "modern-tar": ["modern-tar@0.7.7", "", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="],
- "mongoose": ["mongoose@8.9.6", "", { "dependencies": { "bson": "^6.10.1", "kareem": "2.6.3", "mongodb": "~6.12.0", "mpath": "0.9.0", "mquery": "5.0.0", "ms": "2.1.3", "sift": "17.1.3" } }, "sha512-ipLvXwNPVuuuq5H3lnSD0lpaRH3DlCoC6emnMVJvweTwxU29uxDJWxMsNpERDQt8JMvYF1HGVuTK+Id2BlQLCA=="],
-
- "motion-dom": ["motion-dom@12.0.0", "", { "dependencies": { "motion-utils": "^12.0.0" } }, "sha512-CvYd15OeIR6kHgMdonCc1ihsaUG4MYh/wrkz8gZ3hBX/uamyZCXN9S9qJoYF03GqfTt7thTV/dxnHYX4+55vDg=="],
-
- "motion-utils": ["motion-utils@12.0.0", "", {}, "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA=="],
-
- "mpath": ["mpath@0.9.0", "", {}, "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew=="],
-
- "mquery": ["mquery@5.0.0", "", { "dependencies": { "debug": "4.x" } }, "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg=="],
+ "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
- "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
+ "nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
- "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
+ "nanostores": ["nanostores@1.4.1", "", {}, "sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q=="],
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
- "next": ["next@15.1.6", "", { "dependencies": { "@next/env": "15.1.6", "@swc/counter": "0.1.3", "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.1.6", "@next/swc-darwin-x64": "15.1.6", "@next/swc-linux-arm64-gnu": "15.1.6", "@next/swc-linux-arm64-musl": "15.1.6", "@next/swc-linux-x64-gnu": "15.1.6", "@next/swc-linux-x64-musl": "15.1.6", "@next/swc-win32-arm64-msvc": "15.1.6", "@next/swc-win32-x64-msvc": "15.1.6", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-Hch4wzbaX0vKQtalpXvUiw5sYivBy4cm5rzUKrBnUB/y436LGrvOUqYvlSeNVCWFO/770gDlltR9gqZH62ct4Q=="],
+ "next": ["next@16.2.11", "", { "dependencies": { "@next/env": "16.2.11", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.11", "@next/swc-darwin-x64": "16.2.11", "@next/swc-linux-arm64-gnu": "16.2.11", "@next/swc-linux-arm64-musl": "16.2.11", "@next/swc-linux-x64-gnu": "16.2.11", "@next/swc-linux-x64-musl": "16.2.11", "@next/swc-win32-arm64-msvc": "16.2.11", "@next/swc-win32-x64-msvc": "16.2.11", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-B339zaqbyK8cmxhoAvLrcwoabwCP1wz21zSzfqxqXAemTu2BXnH7tQnfcglKv1vnMUIDBc+Hth7XODQriTZiRQ=="],
- "next-cloudinary": ["next-cloudinary@6.16.0", "", { "dependencies": { "@cloudinary-util/types": "1.5.10", "@cloudinary-util/url-loader": "5.10.4", "@cloudinary-util/util": "4.0.0" }, "peerDependencies": { "next": "^12 || ^13 || ^14 || >=15.0.0-rc || ^15", "react": "^17 || ^18 || >=19.0.0-beta || ^19" } }, "sha512-gbw/A1aBHJBC2thm4/veI77IkmbAQXuRUj8kNEuxf1zgjGkUWkShho/cXS4VsFhnHuDieqb30gMjZnZdBDrQ/Q=="],
+ "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
- "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+ "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
- "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
+ "node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
- "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
- "object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+ "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
"object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
@@ -887,6 +1010,10 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
+ "obug": ["obug@2.1.4", "", {}, "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA=="],
+
+ "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
"own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
@@ -895,87 +1022,71 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
- "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
+ "p-retry": ["p-retry@4.6.2", "", { "dependencies": { "@types/retry": "0.12.0", "retry": "^0.13.1" } }, "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ=="],
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
- "parse-entities": ["parse-entities@4.0.2", "", { "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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="],
-
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
"path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
- "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
+ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
- "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+ "pg-int8": ["pg-int8@1.0.1", "", {}, "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="],
- "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "pg-protocol": ["pg-protocol@1.15.0", "", {}, "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ=="],
- "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
+ "pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
- "pirates": ["pirates@4.0.6", "", {}, "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg=="],
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
- "possible-typed-array-names": ["possible-typed-array-names@1.0.0", "", {}, "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q=="],
+ "picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
- "postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
+ "playwright": ["playwright@1.61.1", "", { "dependencies": { "playwright-core": "1.61.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ=="],
- "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
+ "playwright-core": ["playwright-core@1.61.1", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg=="],
- "postcss-js": ["postcss-js@4.0.1", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw=="],
+ "possible-typed-array-names": ["possible-typed-array-names@1.0.0", "", {}, "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q=="],
- "postcss-load-config": ["postcss-load-config@4.0.2", "", { "dependencies": { "lilconfig": "^3.0.0", "yaml": "^2.3.4" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ=="],
+ "postcss": ["postcss@8.5.21", "", { "dependencies": { "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-v4sDNP3fdNiWMfabO7OwOQdOX8TiQSztKyT1Wj0w+j7LDallJThJRBBBmzVGyYj0crMh7jlV4zepPkiNu9UwDQ=="],
- "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
+ "postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
- "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
+ "postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
- "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
+ "postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
+
+ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
+ "promise-limit": ["promise-limit@2.7.0", "", {}, "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw=="],
+
"prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
- "property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
+ "protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="],
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
- "q": ["q@1.5.1", "", {}, "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw=="],
+ "puppeteer-core": ["puppeteer-core@25.3.0", "", { "dependencies": { "@puppeteer/browsers": "3.0.6", "chromium-bidi": "16.0.1", "devtools-protocol": "0.0.1638949", "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.2", "ws": "^8.21.0" } }, "sha512-fm+wpUr2oigH1PXZvwgATrM2tYWHMDG8ASzTEe9uukCye4X5Ldx1K5BTHPFKITrIWvQQAQ256d1NpbEveBcKjA=="],
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
- "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
-
- "react-day-picker": ["react-day-picker@8.10.1", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA=="],
+ "react": ["react@19.2.8", "", {}, "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw=="],
- "react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
-
- "react-icons": ["react-icons@5.4.0", "", { "peerDependencies": { "react": "*" } }, "sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ=="],
+ "react-dom": ["react-dom@19.2.8", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.8" } }, "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ=="],
"react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
- "react-loading-indicators": ["react-loading-indicators@1.0.0", "", { "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-0xI07RxuT62NcdKB+6pphR++cnkk9iu8DO0H9kSKE9X71oY/M+gkqTaCjoYA1T3UYrRKdm52QvnwY/YTAU5/cg=="],
-
- "react-markdown": ["react-markdown@9.0.3", "", { "dependencies": { "@types/hast": "^3.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" }, "peerDependencies": { "@types/react": ">=18", "react": ">=18" } }, "sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw=="],
-
- "react-remove-scroll": ["react-remove-scroll@2.6.3", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pnAi91oOk8g8ABQKGF5/M9qxmmOPxaAnopyTHYfqYEwJhyFrbbBtHuSgtKEoH0jpcxx5o3hXqH1mNd9/Oi+8iQ=="],
-
- "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
-
- "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
-
- "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
-
- "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
-
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
- "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
- "remark-rehype": ["remark-rehype@11.1.1", "", { "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" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="],
+ "require-in-the-middle": ["require-in-the-middle@8.0.1", "", { "dependencies": { "debug": "^4.3.5", "module-details-from-path": "^1.0.3" } }, "sha512-QT7FVMXfWOYFbeRBF6nu+I6tr2Tf3u0q8RIEjNob/heKY/nh7drD/k7eeMFmSQgnTtCzLDcCu/XEnpW2wk4xCQ=="],
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
@@ -983,19 +1094,33 @@
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
+ "retry": ["retry@0.13.1", "", {}, "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg=="],
+
"reusify": ["reusify@1.0.4", "", {}, "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="],
+ "robots-parser": ["robots-parser@3.0.1", "", {}, "sha512-s+pyvQeIKIZ0dx5iJiQk1tPLJAWln39+MI5jtM8wnyws+G5azk+dMnMX0qfbqNetKKNgcWWOdi0sfm+FbQbgdQ=="],
+
+ "rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="],
+
+ "rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="],
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
- "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
+ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
- "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+ "semifies": ["semifies@1.0.0", "", {}, "sha512-xXR3KGeoxTNWPD4aBvL5NUpMTT7WMANr3EWnaS190QVkY52lqqcVRD7Q05UVbBhiWDGWMlJEUam9m7uFFGVScw=="],
+
+ "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="],
+
+ "set-cookie-parser": ["set-cookie-parser@3.1.2", "", {}, "sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw=="],
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
@@ -1003,7 +1128,7 @@
"set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
- "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
+ "sharp": ["sharp@0.35.3", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.3", "@img/sharp-darwin-x64": "0.35.3", "@img/sharp-freebsd-wasm32": "0.35.3", "@img/sharp-libvips-darwin-arm64": "1.3.2", "@img/sharp-libvips-darwin-x64": "1.3.2", "@img/sharp-libvips-linux-arm": "1.3.2", "@img/sharp-libvips-linux-arm64": "1.3.2", "@img/sharp-libvips-linux-ppc64": "1.3.2", "@img/sharp-libvips-linux-riscv64": "1.3.2", "@img/sharp-libvips-linux-s390x": "1.3.2", "@img/sharp-libvips-linux-x64": "1.3.2", "@img/sharp-libvips-linuxmusl-arm64": "1.3.2", "@img/sharp-libvips-linuxmusl-x64": "1.3.2", "@img/sharp-linux-arm": "0.35.3", "@img/sharp-linux-arm64": "0.35.3", "@img/sharp-linux-ppc64": "0.35.3", "@img/sharp-linux-riscv64": "0.35.3", "@img/sharp-linux-s390x": "0.35.3", "@img/sharp-linux-x64": "0.35.3", "@img/sharp-linuxmusl-arm64": "0.35.3", "@img/sharp-linuxmusl-x64": "0.35.3", "@img/sharp-webcontainers-wasm32": "0.35.3", "@img/sharp-win32-arm64": "0.35.3", "@img/sharp-win32-ia32": "0.35.3", "@img/sharp-win32-x64": "0.35.3" } }, "sha512-ej0zVHuZGHCiABXcNxeYhpRnPNPAcvbG8RMdBAhDAxLKkCRVSpK3Iyu7qbqw3JMzoj0REeM6f3tJLtVwl0023Q=="],
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -1017,25 +1142,25 @@
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
- "sift": ["sift@17.1.3", "", {}, "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="],
-
- "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+ "siginfo": ["siginfo@2.0.0", "", {}, "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g=="],
- "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
+ "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
- "space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
- "sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
+ "speedline-core": ["speedline-core@1.4.3", "", { "dependencies": { "@types/node": "*", "image-ssim": "^0.2.0", "jpeg-js": "^0.4.1" } }, "sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog=="],
"stable-hash": ["stable-hash@0.0.4", "", {}, "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g=="],
- "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
+ "stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
- "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+ "std-env": ["std-env@4.2.0", "", {}, "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw=="],
- "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
+
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="],
@@ -1049,58 +1174,52 @@
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
- "stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
-
- "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
-
- "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
- "style-to-object": ["style-to-object@1.0.8", "", { "dependencies": { "inline-style-parser": "0.2.4" } }, "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g=="],
+ "stubborn-fs": ["stubborn-fs@2.0.0", "", { "dependencies": { "stubborn-utils": "^1.0.1" } }, "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA=="],
- "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
+ "stubborn-utils": ["stubborn-utils@1.0.2", "", {}, "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg=="],
- "sucrase": ["sucrase@3.35.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA=="],
+ "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
- "tailwind-merge": ["tailwind-merge@2.6.0", "", {}, "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA=="],
-
- "tailwindcss": ["tailwindcss@3.4.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og=="],
-
- "tailwindcss-animate": ["tailwindcss-animate@1.0.7", "", { "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders" } }, "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA=="],
-
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
- "text-segmentation": ["text-segmentation@1.0.3", "", { "dependencies": { "utrie": "^1.0.2" } }, "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw=="],
+ "third-party-web": ["third-party-web@0.29.2", "", {}, "sha512-fegtha91tq2DHphyoiBXVHjVi2YG9zFaRnboT9C28tO1en9Y3wJsfspuy40F+u5wl3hHVbw7cnd1b67kEGHb8g=="],
- "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
+ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="],
- "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
+ "tinyexec": ["tinyexec@1.2.4", "", {}, "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg=="],
- "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
+ "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
- "tr46": ["tr46@5.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g=="],
+ "tinyrainbow": ["tinyrainbow@3.1.0", "", {}, "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw=="],
- "trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
+ "tldts-core": ["tldts-core@7.4.9", "", {}, "sha512-DxKfPBI52p2msTEu7MPhdpdDTBhhVQg1a/8PjQckeyAvO13eMYElX545grIp6nnTGIMZlRvFZPvFhvI/WIz2Vg=="],
- "trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
+ "tldts-icann": ["tldts-icann@7.4.9", "", { "dependencies": { "tldts-core": "^7.4.9" } }, "sha512-q6gyxCkcFPu5OZd2hi2quysxCG3ejIi53EACesbCEZHhH7FO3HnliqwO5zUs0TV6dA54SxhCrTGAc4ugl7rTiw=="],
- "ts-api-utils": ["ts-api-utils@2.0.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ=="],
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
- "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
+ "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+ "tsx": ["tsx@4.23.1", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ=="],
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
+ "type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
+
"typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
"typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
@@ -1109,214 +1228,538 @@
"typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="],
- "typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="],
+ "typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="],
+
+ "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
+
+ "typescript-eslint": ["typescript-eslint@8.65.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.65.0", "@typescript-eslint/parser": "8.65.0", "@typescript-eslint/typescript-estree": "8.65.0", "@typescript-eslint/utils": "8.65.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-/ggrHAwyjENDusvyxbuqxAC2dTnZg/Z8F+fgQtYIz+L6n/9HfSlEZcFGV/NsMNa6CkGk0xUjUAFwC0vHOflvIA=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
- "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
- "unified": ["unified@11.0.5", "", { "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" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
+ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
- "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
+ "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
- "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
+ "vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="],
- "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
+ "vitest": ["vitest@4.1.10", "", { "dependencies": { "@vitest/expect": "4.1.10", "@vitest/mocker": "4.1.10", "@vitest/pretty-format": "4.1.10", "@vitest/runner": "4.1.10", "@vitest/snapshot": "4.1.10", "@vitest/spy": "4.1.10", "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.10", "@vitest/browser-preview": "4.1.10", "@vitest/browser-webdriverio": "4.1.10", "@vitest/coverage-istanbul": "4.1.10", "@vitest/coverage-v8": "4.1.10", "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw=="],
- "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
+ "web-features": ["web-features@3.34.1", "", {}, "sha512-x+TGFo3TzB8dmXHNvveQXmaoYhzNH6blW93TO0u1Ygvr8/Bk3KIFZC9LVofHNnkcEmeodh8P3Mes5fPWyO7ATA=="],
- "unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
+ "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
- "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
+ "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.2", "", {}, "sha512-VSV+fzfChirL3e7jay2yUC7B4HQCGtEWEg/MSSQbK+qWbqeGlRLlXTzPpYr3XGUvbpDHumWZBJxgesg4N7dbtA=="],
- "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
+ "when-exit": ["when-exit@2.1.5", "", {}, "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg=="],
- "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
- "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="],
+ "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
- "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
+ "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
- "utrie": ["utrie@1.0.2", "", { "dependencies": { "base64-arraybuffer": "^1.0.2" } }, "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw=="],
+ "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
- "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
+ "which-typed-array": ["which-typed-array@1.1.22", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.9", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw=="],
- "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
+ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
- "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
+ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
- "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="],
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
- "whatwg-url": ["whatwg-url@14.1.0", "", { "dependencies": { "tr46": "^5.0.0", "webidl-conversions": "^7.0.0" } }, "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w=="],
+ "ws": ["ws@7.5.13", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA=="],
- "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+ "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="],
- "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="],
+ "xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="],
- "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="],
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
- "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
+ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
- "which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+ "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
- "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
- "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+ "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
- "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+ "zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
- "yaml": ["yaml@2.7.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA=="],
+ "zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
- "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
+ "@babel/core/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
- "zod": ["zod@3.24.1", "", {}, "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A=="],
+ "@babel/core/json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
- "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
+ "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
- "@cloudinary-util/url-loader/@cloudinary-util/util": ["@cloudinary-util/util@3.3.2", "", {}, "sha512-Cc0iFxzfl7fcOXuznpeZFGYC885Of/vDgccRDnhTe/8Rf8YKv2PjLtezyo0VgmdA/CpeZy29NCXAsf6liokbwg=="],
+ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
- "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
+ "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
- "@next/eslint-plugin-next/fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
+ "@google/genai/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
- "@radix-ui/react-collapsible/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
- "@radix-ui/react-collection/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "@libsql/isomorphic-ws/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
- "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "@puppeteer/browsers/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="],
- "@radix-ui/react-context-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "@rolldown/binding-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
- "@radix-ui/react-dropdown-menu/@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.2", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-dismissable-layer": "1.1.5", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.2", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-popper": "1.2.2", "@radix-ui/react-portal": "1.1.4", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-roving-focus": "1.1.2", "@radix-ui/react-slot": "1.1.2", "@radix-ui/react-use-callback-ref": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tBBb5CXDJW3t2mo9WlO7r6GTmWV0F0uzHZVFmlRmYpiSK1CDU5IKojP1pm7oknpBOrFZx/YgBRW9oorPO2S/Lg=="],
+ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="],
- "@radix-ui/react-menu/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
+ "@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
- "@radix-ui/react-menu/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA=="],
+ "@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="],
- "@radix-ui/react-menu/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+ "array-buffer-byte-length/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-menu/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
+ "array.prototype.findlast/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-menu/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "array.prototype.findlast/es-shim-unscopables": ["es-shim-unscopables@1.0.2", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw=="],
- "@radix-ui/react-menu/@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-collection": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-direction": "1.1.0", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw=="],
+ "array.prototype.findlastindex/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "array.prototype.flat/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-roving-focus/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="],
+ "array.prototype.flat/es-shim-unscopables": ["es-shim-unscopables@1.0.2", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw=="],
- "@radix-ui/react-select/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
+ "array.prototype.flatmap/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-select/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA=="],
+ "array.prototype.flatmap/es-shim-unscopables": ["es-shim-unscopables@1.0.2", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw=="],
- "@radix-ui/react-select/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+ "array.prototype.tosorted/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-select/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
+ "array.prototype.tosorted/es-shim-unscopables": ["es-shim-unscopables@1.0.2", "", { "dependencies": { "hasown": "^2.0.0" } }, "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw=="],
- "@radix-ui/react-select/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "arraybuffer.prototype.slice/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "arraybuffer.prototype.slice/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "@radix-ui/react-switch/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="],
- "@radix-ui/react-toast/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
+ "call-bind/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "@radix-ui/react-toast/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
+ "call-bound/call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
- "@radix-ui/react-toast/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "chromium-bidi/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
- "@radix-ui/react-tooltip/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
+ "data-view-buffer/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-tooltip/@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.1", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-layout-effect": "1.1.0", "@radix-ui/react-use-rect": "1.1.0", "@radix-ui/react-use-size": "1.1.0", "@radix-ui/rect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw=="],
+ "data-view-byte-length/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-tooltip/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
+ "data-view-byte-offset/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-tooltip/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "es-iterator-helpers/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "es-iterator-helpers/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
- "@radix-ui/react-visually-hidden/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "es-iterator-helpers/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+ "es-set-tostringtag/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "@typescript-eslint/typescript-estree/semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="],
+ "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
- "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "eslint-import-resolver-typescript/debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
- "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+ "eslint-import-resolver-typescript/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+ "eslint-plugin-import/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+ "eslint-plugin-jsx-a11y/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
+
+ "eslint-plugin-jsx-a11y/axe-core": ["axe-core@4.10.2", "", {}, "sha512-RE3mdQ7P3FRSe7eqCWoeQ/Z9QXrtniSjp1wUjt5nRC3WIpz5rSCve6o3fsZ2aCpJtrZjSZgjwXAoTO5k4tEI0w=="],
+
+ "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
+ "eslint-plugin-react/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
+
+ "eslint-plugin-react/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
+
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
+ "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
- "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+ "function.prototype.name/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "get-intrinsic/call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
+ "get-symbol-description/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "get-symbol-description/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-array-buffer/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-array-buffer/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-async-function/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-boolean-object/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
"is-bun-module/semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="],
- "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
+ "is-data-view/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-data-view/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-date-object/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-finalizationregistry/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-generator-function/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-number-object/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-regex/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-shared-array-buffer/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-string/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-symbol/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-typed-array/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "is-weakset/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "is-weakset/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "iterator.prototype/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "jsx-ast-utils/array-includes": ["array-includes@3.1.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.4", "is-string": "^1.0.7" } }, "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ=="],
+
+ "libsql/detect-libc": ["detect-libc@2.0.2", "", {}, "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw=="],
+
+ "object.assign/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "object.fromentries/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "object.groupby/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "object.values/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "own-keys/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "puppeteer-core/devtools-protocol": ["devtools-protocol@0.0.1638949", "", {}, "sha512-mXwg4Fqnv0WR4iuAT/gYUmctNkjILwXFHyZ+m7Ty1dfr0ezZt2U3gnrrJTfRobJTHoXf+IbuFvFITzLrLFjwJA=="],
+
+ "puppeteer-core/ws": ["ws@8.21.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw=="],
+
+ "reflect.getprototypeof/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "reflect.getprototypeof/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "safe-array-concat/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "safe-array-concat/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "safe-regex-test/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "set-function-length/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "side-channel/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "side-channel-list/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "side-channel-map/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "side-channel-map/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "side-channel-map/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "side-channel-weakmap/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "side-channel-weakmap/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "side-channel-weakmap/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
+ "string.prototype.includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "string.prototype.matchall/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "string.prototype.matchall/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "string.prototype.matchall/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "string.prototype.repeat/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "string.prototype.trim/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "string.prototype.trim/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "string.prototype.trimend/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "tsx/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "typed-array-buffer/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "unbox-primitive/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "vite/fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
+
+ "which-builtin-type/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "which-builtin-type/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "which-builtin-type/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "which-typed-array/call-bind": ["call-bind@1.0.9", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "get-intrinsic": "^1.3.0", "set-function-length": "^1.2.2" } }, "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ=="],
+
+ "which-typed-array/for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
+
+ "@puppeteer/browsers/yargs/cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="],
+
+ "@puppeteer/browsers/yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
+
+ "@puppeteer/browsers/yargs/yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
+
+ "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@5.0.7", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA=="],
+
+ "array-buffer-byte-length/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.findlast/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "array.prototype.findlast/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.findlast/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "array.prototype.findlast/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "array.prototype.findlast/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "array.prototype.findlastindex/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "array.prototype.findlastindex/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.findlastindex/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "array.prototype.findlastindex/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "array.prototype.findlastindex/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "array.prototype.flat/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "array.prototype.flat/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.flat/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "array.prototype.flat/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "array.prototype.flat/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "array.prototype.flatmap/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "array.prototype.flatmap/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.flatmap/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "array.prototype.flatmap/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "array.prototype.flatmap/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "array.prototype.tosorted/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "array.prototype.tosorted/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "array.prototype.tosorted/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "array.prototype.tosorted/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "array.prototype.tosorted/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "arraybuffer.prototype.slice/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "arraybuffer.prototype.slice/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "arraybuffer.prototype.slice/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "arraybuffer.prototype.slice/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "data-view-buffer/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "data-view-byte-length/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "data-view-byte-offset/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "es-iterator-helpers/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "es-iterator-helpers/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "es-iterator-helpers/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "eslint-import-resolver-typescript/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+
+ "eslint-plugin-jsx-a11y/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "eslint-plugin-jsx-a11y/array-includes/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "eslint-plugin-react/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "eslint-plugin-react/array-includes/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "function.prototype.name/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-async-function/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-boolean-object/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-date-object/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-finalizationregistry/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-generator-function/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-number-object/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-regex/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-shared-array-buffer/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-string/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-symbol/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "is-typed-array/which-typed-array/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "jsx-ast-utils/array-includes/es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
+
+ "jsx-ast-utils/array-includes/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "object.assign/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "object.fromentries/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "object.fromentries/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "object.fromentries/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "object.fromentries/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "object.fromentries/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "object.groupby/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "object.groupby/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "object.groupby/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "object.groupby/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "object.groupby/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "object.values/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "reflect.getprototypeof/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "reflect.getprototypeof/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "reflect.getprototypeof/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "reflect.getprototypeof/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "safe-regex-test/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "string.prototype.includes/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "string.prototype.includes/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "string.prototype.includes/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "string.prototype.includes/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "string.prototype.includes/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "string.prototype.matchall/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "string.prototype.matchall/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "string.prototype.matchall/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "string.prototype.repeat/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
+
+ "string.prototype.repeat/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "string.prototype.repeat/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
+
+ "string.prototype.repeat/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
+
+ "string.prototype.repeat/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
+
+ "string.prototype.trim/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
+
+ "string.prototype.trim/es-abstract/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
+ "string.prototype.trim/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
- "sharp/semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="],
+ "string.prototype.trim/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
- "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+ "string.prototype.trim/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
- "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "string.prototype.trimend/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "typed-array-buffer/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "vaul/@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.4", "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", "@radix-ui/react-portal": "1.1.3", "@radix-ui/react-presence": "1.1.2", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-LaO3e5h/NOEL4OfXjxD43k9Dx+vn+8n+PCFt6uhX/BADFflllyv3WJG6rgvvSVBxpTch938Qq/LGc2MMxipXPw=="],
+ "unbox-primitive/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
+ "which-builtin-type/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+ "which-typed-array/call-bind/call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
- "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+ "@puppeteer/browsers/yargs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
- "@next/eslint-plugin-next/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
+ "@puppeteer/browsers/yargs/cliui/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
- "@radix-ui/react-collapsible/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "@puppeteer/browsers/yargs/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
- "@radix-ui/react-context-menu/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "@puppeteer/browsers/yargs/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
- "@radix-ui/react-dropdown-menu/@radix-ui/react-menu/@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", "@radix-ui/react-primitive": "2.0.2", "@radix-ui/react-slot": "1.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9z54IEKRxIa9VityapoEYMuByaG42iSy1ZXlY2KcuLSEtq8x4987/N6m15ppoMffgZX72gER2uHe1D9Y6Unlcw=="],
+ "@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
- "@radix-ui/react-menu/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+ "eslint-plugin-jsx-a11y/array-includes/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-select/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+ "eslint-plugin-jsx-a11y/array-includes/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
- "@radix-ui/react-switch/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "eslint-plugin-jsx-a11y/array-includes/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
- "@radix-ui/react-toast/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "eslint-plugin-jsx-a11y/array-includes/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
- "@radix-ui/react-tooltip/@radix-ui/react-popper/@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.1", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w=="],
+ "eslint-plugin-react/array-includes/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "eslint-plugin-react/array-includes/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
- "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+ "eslint-plugin-react/array-includes/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
- "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+ "eslint-plugin-react/array-includes/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
- "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "is-typed-array/which-typed-array/call-bound/get-intrinsic": ["get-intrinsic@1.2.7", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA=="],
- "vaul/@radix-ui/react-dialog/@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.1", "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0", "@radix-ui/react-use-escape-keydown": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XDUI0IVYVSwjMXxM6P4Dfti7AH+Y4oS/TB+sglZ/EXc7cqLwGAmp1NlMrcUjj7ks6R5WTZuWKv44FBbLpwU3sA=="],
+ "jsx-ast-utils/array-includes/es-abstract/call-bound": ["call-bound@1.0.3", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" } }, "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA=="],
- "vaul/@radix-ui/react-dialog/@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-callback-ref": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA=="],
+ "jsx-ast-utils/array-includes/es-abstract/is-weakref": ["is-weakref@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2" } }, "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q=="],
- "vaul/@radix-ui/react-dialog/@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.0.1", "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw=="],
+ "jsx-ast-utils/array-includes/es-abstract/object-inspect": ["object-inspect@1.13.3", "", {}, "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA=="],
- "vaul/@radix-ui/react-dialog/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.0.1", "", { "dependencies": { "@radix-ui/react-slot": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg=="],
+ "jsx-ast-utils/array-includes/es-abstract/which-typed-array": ["which-typed-array@1.1.18", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA=="],
- "vaul/@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g=="],
+ "@puppeteer/browsers/yargs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
- "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+ "@puppeteer/browsers/yargs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
- "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+ "@puppeteer/browsers/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
}
}
diff --git a/components.json b/components.json
deleted file mode 100644
index 964f355..0000000
--- a/components.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "$schema": "https://ui.shadcn.com/schema.json",
- "style": "default",
- "rsc": true,
- "tsx": true,
- "tailwind": {
- "config": "tailwind.config.ts",
- "css": "app/globals.css",
- "baseColor": "zinc",
- "cssVariables": true,
- "prefix": ""
- },
- "aliases": {
- "hooks": "@/hooks",
- "components": "@/components",
- "ui": "@/ui",
- "lib": "@/utils",
- "utils": "@/utils/shadcn"
- },
- "iconLibrary": "lucide"
-}
diff --git a/components/BreadCrumbNav.tsx b/components/BreadCrumbNav.tsx
deleted file mode 100644
index d13a806..0000000
--- a/components/BreadCrumbNav.tsx
+++ /dev/null
@@ -1,42 +0,0 @@
-'use client';
-
-import { usePathname } from 'next/navigation';
-import { Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbSeparator } from '@/ui/breadcrumb';
-import { MdMapsHomeWork } from 'react-icons/md';
-
-import Link from 'next/link';
-
-export default () => {
- const path = usePathname();
- const segments = path.split('/').filter(Boolean);
-
- return (
-
-
-
-
-
-
-
-
- {segments.map((segment, i) => (
- <>
-
-
-
-
- {segment}
-
-
- >
- ))}
-
-
- );
-
- return;
-};
diff --git a/components/Copy.tsx b/components/Copy.tsx
deleted file mode 100644
index b9aa0d6..0000000
--- a/components/Copy.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { cn } from '@/utils/shadcn';
-import { Button } from '@/ui/button';
-import { CopyIcon } from '@radix-ui/react-icons';
-
-export default ({ text, className }: { text: string; className?: string }) => {
- const handleClick = () => {
- navigator.clipboard.writeText(text);
- };
-
- return (
-
-
-
- );
-};
diff --git a/components/add-member-dialog.tsx b/components/add-member-dialog.tsx
deleted file mode 100644
index 8244dcb..0000000
--- a/components/add-member-dialog.tsx
+++ /dev/null
@@ -1,254 +0,0 @@
-'use client';
-
-import type React from 'react';
-import { useState } from 'react';
-import {
- AlertDialog,
- AlertDialogContent,
- AlertDialogHeader,
- AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogCancel,
-} from '@/ui/alert-dialog';
-import { Button } from '@/ui/button';
-import { Checkbox } from '@/ui/checkbox';
-import { Input } from '@/ui/input';
-import { Label } from '@/ui/label';
-import { RadioGroup, RadioGroupItem } from '@/ui/radio-group';
-import { Separator } from '@/ui/separator';
-import { Copy, Mail } from 'lucide-react';
-
-const permissions = [
- {
- id: 'view-projects',
- label: 'View projects',
- description: 'Can view all projects in the organization',
- },
- {
- id: 'create-projects',
- label: 'Create projects',
- description: 'Can create new projects',
- },
- {
- id: 'edit-projects',
- label: 'Edit projects',
- description: 'Can edit existing projects',
- },
- {
- id: 'delete-projects',
- label: 'Delete projects',
- description: 'Can delete existing projects',
- },
- {
- id: 'manage-members',
- label: 'Manage members',
- description: 'Can add/remove members and modify their roles',
- },
- {
- id: 'billing-access',
- label: 'Billing access',
- description: 'Can view and modify billing information',
- },
-];
-
-interface AddMemberDialogProps {
- open: boolean;
- onOpenChange: (open: boolean) => void;
-}
-
-export function AddMemberDialog({ open, onOpenChange }: AddMemberDialogProps) {
- const [email, setEmail] = useState('');
- const [username, setUsername] = useState('');
- const [role, setRole] = useState('member');
- const [selectedPermissions, setSelectedPermissions] = useState([]);
- const [useGoogleAccount, setUseGoogleAccount] = useState(false);
-
- const inviteLink = 'https://acme.com/invite/xyz123'; // This would be generated dynamically
-
- function onSubmit(e: React.FormEvent) {
- e.preventDefault();
- console.log({ email, username, role, selectedPermissions, useGoogleAccount });
- onOpenChange(false);
- }
-
- return (
-
-
-
- Add new member
-
- Invite a new member to your organization. They will
- receive an email invitation.
-
-
-
-
-
-
- Email address
-
- setEmail(e.target.value)
- }
- required
- />
-
-
-
- Username
-
- setUsername(e.target.value)
- }
- required
- />
-
-
-
-
- setUseGoogleAccount(
- checked as boolean
- )
- }
- />
-
-
- Use Google Account
-
-
- Allow sign in with Google
- account using this email
-
-
-
-
-
-
Invitation link
-
-
-
- navigator.clipboard.writeText(
- inviteLink
- )
- }>
-
-
-
-
- Share this link to invite members
- directly
-
-
-
-
-
-
-
-
-
Role
-
-
-
-
- Admin - Full access
- to all resources
-
-
-
-
-
- Member - Limited
- access to resources
-
-
-
-
-
-
-
Permissions
-
- Select specific permissions for this
- member
-
- {permissions.map(permission => (
-
-
{
- setSelectedPermissions(
- checked
- ? [
- ...selectedPermissions,
- permission.id,
- ]
- : selectedPermissions.filter(
- id =>
- id !==
- permission.id
- )
- );
- }}
- />
-
-
- {
- permission.label
- }
-
-
- {
- permission.description
- }
-
-
-
- ))}
-
-
-
-
- Cancel
-
-
- Send invitation
-
-
-
-
-
- );
-}
diff --git a/components/chat/_export.tsx b/components/chat/_export.tsx
deleted file mode 100644
index 64740cc..0000000
--- a/components/chat/_export.tsx
+++ /dev/null
@@ -1,821 +0,0 @@
-'use client';
-
-import * as React from 'react';
-import { Slot } from '@radix-ui/react-slot';
-import { VariantProps, cva } from 'class-variance-authority';
-
-import { useIsMobile } from '@/hooks/use-mobile';
-import { cn } from '@/utils/shadcn';
-import { Button } from '@/ui/button';
-import { Input } from '@/ui/input';
-import { Separator } from '@/ui/separator';
-import { Sheet, SheetContent, SheetTrigger } from '@/ui/sheet';
-import { Skeleton } from '@/ui/skeleton';
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/ui/tooltip';
-import { useIsTablet } from '@/hooks/use-tablet';
-import { Drawer, DrawerContent, DrawerTrigger } from './drawer';
-import { TbLayoutSidebarRightCollapseFilled } from 'react-icons/tb';
-import { TbLayoutSidebarRightExpandFilled } from 'react-icons/tb';
-import { BsChevronBarExpand } from 'react-icons/bs';
-
-const SIDEBAR_COOKIE_NAME = 'sidebar:state';
-const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
-const SIDEBAR_WIDTH = '16rem';
-const SIDEBAR_WIDTH_MOBILE = '18rem';
-const SIDEBAR_WIDTH_ICON = '5rem';
-const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
-
-type SidebarContext = {
- state: 'expanded' | 'collapsed';
- open: boolean;
- setOpen: (open: boolean) => void;
- openMobile: boolean;
- setOpenMobile: (open: boolean) => void;
- isMobile: boolean;
- openTablet: boolean;
- setOpenTablet: (open: boolean) => void;
- isTablet: boolean;
- toggleSidebar: () => void;
-};
-
-const SidebarContext = React.createContext(null);
-
-function useSidebar() {
- const context = React.useContext(SidebarContext);
- if (!context) {
- throw new Error('useSidebar must be used within a SidebarProvider.');
- }
-
- return context;
-}
-
-const SidebarProvider = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<'div'> & {
- defaultOpen?: boolean;
- open?: boolean;
- onOpenChange?: (open: boolean) => void;
- }
->(
- (
- {
- defaultOpen = true,
- open: openProp,
- onOpenChange: setOpenProp,
- className,
- style,
- children,
- ...props
- },
- ref
- ) => {
- const isMobile = useIsMobile();
- const isTablet = useIsTablet();
- const [openMobile, setOpenMobile] = React.useState(false);
- const [openTablet, setOpenTablet] = React.useState(false);
-
- // This is the internal state of the sidebar.
- // We use openProp and setOpenProp for control from outside the component.
- const [_open, _setOpen] = React.useState(defaultOpen);
- const open = openProp ?? _open;
- const setOpen = React.useCallback(
- (value: boolean | ((value: boolean) => boolean)) => {
- const openState = typeof value === 'function' ? value(open) : value;
- if (setOpenProp) {
- setOpenProp(openState);
- } else {
- _setOpen(openState);
- }
-
- // This sets the cookie to keep the sidebar state.
- document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
- },
- [setOpenProp, open]
- );
-
- // Helper to toggle the sidebar.
- const toggleSidebar = React.useCallback(() => {
- if (isMobile) return setOpenMobile(open => !open);
- if (isTablet) return setOpenTablet(open => !open);
- return setOpen(open => !open);
- }, [isMobile, isTablet, setOpen, setOpenMobile, setOpenTablet]);
-
- // Adds a keyboard shortcut to toggle the sidebar.
- React.useEffect(() => {
- const handleKeyDown = (event: KeyboardEvent) => {
- if (
- event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
- (event.metaKey || event.ctrlKey)
- ) {
- event.preventDefault();
- toggleSidebar();
- }
- };
-
- window.addEventListener('keydown', handleKeyDown);
- return () => window.removeEventListener('keydown', handleKeyDown);
- }, [toggleSidebar]);
-
- // We add a state so that we can do data-state="expanded" or "collapsed".
- // This makes it easier to style the sidebar with Tailwind classes.
- const state = open ? 'expanded' : 'collapsed';
-
- const contextValue = React.useMemo(
- () => ({
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- isTablet,
- setOpenTablet,
- openTablet,
- toggleSidebar,
- }),
- [
- state,
- open,
- setOpen,
- isMobile,
- openMobile,
- setOpenMobile,
- isTablet,
- setOpenTablet,
- openTablet,
- toggleSidebar,
- ]
- );
-
- return (
-
-
-
- {children}
-
-
-
- );
- }
-);
-SidebarProvider.displayName = 'SidebarProvider';
-
-const Sidebar = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<'div'> & {
- side?: 'left' | 'right';
- variant?: 'sidebar' | 'floating' | 'inset';
- collapsible?: 'offcanvas' | 'icon' | 'none';
- }
->(
- (
- {
- side = 'left',
- variant = 'sidebar',
- collapsible = 'offcanvas',
- className,
- children,
- ...props
- },
- ref
- ) => {
- const {
- isMobile,
- isTablet,
- openTablet,
- setOpenTablet,
- state,
- openMobile,
- setOpenMobile,
- } = useSidebar();
-
- if (collapsible === 'none') {
- return (
-
- {children}
-
- );
- }
-
- if (isMobile)
- return (
-
-
-
-
-
-
- {children}
-
-
-
- );
- if (isTablet)
- return (
-
-
-
-
-
- {!openTablet ? (
-
- ) : (
-
- )}
-
-
-
-
-
- {children}
-
-
-
- );
- return (
-
- {/* This is what handles the sidebar gap on desktop */}
-
-
-
- );
- }
-);
-Sidebar.displayName = 'Sidebar';
-
-const SidebarTrigger = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, onClick, children, ...props }, ref) => {
- const { toggleSidebar, open } = useSidebar();
-
- return (
- svg]:size-6 !p-0', className)}
- onClick={event => {
- onClick?.(event);
- toggleSidebar();
- }}
- {...props}>
- {children ??
- (!open ? (
-
- ) : (
-
- ))}
- Toggle Sidebar
-
- );
-});
-SidebarTrigger.displayName = 'SidebarTrigger';
-
-const SidebarRail = React.forwardRef>(
- ({ className, ...props }, ref) => {
- const { toggleSidebar } = useSidebar();
-
- return (
-
- );
- }
-);
-SidebarRail.displayName = 'SidebarRail';
-
-const SidebarInset = React.forwardRef>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- }
-);
-SidebarInset.displayName = 'SidebarInset';
-
-const SidebarInput = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-SidebarInput.displayName = 'SidebarInput';
-
-const SidebarHeader = React.forwardRef>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- }
-);
-SidebarHeader.displayName = 'SidebarHeader';
-
-const SidebarFooter = React.forwardRef>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- }
-);
-SidebarFooter.displayName = 'SidebarFooter';
-
-const SidebarSeparator = React.forwardRef<
- React.ElementRef,
- React.ComponentProps
->(({ className, ...props }, ref) => {
- return (
-
- );
-});
-SidebarSeparator.displayName = 'SidebarSeparator';
-
-const SidebarContent = React.forwardRef>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- }
-);
-SidebarContent.displayName = 'SidebarContent';
-
-const SidebarGroup = React.forwardRef>(
- ({ className, ...props }, ref) => {
- return (
-
- );
- }
-);
-SidebarGroup.displayName = 'SidebarGroup';
-
-const SidebarGroupLabel = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<'div'> & { asChild?: boolean }
->(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : 'div';
-
- return (
- svg]:size-4 [&>svg]:shrink-0',
- 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
- className
- )}
- {...props}
- />
- );
-});
-SidebarGroupLabel.displayName = 'SidebarGroupLabel';
-
-const SidebarGroupAction = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<'button'> & { asChild?: boolean }
->(({ className, asChild = false, ...props }, ref) => {
- const Comp = asChild ? Slot : 'button';
-
- return (
- svg]:size-4 [&>svg]:shrink-0',
- // Increases the hit area of the button on mobile.
- 'after:absolute after:-inset-2 after:md:hidden',
- 'group-data-[collapsible=icon]:hidden',
- className
- )}
- {...props}
- />
- );
-});
-SidebarGroupAction.displayName = 'SidebarGroupAction';
-
-const SidebarGroupContent = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-SidebarGroupContent.displayName = 'SidebarGroupContent';
-
-const SidebarMenu = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-SidebarMenu.displayName = 'SidebarMenu';
-
-const SidebarMenuItem = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-SidebarMenuItem.displayName = 'SidebarMenuItem';
-
-const sidebarMenuButtonVariants = cva(
- 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
- {
- variants: {
- variant: {
- default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
- outline: 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
- },
- size: {
- default: 'h-8 text-sm',
- sm: 'h-7 text-xs',
- lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
- },
- },
- defaultVariants: {
- variant: 'default',
- size: 'default',
- },
- }
-);
-
-const SidebarMenuButton = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<'button'> & {
- asChild?: boolean;
- isActive?: boolean;
- tooltip?: string | React.ComponentProps;
- } & VariantProps
->(
- (
- {
- asChild = false,
- isActive = false,
- variant = 'default',
- size = 'default',
- tooltip,
- className,
- ...props
- },
- ref
- ) => {
- const Comp = asChild ? Slot : 'button';
- const { isMobile, state } = useSidebar();
-
- const button = (
-
- );
-
- if (!tooltip) {
- return button;
- }
-
- if (typeof tooltip === 'string') {
- tooltip = {
- children: tooltip,
- };
- }
-
- return (
-
- {button}
-
-
- );
- }
-);
-SidebarMenuButton.displayName = 'SidebarMenuButton';
-
-const SidebarMenuAction = React.forwardRef<
- HTMLButtonElement,
- React.ComponentProps<'button'> & {
- asChild?: boolean;
- showOnHover?: boolean;
- }
->(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
- const Comp = asChild ? Slot : 'button';
-
- return (
- svg]:size-4 [&>svg]:shrink-0',
- // Increases the hit area of the button on mobile.
- 'after:absolute after:-inset-2 after:md:hidden',
- 'peer-data-[size=sm]/menu-button:top-1',
- 'peer-data-[size=default]/menu-button:top-1.5',
- 'peer-data-[size=lg]/menu-button:top-2.5',
- 'group-data-[collapsible=icon]:hidden',
- showOnHover &&
- 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
- className
- )}
- {...props}
- />
- );
-});
-SidebarMenuAction.displayName = 'SidebarMenuAction';
-
-const SidebarMenuBadge = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-SidebarMenuBadge.displayName = 'SidebarMenuBadge';
-
-const SidebarMenuSkeleton = React.forwardRef<
- HTMLDivElement,
- React.ComponentProps<'div'> & {
- showIcon?: boolean;
- }
->(({ className, showIcon = false, ...props }, ref) => {
- // Random width between 50 to 90%.
- const width = React.useMemo(() => {
- return `${Math.floor(Math.random() * 40) + 50}%`;
- }, []);
-
- return (
-
- {showIcon && (
-
- )}
-
-
- );
-});
-SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';
-
-const SidebarMenuSub = React.forwardRef>(
- ({ className, ...props }, ref) => (
-
- )
-);
-SidebarMenuSub.displayName = 'SidebarMenuSub';
-
-const SidebarMenuSubItem = React.forwardRef>(
- ({ ...props }, ref) =>
-);
-SidebarMenuSubItem.displayName = 'SidebarMenuSubItem';
-
-const SidebarMenuSubButton = React.forwardRef<
- HTMLAnchorElement,
- React.ComponentProps<'a'> & {
- asChild?: boolean;
- size?: 'sm' | 'md';
- isActive?: boolean;
- }
->(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
- const Comp = asChild ? Slot : 'a';
-
- return (
- span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
- 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
- size === 'sm' && 'text-xs',
- size === 'md' && 'text-sm',
- 'group-data-[collapsible=icon]:hidden',
- className
- )}
- {...props}
- />
- );
-});
-SidebarMenuSubButton.displayName = 'SidebarMenuSubButton';
-
-export {
- Sidebar,
- SidebarContent,
- SidebarFooter,
- SidebarGroup,
- SidebarGroupAction,
- SidebarGroupContent,
- SidebarGroupLabel,
- SidebarHeader,
- SidebarInput,
- SidebarInset,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuBadge,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSkeleton,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
- SidebarProvider,
- SidebarRail,
- SidebarSeparator,
- SidebarTrigger,
- useSidebar,
-};
diff --git a/components/chat/inner.tsx b/components/chat/inner.tsx
deleted file mode 100644
index a6ac04c..0000000
--- a/components/chat/inner.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-'use client';
-
-import { useEffect, useState } from 'react';
-import { SheetContent, SheetTrigger } from '@/ui/sheet';
-
-import { SidebarTrigger } from '@/ui/sidebar';
-import { IoMdChatbubbles } from 'react-icons/io';
-import { IoClose } from 'react-icons/io5';
-import { IoIosArrowDown } from 'react-icons/io';
-
-import Chat from '@/app/@inset/_components/chat';
-
-import sendMessage from '@/actions/send-message';
-import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '@/ui/select';
-import { Separator } from '@/ui/separator';
-
-export const InnerChatTrigger = ({ Icon }: { Icon?: any }) => (
-
- {/* Pulsing AI Badge */}
-
-
-
-
-
- AI
-
-
- {/* Icon */}
- {Icon ? Icon : }
-
-);
-
-export default ({ containerSelector }: { containerSelector: string }) => {
- const [model, setModel] = useState('gemini-1.5-flash-8b');
- const [container, setContainer] = useState(null);
-
- useEffect(() => {
- setContainer(document.querySelector(containerSelector)!);
- }, []);
-
- if (!container) return null;
-
- return (
- e.preventDefault()}
- className="z-50 absolute flex flex-col gap-[--p] p-[--p] size-full !max-w-full !bg-none !bg-inset overflow-hidden">
-
-
-
-
-
-
-
-
-
- Frequent use
- Gemini 1.5 flash
- Gemini 1.5 flash 8b
-
-
-
- High reasoning
- Gemini 1.5 pro
- Gemini 2 flash
-
-
-
- } />
-
-
- await sendMessage(
- model,
- history.slice(0, -1),
- history
- .slice(-1)[0]
- .parts.map(p => p.text)
- .join('\n'),
- )
- }
- />
-
- );
-};
diff --git a/components/chat/sidebar.tsx b/components/chat/sidebar.tsx
deleted file mode 100644
index 0c07867..0000000
--- a/components/chat/sidebar.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-'use client';
-
-import { useEffect, useState } from 'react';
-import { SheetContent, SheetTrigger } from '@/ui/sheet';
-import { BsChevronBarExpand } from 'react-icons/bs';
-import { SidebarTrigger } from '@/ui/sidebar';
-
-export default ({ containerSelector }: { containerSelector: string }) => {
- const [container, setContainer] = useState(null);
-
- useEffect(() => {
- setContainer(document.querySelector(containerSelector)!);
- }, []);
-
- if (!container) return null;
-
- return (
-
-
-
- );
-};
diff --git a/components/check-progress.tsx b/components/check-progress.tsx
deleted file mode 100644
index fc10589..0000000
--- a/components/check-progress.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-import { motion, useMotionValue, useTransform } from 'framer-motion';
-import { useState } from 'react';
-
-export default function CircularProgress() {
- const [ended, setEnded] = useState(false);
- let progress = useMotionValue(90);
- const circleLength = useTransform(progress, [0, 100], [0, 1]);
- const checkmarkPathLength = useTransform(progress, [0, 95, 100], [0, 0, 1]);
- const circleColor = useTransform(progress, [0, 95, 100], ['#FFCC66', '#FFCC66', '#3c79dd']);
-
- return (
- <>
- {
- setEnded(true);
- }}
- />
-
- {/* Circle */}
-
- {/* Check mark */}
-
-
- >
- );
-}
diff --git a/components/copy-popup.tsx b/components/copy-popup.tsx
deleted file mode 100644
index 5abe7dc..0000000
--- a/components/copy-popup.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { motion, AnimatePresence } from "framer-motion"
-import { Check } from "lucide-react"
-
-interface CopyPopupProps {
- isVisible: boolean
-}
-
-export function CopyPopup({ isVisible }: CopyPopupProps) {
- return (
-
- {isVisible && (
-
-
- Copied to clipboard!
-
- )}
-
- )
-}
-
diff --git a/components/data-picker.tsx b/components/data-picker.tsx
deleted file mode 100644
index 4c1108e..0000000
--- a/components/data-picker.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-"use client";
-
-import * as React from "react";
-import { format } from "date-fns";
-import { CalendarIcon } from "lucide-react";
-
-import { cn } from "@/utils/shadcn";
-import { Button, ButtonProps } from "@/ui/button";
-import { Calendar } from "@/ui/calendar";
-import { Popover, PopoverContent, PopoverTrigger } from "@/ui/popover";
-
-export function DatePicker({
- onSelect,
- defaultDate,
- className,
- placeholder,
- ...props
-}: ButtonProps & {
- onSelect: (date: Date) => void;
- defaultDate?: string | Date;
- placeholder?: string;
-}) {
- const [date, setDate] = React.useState(
- defaultDate instanceof Date
- ? defaultDate
- : defaultDate && defaultDate?.trim().length
- ? new Date(defaultDate)
- : null
- );
-
- const select = (date: Date) => {
- setDate(date);
- if (onSelect) onSelect(date);
- };
-
- return (
-
-
-
-
- {!date || isNaN(date?.getTime()) ? (
- {placeholder ?? "Pick a date"}
- ) : (
- format(date, "PPP")
- )}
-
-
-
-
-
-
- );
-}
diff --git a/components/env-switcher.tsx b/components/env-switcher.tsx
deleted file mode 100644
index 497735a..0000000
--- a/components/env-switcher.tsx
+++ /dev/null
@@ -1,82 +0,0 @@
-'use client';
-
-import * as React from 'react';
-
-import Image from 'next/image';
-import { Separator } from '@/ui/separator';
-import { useRouter, usePathname } from 'next/navigation';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/ui/select';
-
-const Env = ({ name, description }: { name: string; description?: string }) => {
- return (
-
-
-
- {name}
-
-
- {description}
-
-
-
- );
-};
-
-export default ({
- envs,
-}: {
- envs: {
- name: string;
- description?: string;
- }[];
-}) => {
- envs = envs.filter(env => env.name.toLowerCase() !== 'users');
- const router = useRouter();
- const path = usePathname();
- const segments = path.split('/').filter(Boolean);
-
- return (
- {
- router.replace(`/${value}`);
- }}
- value={segments[0]}>
-
-
-
-
-
- Seleziona la pagina
- Edita i contenuti
-
- }
- />
-
-
- {envs.map((env, i) => (
-
- {i !== 0 && (
-
- )}
-
-
- ))}
-
-
- );
-};
diff --git a/components/formats.tsx b/components/formats.tsx
deleted file mode 100644
index d0782cd..0000000
--- a/components/formats.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Bold, Italic, Underline } from 'lucide-react';
-
-import { ToggleGroup, ToggleGroupItem } from '@/ui/toggle-group';
-
-export function Formats() {
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/components/groups/_export.tsx b/components/groups/_export.tsx
deleted file mode 100644
index 8b22ec1..0000000
--- a/components/groups/_export.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import { useState } from 'react';
-import { Plus } from 'lucide-react';
-import { CgSortAz } from 'react-icons/cg';
-
-import Group, { EditableGroup } from './group';
-import { SidebarGroup, SidebarMenu } from '@/ui/sidebar';
-
-import type { Group as GroupType } from '@/_server/@dashboard/( journals )';
-import SubGroup from './subGroup';
-
-export default function Groups({ groups: initGroups, section, ...actions }: Props) {
- const [groups, setGroups] = useState(initGroups);
- const [addGroup, setAddGroup] = useState(null);
- const [addSubGroup, setAddSubGroup] = useState(null);
-
- return (
- <>
-
-
-
-
-
-
-
{
- console.log('addGroup', addGroup);
- setAddGroup('');
- }}>
-
-
-
-
- {typeof addGroup === 'string' && (
- setAddGroup(y => (active ? y : null))}
- onSendChanges={name => {
- console.log('onSendChanges', name);
- actions.group.onCreate(name);
- setAddGroup(null);
- }}
- />
- )}
- {groups.map(group => (
-
- {
- actions.group.onDelete(_id);
- setGroups(y =>
- y.filter(x => x._id !== _id)
- );
- }}
- onRename={name => {
- actions.group.onRename({
- _id: group._id,
- name,
- });
- setGroups(y =>
- y.map(x =>
- x._id === group._id
- ? { ...x, name }
- : x
- )
- );
- }}
- onAddSubGroup={() => setAddSubGroup(group._id)}
- />
-
- ))}
- >
- );
-}
-
-interface Props extends Actions {
- groups: Group[];
- section: string;
-}
-
-interface Actions {
- group: {
- onCreate: (name: string) => { _id: string; name: string };
- onRename: ({ _id, name }: { _id: string; name: string }) => void;
- onDelete: (groupId: string) => void;
- };
- subGroup: {
- onCreate: (groupId: string, subGroupName: string) => void;
- onRename: ({ _id, name }: { _id: string; name: string }) => void;
- onDelete: (subGroupId: string) => void;
- };
-}
diff --git a/components/groups/context.tsx b/components/groups/context.tsx
deleted file mode 100644
index a30f153..0000000
--- a/components/groups/context.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import { cn } from '@/utils/shadcn';
-import { Separator } from '@/ui/separator';
-import {
- ContextMenu,
- ContextMenuContent,
- ContextMenuItem,
- ContextMenuTrigger,
-} from '@/ui/context-menu';
-
-import useLongPress from '@/hooks/use-longPress';
-
-import { useRef, type PropsWithChildren } from 'react';
-
-export default function Context({
- actions,
- children,
-}: PropsWithChildren & { actions: React.ComponentPropsWithoutRef[] }) {
- const trigger = useRef(null);
-
- const onLongPress = () => {
- if (!trigger.current) return;
-
- const rect = trigger.current.getBoundingClientRect();
-
- trigger.current.dispatchEvent(
- new MouseEvent('contextmenu', {
- bubbles: true,
- clientX: rect.left, // Centro dell'elemento
- clientY: rect.top, // Centro dell'elemento
- })
- );
- };
-
- const longPressEvent = useLongPress(onLongPress, {
- shouldPreventDefault: false,
- delay: 300,
- });
-
- return (
-
-
- {children}
-
-
-
- {actions.slice(1).map(({ className, ...props }, i) => (
- <>
-
-
- >
- ))}
-
-
- );
-}
diff --git a/components/groups/group.tsx b/components/groups/group.tsx
deleted file mode 100644
index efae047..0000000
--- a/components/groups/group.tsx
+++ /dev/null
@@ -1,160 +0,0 @@
-import { SidebarGroup, SidebarGroupLabel, SidebarMenuButton } from '@/ui/sidebar';
-import { Input } from '@/ui/input';
-import { useState } from 'react';
-import Context from './context';
-
-import { Plus } from 'lucide-react';
-
-import type { ReactNode } from 'react';
-import { SectionIcon } from '@radix-ui/react-icons';
-import { MdDeleteSweep, MdOutlineDriveFileRenameOutline } from 'react-icons/md';
-
-export default function Group({ children, id, name, ...actions }: Props) {
- const [toRename, setToRename] = useState(false);
-
- return (
-
-
- Rename
-
-
- ),
- onClick: () => setToRename(true),
- },
- {
- children: (
-
- Delete
-
-
- ),
- className: 'text-red-400',
- onClick: () => actions.onDelete(id),
- },
- ]}>
- {
- setToRename(false);
- if (!rename || !rename.trim().length || rename === name) return;
- actions.onRename({ id, name: rename });
- }}
- onCreateSubGroup={subGroupName =>
- actions.onCreateSubGroup(subGroupName)
- }
- />
-
- {children}
-
- );
-}
-
-export const EditableGroup = ({
- group,
- toRename = false,
- onRename,
- onCreateSubGroup,
-}: {
- group?: { id: string; name: string };
- toRename?: boolean;
- onRename?: (name: string) => void;
-
- onCreateSubGroup?: (subGroupName: string) => void;
- onSort?: () => void;
-}) => {
- const [rename, setRename] = useState(group?.name ?? '');
- const [addSubGroup, setAddSubGroup] = useState<{ active: boolean; subGroup: string }>({
- active: false,
- subGroup: '',
- });
-
- return (
- <>
- onRename('')}>
-
- {toRename ? (
-
- setRename(e.target.value)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- onRename(rename);
- }
- }}
- className="!text-xs h-fit w-full py-1 "
- />
-
- ) : (
- group?.name
- )}
-
- {onCreateSubGroup && (
-
- setAddSubGroup(p => ({
- ...p,
- active: true,
- }))
- }>
-
-
- )}
-
- {addSubGroup.active && (
- setAddSubGroup(p => ({ ...p, active: false }))}>
-
-
- setAddSubGroup(p => ({
- ...p,
- subGroup: e.target.value,
- }))
- }
- onBlur={() =>
- setAddSubGroup(p => ({
- ...p,
- active: false,
- }))
- }
- onKeyDown={e => {
- if (e.key === 'Enter') {
- setAddSubGroup(p => ({
- ...p,
- active: false,
- }));
- onCreateSubGroup(addSubGroup.subGroup);
- }
- }}
- />
-
- )}
- >
- );
-};
-
-interface Actions {
- onCreateSubGroup: (subGroupName: string) => void;
- onRename: ({ id, name }: { id: string; name: string }) => void;
- onDelete: (groupId: string) => void;
-}
-
-interface Props extends Actions {
- children?: ReactNode | ReactNode[];
- id: string;
- name: string;
-}
diff --git a/components/groups/item.tsx b/components/groups/item.tsx
deleted file mode 100644
index 77bb667..0000000
--- a/components/groups/item.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-import Link from 'next/link';
-import { SidebarMenuSubButton, SidebarMenuSubItem } from '@/ui/sidebar';
-import { useState } from 'react';
-import Context from './context';
-import { Input } from '@/ui/input';
-
-import { MdDeleteSweep } from 'react-icons/md';
-import { MdOutlineDriveFileRenameOutline } from 'react-icons/md';
-
-export default function Item({ id, name, title, section, selected, ...actions }: Props) {
- const [toRename, setToRename] = useState(false);
-
- return (
-
-
- Rename
-
-
- ),
- onClick: () => setToRename(true),
- },
- {
- children: (
-
- Delete
-
-
- ),
- className: 'text-red-400',
- onClick: () => actions.onDelete(id),
- },
- ]}>
- {
- setToRename(false);
- if (!rename || !rename.trim().length || rename === name) return;
- actions.onRename(rename);
- }}
- onDelete={() => actions.onDelete(id)}
- />
-
-
- );
-}
-
-export const EditableItem = ({
- id,
- name,
- section,
- toRename = false,
- onRename,
-}:
- | (Partial & { toRename: true; onRename: (name: string) => void })
- | (Props & { toRename: false })) => {
- const [rename, setRename] = useState(name ?? '');
-
- return toRename ? (
- onRename('')}>
- setRename(e.target.value)}
- onKeyDown={e => {
- if (e.key === 'Enter') onRename(rename);
- }}
- className="leading-7 w-full p-0 !ring-0 placeholder:text-xs"
- />
-
- ) : (
-
-
- {name}
-
-
- );
-};
-
-export type Actions = {
- onRename: (name: string) => void;
- onDelete: (groupId: string) => void;
-};
-
-export interface Props extends Actions {
- id: string;
- name: string;
- title: string;
- section: string;
- selected: boolean;
-}
diff --git a/components/groups/subGroup.tsx b/components/groups/subGroup.tsx
deleted file mode 100644
index 538cfeb..0000000
--- a/components/groups/subGroup.tsx
+++ /dev/null
@@ -1,156 +0,0 @@
-'use client';
-
-import { type ReactNode, useState } from 'react';
-
-import { ChevronRight } from 'lucide-react';
-import { SectionIcon } from '@radix-ui/react-icons';
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/ui/collapsible';
-import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarMenuSub } from '@/ui/sidebar';
-
-import { Input } from '@/ui/input';
-import Context from './context';
-import { EditableItem } from './item';
-
-import { MdNoteAdd } from 'react-icons/md';
-import { MdDeleteSweep } from 'react-icons/md';
-import { MdOutlineDriveFileRenameOutline } from 'react-icons/md';
-
-export default function SubGroup({
- children,
- onCreateNewPost,
- defaultOpen,
- onRename,
- onDelete,
- ...subGroup
-}: Props) {
- const [deleted, setDeleted] = useState(false);
- const [toRename, setToRename] = useState(false);
-
- const [name, setName] = useState(subGroup.name);
- const [newPost, setNewPost] = useState(null);
-
- if (deleted) return null;
-
- return (
-
-
-
- {toRename ? (
- {
- setToRename(false);
- if (name !== subGroup.name)
- onRename({
- id: subGroup.id,
- name,
- });
- }}>
-
- setName(e.target.value)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- setToRename(false);
- if (name !== subGroup.name)
- onRename({
- id: subGroup.id,
- name,
- });
- }
- }}
- />
-
- ) : (
-
- New Post
-
-
- ),
-
- onClick: () => setNewPost(''),
- },
- {
- children: (
-
- Rename
-
-
- ),
- onClick: () => {
- setName(subGroup.name);
- setToRename(true);
- },
- },
- {
- children: (
-
- Delete
-
-
- ),
- className: 'text-red-400',
- onClick: () => onDelete(subGroup.id),
- },
- ]}>
-
-
-
- {name}
-
-
-
-
- )}
-
- setNewPost(null)}>
- {typeof newPost === 'string' && (
- {
- if (
- !rename ||
- !rename.trim().length ||
- rename === name
- )
- return setNewPost(null);
- onCreateNewPost(rename);
- setNewPost(null);
- }}
- />
- )}
- {children}
-
-
-
-
-
- );
-}
-
-interface Actions {
- onCreateNewPost: (name: string) => void;
- onRename: ({ id, name }: { id: string; name: string }) => void;
- onDelete: (subGroupId: string) => void;
-}
-
-interface Props extends Actions {
- children?: ReactNode | ReactNode[];
- id: string;
- name: string;
- defaultOpen?: boolean;
-}
diff --git a/components/imageLoader.tsx b/components/imageLoader.tsx
deleted file mode 100644
index cc31b63..0000000
--- a/components/imageLoader.tsx
+++ /dev/null
@@ -1,99 +0,0 @@
-'use client';
-import { useRef, useState } from 'react';
-import Image from 'next/image';
-
-import { Input } from '@/ui/input';
-import { Button } from '@/ui/button';
-
-const DEMO_PLACEHOLDER =
- 'data:image/svg+xml;utf8,Demo Image ';
-
-const resolveImageSrc = (value?: string) => {
- if (!value) return DEMO_PLACEHOLDER;
- if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') || value.startsWith('data:')) return value;
-
- return DEMO_PLACEHOLDER;
-};
-
-export const ImageLoader = ({
- image: cdlImage,
- onFileChange,
- ...props
-}: React.ComponentProps<'div'> & {
- image: { display_name: string; public_id: string };
- onFileChange: (file: File) => void;
-}) => {
- const fileInput = useRef(null);
-
- const [image, setImage] = useState<{ display_name: string; public_id: string } | null>(cdlImage);
- const [preview, setPreview] = useState(null); // Preview state
-
- const handleFileChange = () => {
- const file = fileInput.current?.files?.[0];
-
- if (!file) return;
- // Validate the selected file format
- const validFormats = ['image/webp', 'image/png', 'image/jpeg', 'image/jpg'];
- if (!validFormats.includes(file.type)) {
- alert('Unsupported file format. Use WebP, PNG, JPEG, or JPG.');
- setPreview(null);
-
- return;
- }
-
- // Create a temporary preview
- setPreview(URL.createObjectURL(file));
- onFileChange(file);
- setImage(null);
- };
-
- return (
-
-
-
- {!!preview ?
- <>
-
-
setPreview(null)}>
- X
-
- >
- : image?.public_id ?
- <>
-
-
setImage(null)}>
- X
-
- >
- :
fileInput.current?.click()}>
- Upload cover image
-
- }
-
-
- );
-};
diff --git a/components/loader.tsx b/components/loader.tsx
deleted file mode 100644
index 91ba706..0000000
--- a/components/loader.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-import { ThreeDot, type ThreeDotProps } from 'react-loading-indicators';
-export default (props: ThreeDotProps) => ;
diff --git a/components/members-page.tsx b/components/members-page.tsx
deleted file mode 100644
index 94862d0..0000000
--- a/components/members-page.tsx
+++ /dev/null
@@ -1,313 +0,0 @@
-'use client';
-
-import { Avatar, AvatarFallback, AvatarImage } from '@/ui/avatar';
-import { Button } from '@/ui/button';
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuTrigger,
-} from '@/ui/dropdown-menu';
-import { Input } from '@/ui/input';
-import { Popover, PopoverContent, PopoverTrigger } from '@/ui/popover';
-import { Copy, RefreshCw, Search, ChevronDown, Plus, MoreVertical } from 'lucide-react';
-import { Check } from 'lucide-react';
-import Image from 'next/image';
-import Link from 'next/link';
-import { useState } from 'react';
-import { AddMemberDialog } from '@/components/add-member-dialog';
-import { ShareableCard } from '@/components/shareable-card';
-import { CopyPopup } from '@/components/copy-popup';
-
-interface Member {
- id: string;
- name: string;
- email: string;
- role: 'Admin' | 'Cooperator' | 'Supervisor' | string;
- avatar: string;
- invitationPending?: boolean;
- secretToken: string;
-}
-
-const roleDescriptions = {
- Admin: 'Full access to all resources and settings',
- Cooperator: 'Can collaborate on projects and access most resources',
- Supervisor: 'Can oversee projects and team members, with limited admin capabilities',
-};
-
-export default function MembersPage({ members }: { members: Member[] }) {
- const [showAddMember, setShowAddMember] = useState(false);
- const [memberList, setMemberList] = useState(members);
- const [showCopyPopup, setShowCopyPopup] = useState(false);
- const [customRoles, setCustomRoles] = useState([]);
-
- const resetToken = (memberId: string) => {
- setMemberList(
- memberList.map(member =>
- member.id === memberId
- ? {
- ...member,
- secretToken: Math.random()
- .toString(36)
- .substring(2, 15),
- }
- : member
- )
- );
- };
-
- const copyToken = (token: string) => {
- navigator.clipboard.writeText(token);
- setShowCopyPopup(true);
- setTimeout(() => setShowCopyPopup(false), 2000);
- };
-
- const addCustomRole = () => {
- const newRole = prompt('Enter the name of the new role:');
- if (newRole && !customRoles.includes(newRole)) {
- setCustomRoles([...customRoles, newRole]);
- }
- };
-
- const changeRole = (memberId: string, newRole: string) => {
- setMemberList(
- memberList.map(member =>
- member.id === memberId ? { ...member, role: newRole } : member
- )
- );
- };
-
- return (
-
-
-
Users
-
Manage members access
-
-
-
-
-
setShowAddMember(true)}>
- Add member
-
-
-
-
- {memberList.map(member => (
-
-
-
-
-
-
- {member.name.charAt(
- 0
- )}
-
-
-
-
- {member.name}
-
-
- {member.email}
-
-
-
-
-
-
- Secret token
-
-
- {member.secretToken}
-
-
-
-
- {member.invitationPending ? (
-
-
- Invitation pending
-
-
- Resend invitation
-
-
- ) : (
- <>
-
-
-
-
-
-
-
-
- copyToken(
- member.secretToken
- )
- }>
- Copy token
-
-
- resetToken(
- member.id
- )
- }>
- Reset token
-
-
- Remove
- member
-
-
-
-
-
- resetToken(
- member.id
- )
- }>
-
- Reset
-
-
- copyToken(
- member.secretToken
- )
- }>
-
- Copy
-
-
-
-
-
-
- {
- member.role
- }
-
-
-
-
-
- {Object.entries(
- roleDescriptions
- ).map(
- ([
- role,
- description,
- ]) => (
-
- changeRole(
- member.id,
- role
- )
- }>
-
-
- {
- role
- }
-
-
- {
- description
- }
-
-
- {member.role ===
- role && (
-
- )}
-
- )
- )}
- {customRoles.map(
- role => (
-
- changeRole(
- member.id,
- role
- )
- }>
-
-
- {
- role
- }
-
-
- Custom
- role
-
-
- {member.role ===
- role && (
-
- )}
-
- )
- )}
-
-
- Add
- custom
- role
-
-
-
-
-
- >
- )}
-
- ))}
-
-
-
-
-
- );
-}
diff --git a/components/nav-main.tsx b/components/nav-main.tsx
deleted file mode 100644
index 51414b5..0000000
--- a/components/nav-main.tsx
+++ /dev/null
@@ -1,370 +0,0 @@
-'use client';
-
-import { useCallback, useState } from 'react';
-
-import { ChevronRight, type LucideIcon } from 'lucide-react';
-import { SectionIcon } from '@radix-ui/react-icons';
-import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/ui/collapsible';
-import {
- ContextMenu,
- ContextMenuContent,
- ContextMenuItem,
- ContextMenuTrigger,
-} from '@/ui/context-menu';
-import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuButton,
- SidebarMenuItem,
- SidebarMenuSub,
- SidebarMenuSubButton,
- SidebarMenuSubItem,
-} from '@/ui/sidebar';
-
-import Link from 'next/link';
-import { Input } from './ui/input';
-import { CgSortAz } from 'react-icons/cg';
-import { Plus } from 'lucide-react';
-import type { PropsWithChildren } from 'react';
-import { Separator } from './ui/separator';
-import { cn } from '@/utils/shadcn';
-
-import type { Group } from '@/_server/@dashboard/( journals )';
-
-const Context = ({
- actions,
- children,
-}: PropsWithChildren & { actions: React.ComponentPropsWithoutRef[] }) => {
- return (
-
- {children}
-
-
- {actions.slice(1).map(({ className, ...props }, i) => (
- <>
-
-
- >
- ))}
-
-
- );
-};
-
-interface Actions {
- group: {
- onCreate: (name: string) => void;
- onRename: ({ _id, name }: { _id: string; name: string }) => void;
- onDelete: (groupId: string) => void;
- };
- subGroup: {
- onCreate: (groupId: string, subGroupName: string) => void;
- onRename: ({ _id, name }: { _id: string; name: string }) => void;
- onDelete: (subGroupId: string) => void;
- };
-}
-
-interface Props extends Actions {
- groups: Group[];
- section: string;
-}
-
-export default function NavMain({ section, groups: initGroups, ...actions }: Props) {
- const [groups, setGroups] = useState(initGroups);
- const [addGroup, setAddGroup] = useState(null);
-
- return (
- <>
-
-
-
-
-
-
-
{
- console.log('addGroup', addGroup);
- setAddGroup('');
- }}>
-
-
-
-
- {typeof addGroup === 'string' && (
-
- )}
- {groups.map(group => (
- {
- actions.group.onDelete(_id);
- setGroups(prev => prev.filter(g => g._id !== _id));
- }}
- onRenameGroup={({ _id, name }) => {
- actions.group.onRename({ _id, name });
- setGroups(prev => {
- return prev.map(g => {
- if (g._id === _id) {
- return {
- ...g,
- name,
- };
- }
- return g;
- });
- });
- }}
- />
- ))}
- >
- );
-}
-
-const Edit = {
- group: ({ addGroup, setAddGroup, onCreateGroup, setGroups }) => {
- return (
-
- setAddGroup(e.target.value)}
- onBlur={e => setAddGroup(null)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- onCreateGroup(addGroup);
- setGroups(prev => [
- {
- id: addGroup,
- name: addGroup,
- sub_groups: [],
- },
- ...prev,
- ]);
- setAddGroup(null);
- }
- }}
- />
-
- );
- },
- subGroup: ({ addSubGroup, setAddSubGroup, onCreateSubGroup }) => (
-
- setAddSubGroup(null)}>
-
-
- setAddSubGroup({
- ...addSubGroup,
- subGroup: e.target.value,
- })
- }
- onBlur={() => setAddSubGroup(null)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- const { group, subGroup } = addSubGroup;
- onCreateSubGroup(group._id, subGroup);
- }
- }}
- />
-
-
- ),
-};
-
-const Group = ({
- _id,
- name,
- section,
- sub_groups,
- onDeleteGroup,
- onRenameGroup,
- onCreateSubGroup,
-}: Group & Actions) => {
- const [rename, setRename] = useState(null);
- const [subGroups, setSubGroups] = useState(sub_groups);
- const [addGroup, setAddGroup] = useState(null);
- const [addSubGroup, setAddSubGroup] = useState<{ group: Group; subGroup: string } | null>(
- null
- );
- return (
-
- setRename(name) },
- {
- children: 'Delete',
- className: 'text-red-400',
- onClick: () => onDeleteGroup(_id),
- },
- ]}>
-
- {typeof rename === 'string' ? (
- setRename(e.target.value)}
- onBlur={() => setRename(null)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- onRenameGroup({
- _id,
- name: rename,
- });
- setRename(null);
- }
- }}
- />
- ) : (
- <>
- {name}
-
-
-
-
-
-
- setAddSubGroup({
- group: {
- _id,
- name,
- },
- subGroup: '',
- })
- }>
-
-
-
- >
- )}
-
-
-
- {addSubGroup && (
-
- setAddSubGroup(null)}>
-
-
- setAddSubGroup({
- ...addSubGroup,
- subGroup: e.target
- .value,
- })
- }
- onBlur={() => setAddSubGroup(null)}
- onKeyDown={e => {
- if (e.key === 'Enter') {
- const {
- group,
- subGroup,
- } = addSubGroup;
- onCreateSubGroup(
- group._id,
- subGroup
- );
- }
- }}
- />
-
-
- )}
- {subGroups &&
- subGroups.map(sub_group => (
-
- ))}
-
-
- );
-};
-
-const Item = ({ _id, name, url, icon: Icon }: Group['sub_groups'][number]['items'][number]) => {
- return (
-
-
-
- {subItem.title}
-
-
-
- );
-};
-
-const SubGroup = ({ _id, name, items }: Group['sub_groups'][number] & Actions['']) => {
- const [rename, setRename] = useState(null);
-
- return (
- setRename(name) }]}>
-
-
- {typeof rename === 'string' ? (
- setRename(null)}>
-
-
- setRename(e.target.value)
- }
- onBlur={() => setRename(null)}
- />
-
-
- ) : (
-
-
-
- {name}
-
-
-
- )}
-
-
-
- {items?.map(subItem => (
-
- ))}
-
-
-
-
-
- );
-};
diff --git a/components/nav-projects.tsx b/components/nav-projects.tsx
deleted file mode 100644
index 662777f..0000000
--- a/components/nav-projects.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-"use client";
-
-import { Folder, MoreHorizontal, Share, Trash2, type LucideIcon } from "lucide-react";
-
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/ui/dropdown-menu";
-import {
- SidebarGroup,
- SidebarGroupLabel,
- SidebarMenu,
- SidebarMenuAction,
- SidebarMenuButton,
- SidebarMenuItem,
- useSidebar,
-} from "@/ui/sidebar";
-
-export function NavProjects({
- projects,
-}: {
- projects: {
- name: string;
- url: string;
- icon: LucideIcon;
- }[];
-}) {
- const { isMobile } = useSidebar();
-
- return (
-
- Projects
-
- {projects.map((item) => (
-
-
-
-
- {item.name}
-
-
-
-
-
-
- More
-
-
-
-
-
- View Project
-
-
-
- Share Project
-
-
-
-
- Delete Project
-
-
-
-
- ))}
-
-
-
- More
-
-
-
-
- );
-}
diff --git a/components/nav-secondary.tsx b/components/nav-secondary.tsx
deleted file mode 100644
index 2e5fff2..0000000
--- a/components/nav-secondary.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import * as React from "react";
-import { type LucideIcon } from "lucide-react";
-
-import { SidebarGroup, SidebarGroupContent, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/ui/sidebar";
-
-export function NavSecondary({
- items,
- ...props
-}: {
- items: {
- title: string;
- url: string;
- icon: LucideIcon;
- }[];
-} & React.ComponentPropsWithoutRef) {
- return (
-
-
-
- {items.map((item) => (
-
-
-
-
- {item.title}
-
-
-
- ))}
-
-
-
- );
-}
diff --git a/components/nav-user.tsx b/components/nav-user.tsx
deleted file mode 100644
index a90564e..0000000
--- a/components/nav-user.tsx
+++ /dev/null
@@ -1,100 +0,0 @@
-"use client";
-
-import { BadgeCheck, Bell, ChevronsUpDown, CreditCard, LogOut, Sparkles } from "lucide-react";
-
-import { Avatar, AvatarFallback, AvatarImage } from "@/ui/avatar";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuGroup,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/ui/dropdown-menu";
-import { SidebarMenu, SidebarMenuButton, SidebarMenuItem, useSidebar } from "@/ui/sidebar";
-
-export function NavUser({
- user,
-}: {
- user: {
- name: string;
- email: string;
- avatar: string;
- };
-}) {
- const { isMobile } = useSidebar();
-
- return (
-
-
-
-
-
-
-
- CN
-
-
- {user.name}
- {user.email}
-
-
-
-
-
-
-
-
-
- CN
-
-
-
- {user.name}
-
- {user.email}
-
-
-
-
-
-
-
- Upgrade to Pro
-
-
-
-
-
-
- Account
-
-
-
- Billing
-
-
-
- Notifications
-
-
-
-
-
- Log out
-
-
-
-
-
- );
-}
diff --git a/components/shareable-card.tsx b/components/shareable-card.tsx
deleted file mode 100644
index 1ee87e1..0000000
--- a/components/shareable-card.tsx
+++ /dev/null
@@ -1,89 +0,0 @@
-'use client';
-
-import { Button } from '@/ui/button';
-import { Share2 } from 'lucide-react';
-import Image from 'next/image';
-import { useRef, useState } from 'react';
-import html2canvas from 'html2canvas';
-
-interface ShareableCardProps {
- member: {
- name: string;
- role: string;
- avatar: string;
- };
-}
-
-export function ShareableCard({ member }: ShareableCardProps) {
- const [makeCard, setMakeCard] = useState(false);
- const cardRef = useRef(null);
-
- const generateCard = async () => {
- if (!cardRef.current) return;
-
- setMakeCard(true);
- await new Promise(resolve => setTimeout(resolve, 1000));
- const canvas = await html2canvas(cardRef.current);
- const dataUrl = canvas.toDataURL('image/webp');
- const link = document.createElement('a');
- link.href = dataUrl;
- link.download = `${member.name.replace(' ', '_')}_card.webp`;
- link.click();
-
- setMakeCard(false);
- };
-
- return (
- <>
-
-
- Share Card
-
-
-
-
-
-
-
- {member.name}
-
-
- {member.role}
-
-
-
-
-
- Proud member of
-
-
-
-
- Acme
-
-
-
-
-
- >
- );
-}
diff --git a/components/theme-switcher.tsx b/components/theme-switcher.tsx
deleted file mode 100644
index ed2d440..0000000
--- a/components/theme-switcher.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-'use client';
-import { useState } from 'react';
-import switchTheme from '@/utils/switch-theme';
-import { Switch } from '@/ui/switch';
-import { BsMoonStars } from 'react-icons/bs';
-import { LucideSunMoon } from 'lucide-react';
-import { useTheme } from '@/hooks/use-theme';
-
-export default function ThemeSwitcher() {
- const [theme, setTheme] = useTheme();
- const [checked, setChecked] = useState(theme === 'dark');
- return (
- {
- setChecked(c);
- setTheme(switchTheme(true));
- }}
- icon={checked ? : }
- />
- );
-}
diff --git a/components/typewriter.tsx b/components/typewriter.tsx
deleted file mode 100644
index 6e8b6cc..0000000
--- a/components/typewriter.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import { useTypewriter } from '@/hooks/use-typewriter';
-
-const Typewriter = ({ text, delay, speed, ...props }: { text: string; delay?: number; speed?: number } & any) => {
- const displayText = useTypewriter(text, delay, speed);
-
- return {displayText}
;
-};
-
-export default Typewriter;
diff --git a/components/webapp/_export.tsx b/components/webapp/_export.tsx
deleted file mode 100644
index 32d9bac..0000000
--- a/components/webapp/_export.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client';
-
-import Status_bar from './status_bar';
-import Header from './header';
-import Inner from './inner';
-import Preview from './preview';
-import type { Post } from '@/types/post';
-
-export default ({
- page,
- className,
- ...props
-}: {
- page: 'preview' | 'inner';
- className?: string;
-} & Post) => {
- return (
-
-
-
- {page === 'preview' ?
:
}
-
- );
-};
diff --git a/components/webapp/header.tsx b/components/webapp/header.tsx
deleted file mode 100644
index cc7e5e3..0000000
--- a/components/webapp/header.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-"use client";
-import Image from "next/image";
-import { useEffect, useState } from "react";
-import { motion } from "framer-motion";
-
-import Link from "next/link";
-import { BiStats } from "react-icons/bi";
-import { CgGym } from "react-icons/cg";
-import { IoPersonSharp } from "react-icons/io5";
-import { LuSunMoon } from "react-icons/lu";
-import { BsMoonStars } from "react-icons/bs";
-
-import { usePathname } from "next/navigation";
-import { Switch } from "@/ui/switch";
-import switchTheme from "@/utils/switch-theme";
-import { TbMenuDeep } from "react-icons/tb";
-
-export default () => {
- const tabs = [
- { href: "/stats", label: "Stats", icon: { Element: BiStats, color: "#F34284" } },
- { href: "/workout", label: "Workout", icon: { Element: CgGym, color: "#84f342" } },
- { href: "/profile", label: "Profile", icon: { Element: IoPersonSharp, color: "#4a7ef2" } },
- ];
-
- const pathname = usePathname();
- const [active, setActive] = useState(1);
-
- const [theme, setTheme] = useState();
- const [checked, setChecked] = useState(theme === "dark");
-
- useEffect(() => {
- const resolvedTheme = switchTheme(false);
- setTheme(resolvedTheme);
- setChecked(resolvedTheme === "dark");
- }, []);
-
- return (
- <>
-
-
-
- >
- );
-};
diff --git a/components/webapp/heading.tsx b/components/webapp/heading.tsx
deleted file mode 100644
index 9cd67b3..0000000
--- a/components/webapp/heading.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import Typewriter from '@/components/typewriter';
-import { BsCalendar2WeekFill } from 'react-icons/bs';
-
-export default () => {
- const date = new Date(Date.now()).toDateString().split(' ').slice(0, -1).join(' ');
-
- return (
-
- );
-};
diff --git a/components/webapp/inner.tsx b/components/webapp/inner.tsx
deleted file mode 100644
index 4f369c8..0000000
--- a/components/webapp/inner.tsx
+++ /dev/null
@@ -1,79 +0,0 @@
-import { useCallback } from 'react';
-
-import Image from 'next/image';
-import { Separator } from '@/ui/separator';
-
-import type { Post } from '@/types/post';
-
-const DEMO_PLACEHOLDER =
- 'data:image/svg+xml;utf8,Demo Image ';
-
-const resolveImageSrc = (value?: string) => {
- if (!value) return DEMO_PLACEHOLDER;
- if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') || value.startsWith('data:')) return value;
-
- return DEMO_PLACEHOLDER;
-};
-
-export default ({ image, title, date, kind, description }: Omit & { image: { public_id?: string; src?: string } }) => {
- const RenderImage = useCallback(
- ({ public_id, src }: { public_id?: string; src?: string }) => (
-
- ),
- [image],
- );
- return (
-
-
- {kind &&
{kind} }
-
{title}
-
- {typeof date === 'object' ?
- <>
- {date &&
- date.from &&
- new Date(date.from).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })}
-
- {date &&
- date.to &&
- ' — ' +
- new Date(date.to).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })}
- >
- : date &&
- new Date(date).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })
- }
-
-
-
-
-
-
-
- );
-};
diff --git a/components/webapp/preview.tsx b/components/webapp/preview.tsx
deleted file mode 100644
index 0c9a64e..0000000
--- a/components/webapp/preview.tsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import Image from 'next/image';
-import { useCallback } from 'react';
-
-import type { Post } from '@/types/post';
-
-const DEMO_PLACEHOLDER =
- 'data:image/svg+xml;utf8,Demo Image ';
-
-const resolveImageSrc = (value?: string) => {
- if (!value) return DEMO_PLACEHOLDER;
- if (value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') || value.startsWith('data:')) return value;
-
- return DEMO_PLACEHOLDER;
-};
-
-export default ({ image, title, date, kind }: Omit & { image: { public_id?: string; src?: string } }) => {
- console.log('date =>', date);
- const RenderImage = useCallback(
- ({ public_id, src }: { public_id?: string; src?: string }) => (
-
- ),
- [image],
- );
- return (
-
-
- {kind && (
-
- {kind}
-
- )}
-
-
-
-
{title}
-
- {typeof date === 'object' ?
- <>
- {date &&
- date.from &&
- new Date(date.from).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })}
-
- {date &&
- date.to &&
- ' — ' +
- new Date(date.to).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })}
- >
- : date &&
- new Date(date).toLocaleString(undefined, {
- month: 'long',
- day: 'numeric',
- year: 'numeric',
- })
- }
-
-
-
- );
-};
diff --git a/components/webapp/status_bar.tsx b/components/webapp/status_bar.tsx
deleted file mode 100644
index a4b3043..0000000
--- a/components/webapp/status_bar.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export default () =>
;
diff --git a/config/_export.ts b/config/_export.ts
deleted file mode 100644
index 291673c..0000000
--- a/config/_export.ts
+++ /dev/null
@@ -1,2 +0,0 @@
-import './css/init.css';
-import './css/vars.css';
diff --git a/config/css/init.css b/config/css/init.css
deleted file mode 100644
index bf82693..0000000
--- a/config/css/init.css
+++ /dev/null
@@ -1,133 +0,0 @@
-@tailwind base;
-
-
-@layer base {
- * {
- @apply border-border;
- }
-
- html {
- background-color: transparent !important;
- position: relative !important;
- max-width: 100vw !important;
- min-height: 100vh !important;
- min-height: 100dvh !important;
- min-height: -webkit-fill-available !important;
- display: block;
- border: 0;
- outline: 0;
- margin: 0;
- padding: 0;
- overflow-x: hidden !important;
- overflow-y: scroll !important;
- -webkit-overflow-scrolling: touch !important;
- -webkit-text-size-adjust: 100%;
- line-height: 1.5;
- -webkit-text-size-adjust: 100%;
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
- font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans,
- Droid Sans, Helvetica Neue, sans-serif;
- font-feature-settings: normal;
- font-variation-settings: normal;
- }
-
- body {
- --_max-width: var(--max-width, 1680px);
- --_padding-n: var(--padding-n, 1.15);
- --_padding: var(--padding, calc(1rem * var(--_padding-n)));
- --_border: var(--border, #dddae7);
- --_shadow-offset: var(--border, 0 0 3px -1px);
- --_shadow-color: var(--border, #fff);
-
- height: 100vh;
- height: 100svh;
-
- width: 100vw;
- max-width: 100vw;
- /* max-width: var(--_max-width, 1680px); */
-
- @apply /* Layout */
- relative block overflow-hidden
- /* Alignments */
- justify-center items-center
- /* Spacing */
- my-0 mx-auto
- /* Themes */
- bg-background text-foreground;
- }
-
- #content {
- @apply contents;
-
- & > div {
- @apply relative flex justify-center items-center w-screen h-svh gap-[--external-p] mx-auto;
- }
- }
-
- input[type="number"]::-webkit-inner-spin-button,
- input[type="number"]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- margin: 0;
- }
-
- input:-webkit-autofill,
- input:-webkit-autofill:hover,
- input:-webkit-autofill:focus,
- input:-webkit-autofill:active {
- -webkit-background-clip: text;
- -webkit-text-fill-color: hsl(var(--foreground)) !important;
- box-shadow: inset 0 0 20px 20px hsl(var(--background)) !important;
- }
-
- ::-webkit-scrollbar-thumb {
- display: none !important;
- }
-
- ::-webkit-scrollbar {
- display: none !important;
- }
-
- [style*="overflow-x:scroll"]::-webkit-scrollbar,
- .overflow-x-scroll::-webkit-scrollbar {
- display: none !important;
- }
-
- ::-webkit-scrollbar:horizontal {
- height: 0;
- width: 0;
- display: none;
- -ms-overflow-style: none;
- scrollbar-width: none;
- }
-
- ::-webkit-scrollbar {
- z-index: 100;
- overflow: visible;
- width: 0px !important;
- margin-left: 0px !important;
- }
-
- ::-webkit-scrollbar-track {
- box-shadow: inset 0 0 3px grey;
- border-radius: 25px;
- }
-
- ::-webkit-scrollbar-thumb {
- box-shadow: inset 0 0 0 1px #0000001f;
- border-radius: 25px;
- }
-
- ::-moz-scrollbar {
- width: calc(0.5rem + 0.05vw) !important;
- }
-
- ::-webkit-scrollbar-thumb {
- background: #454547;
- }
-
- ::-webkit-scrollbar-thumb:active {
- background: #000000 !important;
- }
-}
diff --git a/config/css/vars.css b/config/css/vars.css
deleted file mode 100644
index 1c27dec..0000000
--- a/config/css/vars.css
+++ /dev/null
@@ -1,11 +0,0 @@
-@tailwind base;
-
-
-@layer base {
- :root {
- --sidebar-p: clamp(0.35rem, 0.65vw, 0.6rem);
- --external-p: calc(var(--sidebar-p) * 1.1);
- --p: calc(var(--sidebar-p) + var(--external-p));
- --header-h: clamp(4rem, 8vw, 8rem);
- }
-}
diff --git a/constants/post-init-template.ts b/constants/post-init-template.ts
deleted file mode 100644
index ab05245..0000000
--- a/constants/post-init-template.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-export default {
- featured: {
- name: 'New Featured Story',
- title: 'Add Title',
- description: 'Add description',
- date: new Date().toISOString(),
- state: 'Draft',
- },
- design: {
- name: 'New Design Article',
- title: 'Add Title',
- description: 'Add description',
- date: new Date().toISOString(),
- state: 'Draft',
- },
- culture: {
- name: 'New Culture Story',
- title: 'Add Title',
- description: 'Add description',
- date: new Date().toISOString(),
- state: 'Draft',
- },
- insights: {
- name: 'New Insight',
- title: 'Add Title',
- description: 'Add description',
- date: new Date().toISOString(),
- state: 'Draft',
- },
- resources: {
- name: 'New Resource',
- title: 'Add Title',
- description: 'Add description',
- date: new Date().toISOString(),
- state: 'Draft',
- },
-};
diff --git a/constants/sections.ts b/constants/sections.ts
deleted file mode 100644
index a7e28fc..0000000
--- a/constants/sections.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-export const sections = ['featured', 'design', 'culture', 'insights', 'resources'] as const;
-
-export const sectionLabels: Record = {
- featured: {
- title: 'Featured Stories',
- description: 'Curated highlights and trending content',
- },
- design: {
- title: 'Design & Creative',
- description: 'Design trends, visual systems, and creative insights',
- },
- culture: {
- title: 'Culture & Arts',
- description: 'Cultural commentary, exhibitions, and creative discourse',
- },
- insights: {
- title: 'Insights & Analysis',
- description: 'Industry trends, research, and thought leadership',
- },
- resources: {
- title: 'Resources & Guides',
- description: 'Tutorials, guides, and tools for professional growth',
- },
-};
diff --git a/docs/accessibility.md b/docs/accessibility.md
new file mode 100644
index 0000000..658a4ff
--- /dev/null
+++ b/docs/accessibility.md
@@ -0,0 +1,26 @@
+# Accessibility evidence
+
+AutoBlog makes targeted, tested accessibility claims; this is not a blanket WCAG certification.
+
+## Automated and keyboard evidence
+
+- axe-core serious/critical checks: marketing, sign-in, authenticated workspace and public preview.
+- Keyboard E2E: role activation, draft creation, title/body entry, autosave and submit for review.
+- Focus management: draft creation and post selection move focus to the editable title; skip link and
+ visible `:focus-visible` outline remain available.
+- Form/error semantics: every editor, media, schedule and AI field has a programmatic label; blocking
+ save errors use `role=alert`, progress messages use `role=status`.
+- Contrast: axe color-contrast rules execute on all four primary surfaces; neon accent uses dark ink.
+- Reduced motion: the browser test emulates `prefers-reduced-motion: reduce` and verifies smooth
+ scrolling/animation duration are disabled.
+- Responsive review: versioned Chromium baselines cover desktop marketing/sign-in/workspace/preview
+ and 390×844 marketing/workspace surfaces.
+
+Commands: `bun run test:a11y` and `bun run test:visual`. Visual baselines are reviewed on the pinned
+Windows/Chromium CI job to avoid cross-platform font rasterization noise.
+
+## Manual review scope
+
+The flagship focus order follows sidebar navigation → editor controls → evidence rail on desktop and
+the same DOM order on mobile. No primary command depends only on hover, drag or pointer gestures.
+Screen-reader combinations beyond axe semantic inspection remain a known manual-audit limitation.
diff --git a/docs/ai-data-handling.md b/docs/ai-data-handling.md
new file mode 100644
index 0000000..1def82a
--- /dev/null
+++ b/docs/ai-data-handling.md
@@ -0,0 +1,34 @@
+# AI data handling and governance
+
+## Contract
+
+AI is an authenticated suggestion command, not an editorial mutation. Input is capped at 180 title,
+320 excerpt, 20,000 content and 500 instruction characters. Output is parsed through the same bounded
+schema for mock and Gemini modes. The UI renders a preview and creates a revision only after the user
+selects **Apply suggestion**.
+
+The deterministic mock adapter is labeled `Mock / demo`, makes no network call and reports no token
+counts. Configured mode uses `@google/genai`, structured JSON, 2,048 maximum output tokens, low
+temperature and an eight-second abort signal. The official SDK notes that client abort does not
+guarantee provider cancellation and applicable work may still be charged:
+[GenerateContentConfig](https://googleapis.github.io/js-genai/release_docs/interfaces/types.GenerateContentConfig.html).
+
+## Limits and retained data
+
+- Five requests per authenticated user/workspace per minute, persisted in the database.
+- Monthly workspace character budget configured by `AI_MONTHLY_CHARACTER_QUOTA`.
+- The worst-case request/output budget is reserved transactionally before provider invocation and
+ released on any failure; concurrent calls cannot oversubscribe the workspace.
+- Success stores mode, provider, model, latency, input/output character counts and only token counts
+ returned by the provider. No token count or cost is estimated.
+- Prompt, draft body, suggestion body, API key, raw provider response and error details are not stored
+ in usage or audit records.
+
+Gemini receives the bounded draft and instruction when and only when `AI_MODE=gemini`; configured mode
+therefore requires the operator to accept Google's data-processing terms. Demo/CI remain mock mode.
+
+## Failure behavior
+
+Timeout, cancellation, malformed JSON, schema violations and provider errors map to
+`PROVIDER_UNAVAILABLE` with a correlation ID. Quota and rate failures occur before adapter access.
+Secrets and provider messages are diagnostic-only and never enter public envelopes.
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 0000000..f82c5d0
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,47 @@
+# API contract
+
+Protected application routes use the envelope `{ "data": ... }`. Failures use:
+
+```json
+{
+ "error": {
+ "code": "VERSION_CONFLICT",
+ "message": "A newer version exists. Reload or compare before saving.",
+ "requestId": "correlation-id"
+ }
+}
+```
+
+The `x-request-id` response header mirrors the correlation ID. Stack traces, SQL/provider messages
+and credentials are diagnostic-only and are never returned.
+
+| Endpoint | Method | Permission | Success |
+| --- | --- | --- | --- |
+| `/api/auth/[...all]` | Better Auth GET/POST | public/session | session contract; stable error code + request ID |
+| `/api/workspaces/:workspaceId/posts` | GET | `workspace.read` | post summaries |
+| `/api/workspaces/:workspaceId/posts` | POST | `post.create` | revision-backed post, HTTP 201 |
+| `/api/workspaces/:workspaceId/posts/:postId` | GET | `workspace.read` | current draft revision |
+| `/api/workspaces/:workspaceId/posts/:postId` | PATCH | `post.update` | new immutable revision or HTTP 409 |
+| `/api/workspaces/:workspaceId/posts/:postId/revisions` | GET | `workspace.read` | immutable history, newest first |
+| `/api/workspaces/:workspaceId/posts/:postId/revisions/restore` | POST | `revision.restore` | restored content as a new revision |
+| `/api/workspaces/:workspaceId/posts/:postId/transitions` | POST | action-specific policy | versioned workflow result |
+| `/api/workspaces/:workspaceId/media?postId=…` | GET | `workspace.read` | active workspace/post asset |
+| `/api/workspaces/:workspaceId/media` | POST multipart | `media.upload` + ownership | decoded and activated asset |
+| `/api/workspaces/:workspaceId/media/:assetId` | GET | `workspace.read` | private verified bytes |
+| `/api/workspaces/:workspaceId/media/:assetId` | DELETE | `media.delete` | logical deletion + cleanup job |
+| `/api/workspaces/:workspaceId/ai/suggest` | POST | `ai.suggest` | labeled, metered suggestion only |
+| `/api/workspaces/:workspaceId/demo/reset` | POST | Owner + demo guard | idempotent bounded fixture result |
+| `/api/jobs/run` | POST | `jobs.run` or scheduler secret | bounded publication/media execution results |
+| `/preview/:workspaceSlug/:postSlug` | GET | public | immutable published revision or 404 |
+| `/api/health` | GET | public | readiness without secret details |
+
+Mutation adapters require a trusted `Origin` in addition to the HTTP-only session cookie. Workspace
+and actor identity are always derived from that session; neither is accepted from JSON payloads.
+The scheduler adapter instead uses the distinct `x-autoblog-cron-secret`, compared by SHA-256 digest
+with constant-time equality; it never accepts workspace/job payloads from the caller.
+Application JSON responses are `private, no-store`; this does not alter static marketing or explicit
+public-preview revalidation.
+
+Stable application codes are `UNAUTHENTICATED`, `FORBIDDEN`, `NOT_FOUND`,
+`VALIDATION_FAILED`, `VERSION_CONFLICT`, `ILLEGAL_TRANSITION`, `QUOTA_EXCEEDED`,
+`RATE_LIMITED`, `PROVIDER_UNAVAILABLE` and `INTERNAL_FAILURE`.
diff --git a/docs/architecture.md b/docs/architecture.md
new file mode 100644
index 0000000..0c2495d
--- /dev/null
+++ b/docs/architecture.md
@@ -0,0 +1,33 @@
+# Architecture
+
+AutoBlog is a modular monolith. Browser code calls Next.js adapters; adapters establish a
+database-backed session and workspace membership before invoking an application service. Domain
+policies execute before a repository or provider. React components never import database clients.
+
+```mermaid
+flowchart LR
+ Browser --> Next[Next.js route / server adapter]
+ Next --> Session[Better Auth session]
+ Session --> Membership[Workspace membership + RBAC]
+ Membership --> Service[Application command/query]
+ Service --> Domain[Editorial invariants]
+ Service --> Repository[Repository port]
+ Repository --> LibSQL[(Drizzle / libSQL)]
+ Service --> Jobs[(Durable jobs)]
+ Service --> Media[Media port]
+ Service --> AI[AI suggestion port]
+```
+
+## Ownership boundaries
+
+- `src/modules/identity`: roles, permission matrix and workspace context.
+- `src/modules/editorial`: canonical post inputs, service and repository contract.
+- `src/modules/media`: verified asset storage and replacement policy.
+- `src/modules/ai`: bounded suggestion adapters and usage accounting.
+- `src/platform/auth`: Better Auth and origin/session adapters.
+- `src/platform/db`: schema, libSQL connection, versioned migration and seed infrastructure.
+- `src/platform/observability`: stable application errors and structured diagnostic logging.
+- `src/ui/patterns`: client product surfaces; no database/provider imports.
+
+The same Drizzle repository connects to a local durable `file:` URL and a remote libSQL URL. Demo
+accounts and content are validated seed records, not a separate business-logic path.
diff --git a/docs/authentication-rbac.md b/docs/authentication-rbac.md
new file mode 100644
index 0000000..5457c5b
--- /dev/null
+++ b/docs/authentication-rbac.md
@@ -0,0 +1,29 @@
+# Authentication and RBAC
+
+Better Auth issues 12-hour database sessions in signed, HTTP-only, SameSite=Lax cookies. Production
+requires a unique 32+ character secret and HTTPS enables Secure cookies. Sign-up is disabled;
+bounded demo accounts are installed by the validated seed. Sign-out deletes the session, and login
+and logout append workspace audit events. Authentication rate limits persist in libSQL.
+
+Every custom mutation first validates the session and requested membership, then verifies the
+request origin, then executes the application permission. Workspace identity is never read from a
+JSON payload.
+
+| Command | Owner | Admin | Editor | Author | Reviewer |
+| --- | --- | --- | --- | --- | --- |
+| Read workspace | Allow | Allow | Allow | Allow | Allow |
+| Create post | Allow | Allow | Allow | Allow | Deny |
+| Update/submit/restore own post | Allow | Allow | Allow | Allow own | Deny |
+| Delete post | Allow | Allow | Allow | Deny | Deny |
+| Request changes / approve | Allow | Allow | Allow | Deny | Allow |
+| Schedule / publish / archive | Allow | Allow | Allow | Deny | Deny |
+| Upload media | Allow | Allow | Allow | Allow own | Deny |
+| Delete media | Allow | Allow | Allow | Deny | Deny |
+| Request AI suggestion | Allow | Allow | Allow | Allow | Allow |
+| Manage membership | Allow | Allow | Deny | Deny | Deny |
+| Reset bounded demo | Allow | Deny | Deny | Deny | Deny |
+| Run jobs manually | Allow | Allow | Deny | Deny | Deny |
+
+`tests/unit/rbac-policy.test.ts` evaluates every role/command pair and explicitly verifies Author
+ownership denial. Integration and E2E tests verify arbitrary credentials, revocation, Reviewer
+denial, anonymous denial and cross-workspace isolation.
diff --git a/docs/data-model.md b/docs/data-model.md
new file mode 100644
index 0000000..d0633cb
--- /dev/null
+++ b/docs/data-model.md
@@ -0,0 +1,40 @@
+# Data model and persistence invariants
+
+Migration `drizzle/0000_editorial_core.sql` creates the application model; later migrations add
+authentication limits, media/AI governance and demo-reset idempotency. Checks, foreign keys and
+uniqueness are validated from an empty database in integration tests. Operational commands and the
+complete sequence are documented in [migrations.md](./migrations.md).
+
+```mermaid
+erDiagram
+ USER ||--o{ SESSION : owns
+ USER ||--o{ MEMBERSHIP : joins
+ WORKSPACE ||--o{ MEMBERSHIP : contains
+ WORKSPACE ||--o{ POST : owns
+ POST ||--o{ REVISION : records
+ POST ||--o{ PUBLICATION : publishes
+ REVISION ||--o{ PUBLICATION : pins
+ WORKSPACE ||--o{ MEDIA_ASSET : owns
+ MEDIA_ASSET }o--|| MEDIA_OBJECT : references
+ WORKSPACE ||--o{ AUDIT_EVENT : records
+ WORKSPACE ||--o{ AI_USAGE : meters
+ WORKSPACE ||--o{ JOB : schedules
+```
+
+- Every mutable business entity has `workspace_id`; application queries filter it and relational
+ constraints preserve ownership relationships.
+- `revisions` are insert-only: a database trigger rejects updates. Restore creates a new row.
+- `posts.version` is positive. Save inserts revision `expectedVersion + 1` and conditionally updates
+ the post; a concurrent duplicate or zero-row update maps to `VERSION_CONFLICT`.
+- `published_revision_id` and `publications.revision_id` point at immutable content.
+- Job and publication idempotency keys are unique.
+- Scheduled publications and jobs are inserted in one transaction. The payload pins the immutable
+ revision, and conditional 30-second leases support recovery without duplicate publication.
+- Audit events are append-only; a database trigger rejects updates.
+- Migration `0002` replaces the coupled media blob table with opaque `media_objects`, constrains one
+ active asset per post and adds transactional monthly AI quota windows.
+- Migration `0003` adds immutable operation/idempotency records that intentionally survive the demo
+ workspace cascade so a retry cannot trigger a second reset.
+
+Migrations are checksum-verified. Changing an applied SQL file fails with
+`MIGRATION_CHECKSUM_MISMATCH`; schema evolution requires a new numbered file.
diff --git a/docs/demo-guide.md b/docs/demo-guide.md
new file mode 100644
index 0000000..52872c6
--- /dev/null
+++ b/docs/demo-guide.md
@@ -0,0 +1,18 @@
+# Guided demo
+
+1. Open `/sign-in` and enter as **Author**. These are seeded database identities with normal
+ HTTP-only sessions; no authentication bypass is used.
+2. Select the first seeded draft, edit it and observe the saving/saved state. Reload to prove
+ persistence; open history to compare and restore an old revision as a new one.
+3. Submit for review. Sign out, enter as **Reviewer**, select the post and approve or request changes.
+4. Enter as **Editor**, publish or schedule the approved immutable revision. Open the public preview.
+5. Generate an AI suggestion in visibly labeled mock mode. Confirm the post remains unchanged until
+ **Apply suggestion**; the accepted result then follows ordinary autosave/version rules.
+6. Upload a PNG/JPEG/WebP cover. Replacement activates the verified object before cleanup of the old
+ one. Editor can delete it.
+7. Enter as **Owner** and use **Reset demo data**, then **Confirm reset**. Only `ws-demo` is rebuilt;
+ user sessions and any configured workspace remain intact.
+
+The checklist in the workspace derives completion from persisted version/state/publication data.
+Reset is Owner-only, origin-checked, limited to three distinct operations per hour and durable across
+restart. Repeating the same idempotency key returns the original result without another reset.
diff --git a/docs/deployment.md b/docs/deployment.md
new file mode 100644
index 0000000..d79a01c
--- /dev/null
+++ b/docs/deployment.md
@@ -0,0 +1,55 @@
+# Deployment runbook
+
+## Supported shape
+
+Deploy the Next.js modular monolith with a durable remote libSQL-compatible database and an external
+scheduler that calls the protected job adapter at least once per minute. A local `file:` database is
+valid for development and CI, not for ephemeral/serverless production storage.
+
+Required production configuration:
+
+```text
+NEXT_PUBLIC_APP_URL=https://your-canonical-origin.example
+DATABASE_URL=libsql://your-database.example
+DATABASE_AUTH_TOKEN=
+BETTER_AUTH_SECRET=
+DEMO_ENABLED=true|false
+AI_MODE=mock|gemini
+GEMINI_API_KEY=
+GEMINI_MODEL=gemini-2.5-flash
+CRON_SECRET=
+AI_MONTHLY_CHARACTER_QUOTA=200000
+MEDIA_MAX_BYTES=5242880
+AUTH_SIGN_IN_RATE_LIMIT=8
+```
+
+Never copy example values into production. `NEXT_PUBLIC_APP_URL` must exactly match the public HTTPS
+origin used by browsers or origin validation and session cookies will fail.
+
+## Release procedure
+
+1. Rotate/revoke all historical provider values identified in `SECURITY.md`.
+2. Create a database backup or restore point.
+3. Configure secrets in the deployment platform; do not place them in GitHub logs or source.
+4. Run `bun install --frozen-lockfile`, `bun run db:migrate`, `bun run build`.
+5. Deploy the immutable commit and call `GET /api/health`; require HTTP 200.
+6. Configure the scheduler to `POST /api/jobs/run` with header
+ `x-autoblog-cron-secret: ` at least once per minute.
+7. From a clean browser, execute the Author → Reviewer → Editor → public-preview path.
+8. Verify anonymous mutation denial, demo reset isolation and public preview after reload.
+
+The scheduler accepts no job/workspace payload. Each invocation claims a bounded due batch; leases,
+idempotency keys and retry state live in libSQL.
+
+## Vercel notes
+
+The repository can be connected to Vercel, but AutoBlog does not enable or assume a paid plan. Use a
+remote database; do not rely on Vercel's ephemeral filesystem. Configure a scheduler only when the
+existing account tier supports the required cadence, otherwise use an already-authorized external
+scheduler. Provider/account creation is outside this repository.
+
+## Verification and rollback
+
+Record the commit SHA, deployment URL, migration result, health request ID and clean-session E2E
+result in release notes. For any failure, stop the scheduler and follow
+[rollback-recovery.md](./rollback-recovery.md).
diff --git a/docs/editorial-workflow.md b/docs/editorial-workflow.md
new file mode 100644
index 0000000..19f8c8a
--- /dev/null
+++ b/docs/editorial-workflow.md
@@ -0,0 +1,44 @@
+# Editorial workflow and publication
+
+## State machine
+
+| Command | From | To | Authorized roles | Preconditions |
+| --- | --- | --- | --- | --- |
+| Submit | Draft, Changes Requested | In Review | Owner, Admin, Editor, owning Author | non-empty immutable draft revision |
+| Request changes | In Review | Changes Requested | Owner, Admin, Editor, Reviewer | current expected version |
+| Approve | In Review | Approved | Owner, Admin, Editor, Reviewer | current expected version |
+| Schedule | Approved | Scheduled | Owner, Admin, Editor | future ISO timestamp and idempotency key |
+| Publish | Approved, Scheduled | Published | Owner, Admin, Editor | idempotency key and current draft revision |
+| Archive | any non-archived state | Archived | Owner, Admin, Editor | current expected version |
+
+Every transition is checked in the application service and again against state/version in the
+repository transaction. Illegal transitions return `ILLEGAL_TRANSITION`; stale commands return
+`VERSION_CONFLICT`. Audit rows record actor, request ID, source/destination and revision pointer.
+
+Content is locked during review, approval and scheduling. Editing or restoring a Published post
+opens a new Draft revision but does not change `published_revision_id`. Public preview therefore
+continues to render the reviewed artifact until another explicit publication.
+
+## Revision behavior
+
+- Autosave is debounced by 850 ms and serializes in-flight writes.
+- Each successful content save appends a row; database triggers reject revision updates.
+- History is workspace scoped and newest-first. Compare never mutates state.
+- Restore copies an old revision into a new row with `restored_from_revision_id`; the source remains
+ byte-for-byte unchanged.
+- The post version is a concurrency token. Workflow transitions can create intentional gaps between
+ revision version numbers.
+
+## Scheduled worker
+
+Scheduling inserts the publication and `publish` job atomically. The job payload pins post,
+publication and revision IDs. `bun run jobs:run` claims at most 50 due jobs; the protected
+`POST /api/jobs/run` adapter provides the same operation for an authorized deployment trigger.
+
+Claims use a 30-second conditional lease. Failures retain a stable error code, apply bounded
+exponential delay and stop after three attempts. Completion first checks publication status, making
+lease recovery and repeated execution no-ops. Archiving a scheduled post cancels only its matching
+publication/job.
+
+Deployment must invoke the worker at least once per minute. No external queue or paid scheduler is
+required by the application contract.
diff --git a/docs/engineering/baseline.md b/docs/engineering/baseline.md
new file mode 100644
index 0000000..2422310
--- /dev/null
+++ b/docs/engineering/baseline.md
@@ -0,0 +1,38 @@
+# Engineering baseline — 2026-07-22
+
+Measurements were taken on commit `485f037` using Bun 1.3.12 on the dedicated
+`feat/rfc-editorial-cms` branch. Counts below are command output, not estimates.
+
+| Gate | Command | Baseline result |
+| --- | --- | --- |
+| Frozen install | `bun install --frozen-lockfile` | Failed: lockfile would change |
+| Typecheck | `bunx tsc --noEmit --pretty false` | Failed: 42 errors |
+| Source lint | `bunx eslint . --ignore-pattern .next` | Failed: 93 errors, 39 warnings |
+| Production build | `bun run build` | Passed only while skipping type and lint validation |
+| Runtime audit | `bun audit --production` | Failed: 48 findings; 2 critical, 22 high |
+| Tests | package scripts and repository search | No automated test command or test suite |
+| CI | `.github/workflows` search | No workflow |
+
+## Security containment finding
+
+`.env.local` is tracked. Its current values are empty, but commit `8f83cec` contains non-empty
+values for MongoDB, Cloudinary and Gemini variables. Values were not printed or copied. Because
+shared history will not be rewritten, the affected provider credentials must be rotated or
+revoked by their account owner before release. Current-tree removal and automated secret scanning
+are Phase 0 gates.
+
+## Product-path evidence
+
+- Visible post flows call `utils/api-fetch.ts`, a process-local simulator.
+- Separate Mongoose, Cloudinary and Gemini routes are directly reachable without a real session.
+- `actions/check-user.ts` accepts any two non-empty strings.
+- `next.config.ts` permits every HTTPS image host, accepts 20 MB server-action bodies, applies
+ global no-cache headers, and suppresses type and lint build validation.
+- README and FEATURES claim versioning, conflict resolution, WCAG conformance and durable autosave
+ without corresponding executable paths or tests.
+
+## Reproduction integrity
+
+The baseline intentionally records the current toolchain result, which differs from older RFC
+counts after advisory and lint-rule updates. Improved metrics will only be added after the named
+commands pass in a clean worktree.
diff --git a/docs/engineering/rfc-closure-ledger.md b/docs/engineering/rfc-closure-ledger.md
new file mode 100644
index 0000000..61c5dec
--- /dev/null
+++ b/docs/engineering/rfc-closure-ledger.md
@@ -0,0 +1,34 @@
+# RFC closure ledger
+
+Status values are `Open`, `In progress`, `Verified`, or `Externally blocked`. A row becomes
+`Verified` only when its user path and named automated evidence both pass.
+
+| ID | Required implementation | Primary evidence | Status |
+| --- | --- | --- | --- |
+| P-01 | One durable application/repository path | integration persistence + restart tests | Verified |
+| P-02 | Strict type/lint/build gates | independent CI jobs | Verified |
+| P-03 | Reproducible, patched dependencies | frozen install + runtime audit | Verified |
+| P-04 | Real revocable sessions on every protected adapter | anonymous/session route tests | Verified |
+| P-05 | Five-role application policy | complete positive/negative matrix tests | Verified |
+| P-06 | Canonical validated workspace domain schemas | schema/validation tests | Verified |
+| P-07 | Explicit workflow and durable publisher | state machine + duplicate/retry job tests + flagship E2E | Verified |
+| P-08 | Immutable revisions, compare and restore | append-only integration + history/restore E2E | Verified |
+| P-09 | Debounced conditional autosave and conflict UI | concurrent integration + stale-writer E2E | Verified |
+| P-10 | Bounded verified media with safe replacement | MIME/size/dimension/isolation + compensation/cleanup + E2E | Verified |
+| P-11 | Authorized, metered, bounded AI command | quota/rate/timeout/anonymous + explicit-Apply E2E | Verified |
+| P-12 | Explicit mock/configured adapter contract | shared mock/Gemini structured-output tests | Verified |
+| P-13 | Domain ownership; obsolete paths removed | modular tree + type/lint + legacy asset deletion | Verified |
+| P-14 | Tested accessibility targets | four-surface axe + keyboard/focus/reduced-motion + visual baselines | Verified |
+| P-15 | Semantic caching and measured budgets | production LCP/INP/CLS/JS budgets + six Lighthouse reports | Verified |
+| P-16 | Stable non-leaking public errors | API contract tests | Verified |
+| P-17 | Guided bounded recruiter demo | dynamic checklist + isolated idempotent reset integration/E2E | Verified |
+| P-18 | CI, truthful docs, screenshots and release | versioned visuals/docs + PR #3; release/public verification externally blocked | Externally blocked |
+
+## Release-only external dependencies
+
+- Rotate/revoke the historical MongoDB, Cloudinary and Gemini credentials named in the baseline.
+- Configure the existing deployment with a remote libSQL URL/token and a unique Better Auth secret.
+- Disable Vercel preview/production deployment protection for the intended public URL, or supply an
+ approved public domain; the current candidate redirects anonymous reviewers to Vercel login.
+- GitHub and deployment mutations proceed only after local gates pass and authenticated ownership
+ is verified as `PatrickDev-it`.
diff --git a/docs/known-limitations.md b/docs/known-limitations.md
new file mode 100644
index 0000000..0610716
--- /dev/null
+++ b/docs/known-limitations.md
@@ -0,0 +1,19 @@
+# Known limitations
+
+- AutoBlog surfaces concurrent-write conflicts but does not merge text or provide real-time co-editing.
+- The default media provider stores bounded objects in libSQL; it is not a high-volume CDN/object-store
+ design and does not perform malware scanning or image conversion.
+- Gemini behavior is contract-tested with a controlled provider boundary; no live output quality,
+ token, cost or cancellation guarantee is claimed.
+- Scheduled publication requires an external one-minute trigger. No paid scheduler is provisioned.
+- Accessibility evidence covers automated axe, keyboard/focus/reduced-motion and scoped visual review;
+ it is not a blanket WCAG certification or full screen-reader/browser matrix.
+- Performance values are Chromium lab measurements on the recorded machine, not field telemetry.
+- Visual regression is pinned to Windows Chromium to control font rasterization variance.
+- Demo authentication uses bounded seeded identities selected by role; production user invitation,
+ password recovery, MFA and identity-provider federation are not implemented.
+- Membership management is policy-defined but has no recruiter-demo UI.
+- Analytics and engagement metrics are intentionally absent rather than simulated.
+- Public v2 deployment remains blocked until historical provider credentials are rotated, the
+ deployment owner configures durable remote libSQL plus the scheduler, and Vercel deployment
+ protection is removed from the intended recruiter-facing URL.
diff --git a/docs/media-security.md b/docs/media-security.md
new file mode 100644
index 0000000..c5d3869
--- /dev/null
+++ b/docs/media-security.md
@@ -0,0 +1,34 @@
+# Media security policy
+
+## Accepted input
+
+- Authenticated bounded multipart only; remote URL ingestion is not implemented and the remote-host
+ allowlist is empty, eliminating this SSRF path.
+- PNG, JPEG and WebP only. The claimed MIME must match the decoded `sharp` format.
+- Default maximum: 5 MiB; configuration cannot exceed 10 MiB.
+- Maximum width/height: 4096 pixels; maximum decoded area: 16 megapixels.
+- Filenames are display metadata only. Path components and unsafe characters are removed; provider
+ keys are generated from authenticated workspace identity plus a UUID.
+
+The route reads the request stream with an explicit cap before invoking multipart parsing. It returns
+stable validation errors without decoder/provider details. Authenticated reads send `nosniff` and
+private cache headers.
+
+## Storage and replacement
+
+`MediaProvider` is the object boundary. The default production adapter stores opaque bounded objects
+in durable libSQL; metadata remains in `media_assets`. This keeps local, CI and remote libSQL behavior
+identical without private infrastructure.
+
+Upload order is verify → provider put → metadata finalization. Replacement marks the old object
+`replaced` and activates the new object in one transaction protected by a partial unique index. If
+provider put or finalization fails, the prior active row is unchanged. Orphan and replaced objects are
+deleted by leased, idempotent `media_cleanup` jobs with three attempts.
+
+Authors may upload/replace only for their own posts. Owner/Admin/Editor may delete. Every operation is
+workspace-filtered and audited; hiding a control is not relied upon for enforcement.
+
+## Operational limitation
+
+Database object storage is deliberate for a bounded portfolio demo. A high-volume deployment should
+supersede RFC 003 with signed direct uploads to an explicitly approved object store/CDN.
diff --git a/docs/migrations.md b/docs/migrations.md
new file mode 100644
index 0000000..d9fbd7f
--- /dev/null
+++ b/docs/migrations.md
@@ -0,0 +1,33 @@
+# Migration and seed operations
+
+Numbered SQL files under `drizzle/` are forward-only and checksum verified. Applied SQL must never be
+edited; add a new migration instead.
+
+## Empty database validation
+
+```bash
+DATABASE_URL=file:./data/empty-verification.db bun run db:migrate
+DATABASE_URL=file:./data/empty-verification.db DEMO_ENABLED=true bun run db:seed
+```
+
+`bun run db:setup` applies pending migrations and seeds only when `DEMO_ENABLED=true`.
+`bun run db:setup -- --reset-demo` is intended for isolated local/E2E setup; product reset uses the
+authenticated Owner command.
+
+## Current sequence
+
+| Migration | Change | Compatibility |
+| --- | --- | --- |
+| `0000_editorial_core.sql` | workspace identity, posts, immutable revisions, publications, jobs and audit | foundational |
+| `0001_auth_rate_limit.sql` | durable authentication rate limiting | additive |
+| `0002_media_ai_governance.sql` | verified media object boundary, active-asset constraint and AI quota windows | replaces legacy media blob table |
+| `0003_demo_reset_idempotency.sql` | immutable reset operation keys outside workspace cascade | additive |
+
+Integration tests create an empty isolated database, apply the complete sequence, verify checksums
+and exercise seed/reset behavior. Remote migration uses the same client and SQL path.
+
+## Production discipline
+
+Back up first, migrate once, deploy compatible application code, then start the scheduler. A checksum
+mismatch is an incident: restore the expected migration file from source control; do not alter the
+database ledger manually.
diff --git a/docs/performance.md b/docs/performance.md
new file mode 100644
index 0000000..860c0bf
--- /dev/null
+++ b/docs/performance.md
@@ -0,0 +1,49 @@
+# Performance budgets and measurements
+
+Budgets were accepted before measurement in RFC 004 and execute against `next start`, not the HMR
+development server.
+
+| Route / metric | Budget | 2026-07-22 local Chromium | Status |
+| --- | ---: | ---: | --- |
+| Marketing LCP | ≤ 2,500 ms | 188 ms | Pass |
+| Marketing CLS | ≤ 0.10 | 0.000 | Pass |
+| Marketing initial JS transfer | ≤ 180 KiB | 138.6 KiB (141,959 B) | Pass |
+| Workspace LCP | ≤ 2,500 ms | 300 ms | Pass |
+| Workspace CLS | ≤ 0.10 | 0.000 | Pass |
+| Workspace INP event-duration upper bound | ≤ 200 ms | 16 ms | Pass |
+| Workspace history response/paint proxy | ≤ 500 ms | 146 ms | Pass |
+| Workspace cold initial JS transfer | ≤ 320 KiB | 216.2 KiB (221,420 B) | Pass |
+
+Chromium recorded a 16 ms Event Timing duration for the measured workspace interaction. This is a
+bounded lab proxy rather than a field INP claim. The 146 ms workflow measurement includes fetch,
+rendering and visibility of revision history.
+
+Run `bun run build` followed by `bun run test:performance`. CI fails on any declared regression.
+Results are lab targets and do not claim universal field performance across devices/networks.
+The command writes the representative public/editor bundle and Web Vital measurements to
+`.performance/bundle-report.json`; CI retains it with the Lighthouse output for seven days.
+
+## Lighthouse reports
+
+`bun run test:lighthouse` performs three desktop Lighthouse runs on marketing and sign-in under the
+same production server. Median-run assertions require performance ≥0.80, accessibility ≥0.95,
+best-practices ≥0.90, SEO ≥0.90, LCP ≤2,500 ms and CLS ≤0.10. Reports remain local in the ignored
+`.lighthouse/` directory; no hosted report service receives route output. Direct interaction and
+JavaScript budgets above remain mandatory because Lighthouse does not replace them.
+
+Measured medians on 2026-07-22:
+
+| Route | Performance | Accessibility | Best practices | SEO | LCP | CLS |
+| --- | ---: | ---: | ---: | ---: | ---: | ---: |
+| Marketing | 0.92 | 1.00 | 1.00 | 1.00 | 1,718 ms | 0 |
+| Sign-in | 0.91 | 1.00 | 1.00 | 1.00 | 1,709 ms | 0 |
+
+## Cache semantics
+
+- Marketing is statically generated.
+- Authenticated workspace/API data is dynamic; JSON application responses send `private, no-store`.
+- Media bytes are authenticated and privately cached for five minutes with `nosniff`.
+- Public preview pins an immutable revision and declares 60-second revalidation.
+- Health/readiness is `no-store`.
+
+No global cache disablement is configured.
diff --git a/docs/releases/v2.0.0.md b/docs/releases/v2.0.0.md
new file mode 100644
index 0000000..94926af
--- /dev/null
+++ b/docs/releases/v2.0.0.md
@@ -0,0 +1,45 @@
+# AutoBlog CMS 2.0.0 release notes
+
+Status: repository candidate on [draft PR #3](https://github.com/PatrickDev-it/AutoBlog-CMS/pull/3);
+public release is blocked by the external actions below.
+
+## Product
+
+- Replaced the process-local simulator with one durable workspace-scoped application path.
+- Added database-backed demo sessions and Owner/Admin/Editor/Author/Reviewer authorization.
+- Added immutable revisions, compare, restore-as-new, debounced conditional autosave and conflict UI.
+- Added review/approval, idempotent scheduling, immutable publication and public preview.
+- Added bounded verified media with failure compensation and explicit metered AI suggestions.
+- Added recruiter landing, seeded guided workflow, bounded reset and responsive product states.
+
+## Engineering evidence
+
+- Bun frozen install; strict TypeScript and zero-warning ESLint; production build without bypasses.
+- 19 unit, 25 integration, 10 E2E, 6 accessibility, 5 visual, 2 direct performance tests and six
+ Lighthouse runs locally pass.
+- Runtime dependency audit reports zero vulnerabilities; current-tree secret scan passes.
+- Migrations `0000`–`0003` reproduce the database from empty and reject modified checksums.
+- CI separates install, type, lint, unit, integration, E2E, accessibility, visual, performance, build,
+ runtime audit and full-history secret scanning.
+- Protected PR run `29909234038` passes all eleven independent jobs, including the pinned TruffleHog
+ history-range scan. That automated zero-finding result does not prove revocation of provider values
+ identified during the manual baseline review; owner-confirmed rotation remains a release gate.
+
+## Migration and rollback
+
+This is a breaking replacement of the legacy simulator/MongoDB/Cloudinary paths. Back up the target
+database, apply all migrations and deploy 2.0 together. There is no supported data migration from the
+legacy demo simulator because it was not durable. See [migrations](../migrations.md) and
+[rollback/recovery](../rollback-recovery.md).
+
+## External release blockers
+
+1. Provider owners must rotate/revoke historical MongoDB, Cloudinary and Gemini values from commit
+ `8f83cec` and record completion without publishing values.
+2. Deployment owner must configure remote libSQL, unique auth/cron secrets and a one-minute scheduler.
+3. Deployment owner must expose an approved public URL. The candidate Vercel preview is protected and
+ redirects an anonymous clean session to Vercel login.
+4. The release manager must then verify CI, merge the pull request, deploy the merged SHA, execute the
+ clean-browser workflow and create the `v2.0.0` tag/release.
+
+No old production URL is presented as AutoBlog 2.0 evidence.
diff --git a/docs/rollback-recovery.md b/docs/rollback-recovery.md
new file mode 100644
index 0000000..8060194
--- /dev/null
+++ b/docs/rollback-recovery.md
@@ -0,0 +1,32 @@
+# Rollback and recovery
+
+## Application rollback
+
+1. Stop scheduler invocations to prevent new state transitions during triage.
+2. Preserve diagnostic logs by request ID and take a database snapshot.
+3. Roll back to the previous immutable deployment only if it supports every applied migration.
+4. Run `GET /api/health`, authenticate and verify workspace isolation before resuming jobs.
+
+Migrations are forward-only. Do not delete migration ledger rows or rewrite applied SQL. If the prior
+application cannot read the new schema, restore the pre-migration database snapshot together with the
+prior application commit.
+
+## Data recovery
+
+- Editorial recovery uses revision history: restore creates a new revision and preserves evidence.
+- Published content remains pinned while later drafts are repaired.
+- Failed scheduled jobs retain attempt/error state and resume after the lease expires.
+- Media cleanup is idempotent; the active asset is never a cleanup candidate.
+- Demo reset affects only the workspace with `is_demo=true`; it is not a production recovery tool.
+
+## Provider incidents
+
+Set `AI_MODE=mock` to stop Gemini calls without disabling editing. A media-provider failure rejects the
+new upload and retains the active object. Rotate compromised `CRON_SECRET` and provider/database
+tokens in their owner consoles, then redeploy all consumers atomically.
+
+## Recovery verification
+
+Run integration tests against a restored copy, then exercise login, stale-write conflict, publication,
+public preview and one no-op duplicate job. Record recovery point, recovery time and any permanently
+lost data; the project does not publish unmeasured RPO/RTO claims.
diff --git a/docs/security-threat-model.md b/docs/security-threat-model.md
new file mode 100644
index 0000000..5b2d526
--- /dev/null
+++ b/docs/security-threat-model.md
@@ -0,0 +1,56 @@
+# Security and threat model
+
+## Scope and assets
+
+This model covers the Next.js application, Better Auth sessions, workspace content, immutable
+revisions/publications, media objects, AI usage metadata, libSQL and the scheduled worker. Primary
+assets are session integrity, tenant isolation, unpublished content, published-revision integrity,
+provider credentials, media availability and AI quota.
+
+## Trust boundaries
+
+```mermaid
+flowchart LR
+ Internet --> Next[Next.js adapters]
+ Next --> Auth[Session and Origin boundary]
+ Auth --> Policy[Workspace RBAC]
+ Policy --> Domain[Application services]
+ Domain --> DB[(libSQL)]
+ Domain --> Gemini[Optional Gemini provider]
+ Scheduler --> Cron[Distinct scheduler secret]
+ Cron --> Domain
+```
+
+Browser payload identity is untrusted. Workspace and actor come only from the validated session.
+Gemini and the deployment scheduler are external principals. Local database object storage remains
+inside the application persistence boundary.
+
+## Threats, controls and evidence
+
+| Threat | Control | Verification | Residual risk |
+| --- | --- | --- | --- |
+| Session theft/fixation | HTTP-only SameSite=Lax cookie, 12-hour expiry, DB revocation, HTTPS Secure cookie | login/logout/revocation integration | compromised endpoint/browser remains outside application control |
+| CSRF | trusted Origin check on custom mutations plus SameSite cookie | invalid-origin integration/E2E | upstream proxy must preserve canonical scheme/host |
+| Cross-workspace access | session-derived membership, policy first, workspace-filtered repositories and FKs | negative role matrix and isolation integration | operator SQL access is privileged |
+| Privilege escalation | five-role command matrix enforced in services | every role/command pair plus route denials | membership administration needs operational review |
+| Lost update | expected post version and HTTP 409 | concurrent-writer integration and conflict E2E | no automatic text merge |
+| Revision/publication tampering | insert-only triggers and pinned published revision | trigger, restore and later-edit tests | database owner can alter infrastructure directly |
+| Upload abuse/decompression | stream byte cap, decoded type/dimension/pixel checks, no remote URL ingestion | forged MIME/size/dimension tests | malware scanning is not included |
+| Media replacement loss | verify/store before atomic activation, leased compensation job | provider/finalization injection tests | prolonged worker outage delays orphan cleanup |
+| AI cost/data abuse | RBAC, persistent rate/quota reservation, bounded fields/output, timeout | quota/rate/timeout/anonymous tests | SDK abort may not cancel provider billing |
+| Scheduler forgery/replay | distinct constant-time secret and idempotent leased jobs | unauthorized and duplicate job tests | secret rotation requires coordinated deployment |
+| Information disclosure | stable error taxonomy, request ID, structured diagnostic log | API contract tests | deployment logs remain operator-controlled |
+| Dependency/secret compromise | frozen lockfile, pinned Actions, production audit and secret scans | independent CI jobs | historic provider values require owner rotation |
+
+## Data minimization
+
+AI usage rows retain provider/model, latency and actual count metadata; they do not retain prompts,
+drafts, suggestions, raw responses or API keys. Audit events store actor/action/resource metadata, not
+editorial bodies. Public preview exposes only the pinned published revision.
+
+## Security release gate
+
+Commit `8f83cec` contains historical MongoDB, Cloudinary and Gemini values. The current tree neither
+uses nor reproduces them, but repository history is still a distribution channel. Provider account
+owners must rotate/revoke all affected credentials and record completion before the v2 release. The
+history scan must not be suppressed to manufacture a green build.
diff --git a/drizzle.config.ts b/drizzle.config.ts
new file mode 100644
index 0000000..dd192b5
--- /dev/null
+++ b/drizzle.config.ts
@@ -0,0 +1,9 @@
+import { defineConfig } from 'drizzle-kit';
+
+export default defineConfig({
+ dialect: 'sqlite',
+ schema: './src/platform/db/schema.ts',
+ out: './drizzle/generated',
+ dbCredentials: { url: process.env.DATABASE_URL ?? 'file:./data/autoblog.db' },
+ strict: true,
+});
diff --git a/drizzle/0000_editorial_core.sql b/drizzle/0000_editorial_core.sql
new file mode 100644
index 0000000..5f21a18
--- /dev/null
+++ b/drizzle/0000_editorial_core.sql
@@ -0,0 +1,215 @@
+PRAGMA foreign_keys = ON;
+
+CREATE TABLE "user" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "name" TEXT NOT NULL,
+ "email" TEXT NOT NULL UNIQUE,
+ "email_verified" INTEGER NOT NULL DEFAULT 0,
+ "image" TEXT,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL
+);
+
+CREATE TABLE "session" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "expires_at" INTEGER NOT NULL,
+ "token" TEXT NOT NULL UNIQUE,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL,
+ "ip_address" TEXT,
+ "user_agent" TEXT,
+ "user_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE CASCADE
+);
+CREATE INDEX "session_user_idx" ON "session" ("user_id");
+
+CREATE TABLE "account" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "account_id" TEXT NOT NULL,
+ "provider_id" TEXT NOT NULL,
+ "user_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
+ "access_token" TEXT,
+ "refresh_token" TEXT,
+ "id_token" TEXT,
+ "access_token_expires_at" INTEGER,
+ "refresh_token_expires_at" INTEGER,
+ "scope" TEXT,
+ "password" TEXT,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL,
+ UNIQUE ("provider_id", "account_id")
+);
+CREATE INDEX "account_user_idx" ON "account" ("user_id");
+
+CREATE TABLE "verification" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "identifier" TEXT NOT NULL,
+ "value" TEXT NOT NULL,
+ "expires_at" INTEGER NOT NULL,
+ "created_at" INTEGER,
+ "updated_at" INTEGER
+);
+CREATE INDEX "verification_identifier_idx" ON "verification" ("identifier");
+
+CREATE TABLE "workspaces" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "slug" TEXT NOT NULL UNIQUE,
+ "name" TEXT NOT NULL,
+ "is_demo" INTEGER NOT NULL DEFAULT 0,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL
+);
+
+CREATE TABLE "memberships" (
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "user_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
+ "role" TEXT NOT NULL CHECK ("role" IN ('Owner','Admin','Editor','Author','Reviewer')),
+ "created_at" INTEGER NOT NULL,
+ PRIMARY KEY ("workspace_id", "user_id")
+);
+CREATE INDEX "membership_user_idx" ON "memberships" ("user_id");
+
+CREATE TABLE "posts" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "slug" TEXT NOT NULL,
+ "title" TEXT NOT NULL,
+ "excerpt" TEXT NOT NULL DEFAULT '',
+ "state" TEXT NOT NULL DEFAULT 'Draft' CHECK ("state" IN ('Draft','InReview','ChangesRequested','Approved','Scheduled','Published','Archived')),
+ "version" INTEGER NOT NULL DEFAULT 1 CHECK ("version" > 0),
+ "author_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE RESTRICT,
+ "draft_revision_id" TEXT REFERENCES "revisions"("id") ON DELETE SET NULL,
+ "published_revision_id" TEXT REFERENCES "revisions"("id") ON DELETE SET NULL,
+ "scheduled_for" INTEGER,
+ "submitted_at" INTEGER,
+ "approved_at" INTEGER,
+ "published_at" INTEGER,
+ "archived_at" INTEGER,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL,
+ UNIQUE ("workspace_id", "slug"),
+ UNIQUE ("workspace_id", "id")
+);
+CREATE INDEX "post_workspace_state_idx" ON "posts" ("workspace_id", "state");
+
+CREATE TABLE "revisions" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "post_id" TEXT NOT NULL,
+ "version" INTEGER NOT NULL,
+ "title" TEXT NOT NULL,
+ "excerpt" TEXT NOT NULL DEFAULT '',
+ "content" TEXT NOT NULL,
+ "author_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE RESTRICT,
+ "restored_from_revision_id" TEXT,
+ "created_at" INTEGER NOT NULL,
+ UNIQUE ("post_id", "version"),
+ UNIQUE ("workspace_id", "id"),
+ FOREIGN KEY ("workspace_id", "post_id") REFERENCES "posts"("workspace_id", "id") ON DELETE CASCADE
+);
+CREATE INDEX "revision_workspace_post_idx" ON "revisions" ("workspace_id", "post_id");
+
+CREATE TABLE "publications" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "post_id" TEXT NOT NULL,
+ "revision_id" TEXT NOT NULL REFERENCES "revisions"("id") ON DELETE RESTRICT,
+ "status" TEXT NOT NULL CHECK ("status" IN ('scheduled','published','cancelled')),
+ "scheduled_for" INTEGER,
+ "published_at" INTEGER,
+ "idempotency_key" TEXT NOT NULL UNIQUE,
+ "created_by" TEXT NOT NULL REFERENCES "user"("id") ON DELETE RESTRICT,
+ "created_at" INTEGER NOT NULL,
+ FOREIGN KEY ("workspace_id", "post_id") REFERENCES "posts"("workspace_id", "id") ON DELETE CASCADE
+);
+CREATE INDEX "publication_due_idx" ON "publications" ("status", "scheduled_for");
+
+CREATE TABLE "media_assets" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "post_id" TEXT,
+ "status" TEXT NOT NULL CHECK ("status" IN ('pending','active','replaced','deleted','cleanup_pending')),
+ "storage_key" TEXT NOT NULL UNIQUE,
+ "file_name" TEXT NOT NULL,
+ "mime_type" TEXT NOT NULL,
+ "byte_size" INTEGER NOT NULL CHECK ("byte_size" > 0),
+ "width" INTEGER NOT NULL,
+ "height" INTEGER NOT NULL,
+ "checksum" TEXT NOT NULL,
+ "alt_text" TEXT NOT NULL DEFAULT '',
+ "created_by" TEXT NOT NULL REFERENCES "user"("id") ON DELETE RESTRICT,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL,
+ FOREIGN KEY ("workspace_id", "post_id") REFERENCES "posts"("workspace_id", "id") ON DELETE SET NULL
+);
+CREATE INDEX "media_workspace_post_idx" ON "media_assets" ("workspace_id", "post_id");
+
+CREATE TABLE "media_blobs" (
+ "asset_id" TEXT PRIMARY KEY NOT NULL REFERENCES "media_assets"("id") ON DELETE CASCADE,
+ "data" BLOB NOT NULL
+);
+
+CREATE TABLE "audit_events" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "actor_id" TEXT REFERENCES "user"("id") ON DELETE SET NULL,
+ "action" TEXT NOT NULL,
+ "target_type" TEXT NOT NULL,
+ "target_id" TEXT,
+ "request_id" TEXT NOT NULL,
+ "metadata" TEXT NOT NULL DEFAULT '{}',
+ "created_at" INTEGER NOT NULL
+);
+CREATE INDEX "audit_workspace_created_idx" ON "audit_events" ("workspace_id", "created_at");
+
+CREATE TABLE "ai_usage" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "user_id" TEXT NOT NULL REFERENCES "user"("id") ON DELETE CASCADE,
+ "mode" TEXT NOT NULL CHECK ("mode" IN ('mock','gemini')),
+ "provider" TEXT NOT NULL,
+ "model" TEXT NOT NULL,
+ "latency_ms" INTEGER NOT NULL,
+ "input_characters" INTEGER NOT NULL,
+ "output_characters" INTEGER NOT NULL,
+ "input_tokens" INTEGER,
+ "output_tokens" INTEGER,
+ "created_at" INTEGER NOT NULL
+);
+CREATE INDEX "ai_usage_workspace_created_idx" ON "ai_usage" ("workspace_id", "created_at");
+
+CREATE TABLE "jobs" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "type" TEXT NOT NULL CHECK ("type" IN ('publish','media_cleanup')),
+ "payload" TEXT NOT NULL,
+ "status" TEXT NOT NULL DEFAULT 'pending' CHECK ("status" IN ('pending','leased','completed','failed')),
+ "attempts" INTEGER NOT NULL DEFAULT 0,
+ "run_at" INTEGER NOT NULL,
+ "lease_until" INTEGER,
+ "last_error_code" TEXT,
+ "idempotency_key" TEXT NOT NULL UNIQUE,
+ "created_at" INTEGER NOT NULL,
+ "updated_at" INTEGER NOT NULL
+);
+CREATE INDEX "job_claim_idx" ON "jobs" ("status", "run_at", "lease_until");
+
+CREATE TABLE "rate_limits" (
+ "key" TEXT NOT NULL,
+ "window_started_at" INTEGER NOT NULL,
+ "count" INTEGER NOT NULL DEFAULT 0,
+ "expires_at" INTEGER NOT NULL,
+ PRIMARY KEY ("key", "window_started_at")
+);
+CREATE INDEX "rate_limit_expiry_idx" ON "rate_limits" ("expires_at");
+
+CREATE TRIGGER "revision_immutable_update"
+BEFORE UPDATE ON "revisions"
+BEGIN
+ SELECT RAISE(ABORT, 'IMMUTABLE_REVISION');
+END;
+
+CREATE TRIGGER "audit_event_immutable_update"
+BEFORE UPDATE ON "audit_events"
+BEGIN
+ SELECT RAISE(ABORT, 'IMMUTABLE_AUDIT_EVENT');
+END;
diff --git a/drizzle/0001_auth_rate_limit.sql b/drizzle/0001_auth_rate_limit.sql
new file mode 100644
index 0000000..2dcf1fe
--- /dev/null
+++ b/drizzle/0001_auth_rate_limit.sql
@@ -0,0 +1,6 @@
+CREATE TABLE "rateLimit" (
+ "id" TEXT PRIMARY KEY NOT NULL,
+ "key" TEXT NOT NULL UNIQUE,
+ "count" INTEGER NOT NULL,
+ "lastRequest" INTEGER NOT NULL
+);
diff --git a/drizzle/0002_media_ai_governance.sql b/drizzle/0002_media_ai_governance.sql
new file mode 100644
index 0000000..016ae69
--- /dev/null
+++ b/drizzle/0002_media_ai_governance.sql
@@ -0,0 +1,22 @@
+CREATE TABLE "media_objects" (
+ "storage_key" TEXT PRIMARY KEY NOT NULL,
+ "data" BLOB NOT NULL,
+ "created_at" INTEGER NOT NULL
+);
+
+DROP TABLE "media_blobs";
+
+ALTER TABLE "media_assets" ADD COLUMN "replaces_asset_id" TEXT;
+
+CREATE UNIQUE INDEX "media_one_active_per_post"
+ON "media_assets" ("workspace_id", "post_id")
+WHERE "status" = 'active' AND "post_id" IS NOT NULL;
+
+CREATE TABLE "ai_quota_windows" (
+ "workspace_id" TEXT NOT NULL REFERENCES "workspaces"("id") ON DELETE CASCADE,
+ "window_started_at" INTEGER NOT NULL,
+ "reserved_characters" INTEGER NOT NULL DEFAULT 0 CHECK ("reserved_characters" >= 0),
+ "used_characters" INTEGER NOT NULL DEFAULT 0 CHECK ("used_characters" >= 0),
+ "updated_at" INTEGER NOT NULL,
+ PRIMARY KEY ("workspace_id", "window_started_at")
+);
diff --git a/drizzle/0003_demo_reset_idempotency.sql b/drizzle/0003_demo_reset_idempotency.sql
new file mode 100644
index 0000000..e995fd7
--- /dev/null
+++ b/drizzle/0003_demo_reset_idempotency.sql
@@ -0,0 +1,14 @@
+CREATE TABLE "idempotency_records" (
+ "workspace_id" TEXT NOT NULL,
+ "operation" TEXT NOT NULL,
+ "key" TEXT NOT NULL,
+ "result" TEXT NOT NULL,
+ "created_at" INTEGER NOT NULL,
+ PRIMARY KEY ("workspace_id", "operation", "key")
+);
+
+CREATE TRIGGER "idempotency_record_immutable_update"
+BEFORE UPDATE ON "idempotency_records"
+BEGIN
+ SELECT RAISE(ABORT, 'IMMUTABLE_IDEMPOTENCY_RECORD');
+END;
diff --git a/eslint.config.mjs b/eslint.config.mjs
index ea598c1..9a2a789 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,22 +1,24 @@
-import { dirname } from "path";
-import { fileURLToPath } from "url";
-import { FlatCompat } from "@eslint/eslintrc";
+import { defineConfig, globalIgnores } from 'eslint/config';
+import nextVitals from 'eslint-config-next/core-web-vitals';
+import nextTypeScript from 'eslint-config-next/typescript';
-const __filename = fileURLToPath(import.meta.url);
-const __dirname = dirname(__filename);
-
-const compat = new FlatCompat({
- baseDirectory: __dirname,
-});
-
-const eslintConfig = [
- ...compat.extends("next/core-web-vitals", "next/typescript"),
+export default defineConfig([
+ ...nextVitals,
+ ...nextTypeScript,
+ globalIgnores([
+ '.next/**',
+ 'coverage/**',
+ 'data/**',
+ 'playwright-report/**',
+ 'test-results/**',
+ '.sinapsi/**',
+ ]),
{
+ files: ['**/*.{ts,tsx}'],
rules: {
- "@typescript-eslint/no-explicit-any": "off", // Disabilita il controllo su 'any'
- "@typescript-eslint/ban-ts-comment": "off",
+ '@typescript-eslint/consistent-type-imports': 'error',
+ '@typescript-eslint/no-explicit-any': 'error',
+ '@typescript-eslint/no-non-null-assertion': 'error',
},
},
-];
-
-export default eslintConfig;
+]);
diff --git a/global.d.ts b/global.d.ts
deleted file mode 100644
index a78ebf2..0000000
--- a/global.d.ts
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Connection, Mongoose } from 'mongoose';
-import { v2 as Cloudinary } from 'cloudinary';
-import type { Env } from './env';
-
-declare global {
- // process.env
- namespace NodeJS {
- interface ProcessEnv {
- MONGODB_URI: string;
- DBNAME: string;
- }
- }
-
- var db: (args?: {
- MONGODB_URI: string;
- DBNAME: string;
- newConnection?: boolean;
- }) => Promise;
- var mongoose: Mongoose;
- var connection: Connection;
-
- var cloudinary: typeof Cloudinary;
-}
-
-export {};
diff --git a/hooks/use-longPress.ts b/hooks/use-longPress.ts
deleted file mode 100644
index 32e479f..0000000
--- a/hooks/use-longPress.ts
+++ /dev/null
@@ -1,60 +0,0 @@
-import { useCallback, useRef, useState } from 'react';
-
-export default (
- onLongPress,
- { shouldPreventDefault = true, delay = 300 } = {},
- onClick = () => {}
-) => {
- const [longPressTriggered, setLongPressTriggered] = useState(false);
- const timeout = useRef(null);
- const target = useRef(null);
-
- const start = useCallback(
- event => {
- if (shouldPreventDefault && event.target) {
- event.target.addEventListener('touchend', preventDefault, {
- passive: false,
- });
- target.current = event.target;
- }
- timeout.current = setTimeout(() => {
- onLongPress(event);
- console.log('onLongPress => ', event);
- setLongPressTriggered(true);
- }, delay);
- },
- [onLongPress, delay, shouldPreventDefault]
- );
-
- const clear = useCallback(
- (event, shouldTriggerClick = true) => {
- timeout.current && clearTimeout(timeout.current);
- shouldTriggerClick && !longPressTriggered && onClick();
- setLongPressTriggered(false);
- if (shouldPreventDefault && target.current) {
- target.current.removeEventListener('touchend', preventDefault);
- }
- },
- [shouldPreventDefault, onClick, longPressTriggered]
- );
-
- return {
- onMouseDown: e => start(e),
- onTouchStart: e => start(e),
- onMouseUp: e => clear(e),
- onMouseLeave: e => clear(e, false),
- onTouchEnd: e => clear(e),
- };
-};
-
-const isTouchEvent = event => {
- return 'touches' in event;
-};
-
-const preventDefault = event => {
- if (!isTouchEvent(event)) return;
-
- if (event.touches.length < 2 && event.preventDefault) {
- event.preventDefault();
- }
-};
diff --git a/hooks/use-mobile.tsx b/hooks/use-mobile.tsx
deleted file mode 100644
index 2b0fe1d..0000000
--- a/hooks/use-mobile.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from "react"
-
-const MOBILE_BREAKPOINT = 768
-
-export function useIsMobile() {
- const [isMobile, setIsMobile] = React.useState(undefined)
-
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
- const onChange = () => {
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- }
- mql.addEventListener("change", onChange)
- setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
- return () => mql.removeEventListener("change", onChange)
- }, [])
-
- return !!isMobile
-}
diff --git a/hooks/use-tablet.tsx b/hooks/use-tablet.tsx
deleted file mode 100644
index a7db307..0000000
--- a/hooks/use-tablet.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import * as React from 'react';
-
-const TABLET_BREAKPOINT = 1024;
-
-export function useIsTablet() {
- const [isTablet, setIsTablet] = React.useState(undefined);
-
- React.useEffect(() => {
- const mql = window.matchMedia(`(max-width: ${TABLET_BREAKPOINT - 1}px)`);
- const onChange = () => {
- setIsTablet(window.innerWidth < TABLET_BREAKPOINT);
- };
- mql.addEventListener('change', onChange);
- setIsTablet(window.innerWidth < TABLET_BREAKPOINT);
- return () => mql.removeEventListener('change', onChange);
- }, []);
-
- return !!isTablet;
-}
diff --git a/hooks/use-theme.tsx b/hooks/use-theme.tsx
deleted file mode 100644
index 54000c4..0000000
--- a/hooks/use-theme.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-'use client';
-
-import switchTheme from '@/utils/switch-theme';
-import { createContext, useContext, useEffect, useState } from 'react';
-
-import type { Dispatch, PropsWithChildren, SetStateAction } from 'react';
-
-type Theme = 'dark' | 'light';
-type ThemeContext = [Theme, Dispatch>];
-
-const themeContext = createContext(['dark', () => {}]);
-
-export const ThemeProvider = ({ children, defaultValue }: PropsWithChildren & { defaultValue?: Theme }) => {
- const [theme, setTheme] = useState(defaultValue ?? 'dark');
-
- useEffect(() => {
- const resolvedTheme = switchTheme(false);
- setTheme(resolvedTheme);
- }, []);
-
- return {children} ;
-};
-
-export const useTheme = () => {
- const context = useContext(themeContext);
-
- if (!context) {
- throw new Error('useTheme must be used within a ThemeProvider.');
- }
- return context;
-};
diff --git a/hooks/use-toast.ts b/hooks/use-toast.ts
deleted file mode 100644
index 2e5e38c..0000000
--- a/hooks/use-toast.ts
+++ /dev/null
@@ -1,189 +0,0 @@
-"use client";
-
-// Inspired by react-hot-toast library
-import * as React from "react";
-
-import type { ToastActionElement, ToastProps } from "@/ui/toast";
-
-const TOAST_LIMIT = 1;
-const TOAST_REMOVE_DELAY = 1000000;
-
-type ToasterToast = ToastProps & {
- id: string;
- title?: React.ReactNode;
- description?: React.ReactNode;
- action?: ToastActionElement;
-};
-
-const actionTypes = {
- ADD_TOAST: "ADD_TOAST",
- UPDATE_TOAST: "UPDATE_TOAST",
- DISMISS_TOAST: "DISMISS_TOAST",
- REMOVE_TOAST: "REMOVE_TOAST",
-} as const;
-
-let count = 0;
-
-function genId() {
- count = (count + 1) % Number.MAX_SAFE_INTEGER;
- return count.toString();
-}
-
-type ActionType = typeof actionTypes;
-
-type Action =
- | {
- type: ActionType["ADD_TOAST"];
- toast: ToasterToast;
- }
- | {
- type: ActionType["UPDATE_TOAST"];
- toast: Partial;
- }
- | {
- type: ActionType["DISMISS_TOAST"];
- toastId?: ToasterToast["id"];
- }
- | {
- type: ActionType["REMOVE_TOAST"];
- toastId?: ToasterToast["id"];
- };
-
-interface State {
- toasts: ToasterToast[];
-}
-
-const toastTimeouts = new Map>();
-
-const addToRemoveQueue = (toastId: string) => {
- if (toastTimeouts.has(toastId)) {
- return;
- }
-
- const timeout = setTimeout(() => {
- toastTimeouts.delete(toastId);
- dispatch({
- type: "REMOVE_TOAST",
- toastId: toastId,
- });
- }, TOAST_REMOVE_DELAY);
-
- toastTimeouts.set(toastId, timeout);
-};
-
-export const reducer = (state: State, action: Action): State => {
- switch (action.type) {
- case "ADD_TOAST":
- return {
- ...state,
- toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
- };
-
- case "UPDATE_TOAST":
- return {
- ...state,
- toasts: state.toasts.map((t) => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
- };
-
- case "DISMISS_TOAST": {
- const { toastId } = action;
-
- // ! Side effects ! - This could be extracted into a dismissToast() action,
- // but I'll keep it here for simplicity
- if (toastId) {
- addToRemoveQueue(toastId);
- } else {
- state.toasts.forEach((toast) => {
- addToRemoveQueue(toast.id);
- });
- }
-
- return {
- ...state,
- toasts: state.toasts.map((t) =>
- t.id === toastId || toastId === undefined
- ? {
- ...t,
- open: false,
- }
- : t
- ),
- };
- }
- case "REMOVE_TOAST":
- if (action.toastId === undefined) {
- return {
- ...state,
- toasts: [],
- };
- }
- return {
- ...state,
- toasts: state.toasts.filter((t) => t.id !== action.toastId),
- };
- }
-};
-
-const listeners: Array<(state: State) => void> = [];
-
-let memoryState: State = { toasts: [] };
-
-function dispatch(action: Action) {
- memoryState = reducer(memoryState, action);
- listeners.forEach((listener) => {
- listener(memoryState);
- });
-}
-
-type Toast = Omit;
-
-function toast({ ...props }: Toast) {
- const id = genId();
-
- const update = (props: ToasterToast) =>
- dispatch({
- type: "UPDATE_TOAST",
- toast: { ...props, id },
- });
- const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
-
- dispatch({
- type: "ADD_TOAST",
- toast: {
- ...props,
- id,
- open: true,
- onOpenChange: (open) => {
- if (!open) dismiss();
- },
- },
- });
-
- return {
- id: id,
- dismiss,
- update,
- };
-}
-
-function useToast() {
- const [state, setState] = React.useState(memoryState);
-
- React.useEffect(() => {
- listeners.push(setState);
- return () => {
- const index = listeners.indexOf(setState);
- if (index > -1) {
- listeners.splice(index, 1);
- }
- };
- }, [state]);
-
- return {
- ...state,
- toast,
- dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
- };
-}
-
-export { useToast, toast };
diff --git a/hooks/use-typewriter.ts b/hooks/use-typewriter.ts
deleted file mode 100644
index 850eab6..0000000
--- a/hooks/use-typewriter.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect, useState } from 'react';
-
-const typing = async ({ text, callback }: { text: string; callback: (i: number) => any }, { delay, speed }: { delay: number; speed: number }) => {
- await new Promise(r => setTimeout(r, delay));
-
- let i = 1;
- while (i < text.length) {
- await new Promise(r => setTimeout(r, speed));
- i++;
- callback(i);
- }
-};
-
-export const useTypewriter = (text: string, delay = 0, speed = 2) => {
- const [displayText, setDisplayText] = useState('');
-
- useEffect(() => {
- typing({ text, callback: i => setDisplayText(text.slice(0, i)) }, { delay, speed });
- }, [text, speed]);
-
- return displayText;
-};
diff --git a/lib/cloudinary.ts b/lib/cloudinary.ts
deleted file mode 100644
index 36eecd0..0000000
--- a/lib/cloudinary.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { v2 as cloudinary } from 'cloudinary';
-
-if (!globalThis.cloudinary) {
- cloudinary.config({
- cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
- api_key: process.env.CLOUDINARY_API_KEY,
- api_secret: process.env.CLOUDINARY_API_SECRET,
- });
- globalThis.cloudinary = cloudinary;
-}
-
-export default globalThis.cloudinary;
diff --git a/lib/db.ts b/lib/db.ts
deleted file mode 100644
index edaed9c..0000000
--- a/lib/db.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-'use server';
-
-import mongoose from 'mongoose';
-import { env } from 'process';
-
-globalThis.db = async (
- {
- MONGODB_URI,
- DBNAME,
- newConnection,
- }: { MONGODB_URI: string; DBNAME: string; newConnection?: boolean } = {
- MONGODB_URI: env.MONGODB_URI!,
- DBNAME: env.DBNAME!,
- newConnection: false,
- }
-) => {
- if (newConnection) return (await mongoose.connect(MONGODB_URI)).connection.useDb(DBNAME!);
-
- if (!globalThis.connection) {
- // Create connection
- globalThis.mongoose = await mongoose.connect(MONGODB_URI);
- globalThis.connection = globalThis.mongoose.connection.useDb(DBNAME!);
-
- return globalThis.connection;
- }
- if ([0, 3, 99].some(state => state === globalThis.connection.readyState)) {
- // First close all connections
- await Promise.all(
- globalThis.mongoose.connections.map(
- async connection => await connection.close(true)
- )
- );
- await globalThis.mongoose.disconnect();
-
- // Then redefine the connection
- globalThis.mongoose = await mongoose.connect(MONGODB_URI);
- globalThis.connection = globalThis.mongoose.connection.useDb(DBNAME!);
-
- return globalThis.connection;
- }
- return globalThis.connection;
-};
-
-export default async (DBNAME?: string) =>
- await db({
- MONGODB_URI: env.MONGODB_URI!,
- DBNAME: DBNAME ?? env.DBNAME!,
- newConnection: DBNAME !== undefined,
- });
diff --git a/lib/gemini.ts b/lib/gemini.ts
deleted file mode 100644
index d4bb4d4..0000000
--- a/lib/gemini.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { env } from 'process';
-import { GoogleGenerativeAI } from '@google/generative-ai';
-
-import type { GenerativeModel, ModelParams } from '@google/generative-ai';
-
-const genAI = new GoogleGenerativeAI(env.GEMINI_API_KEY);
-
-const modelsParams: ModelParams[] = [
- {
- model: 'gemini-1.5-flash',
- generationConfig: { temperature: 0.75, maxOutputTokens: 1650 },
- },
- {
- model: 'gemini-1.5-flash-8b',
- generationConfig: { temperature: 0.63, maxOutputTokens: 1380 },
- },
- { model: 'gemini-1.5-pro', generationConfig: { temperature: 1.1, maxOutputTokens: 1085 } },
- {
- model: 'gemini-2.0-flash',
- generationConfig: { temperature: 0.875, maxOutputTokens: 975 },
- },
-];
-
-const models = new Map(
- modelsParams.map(({ model, ...params }, i) => [
- model,
- genAI.getGenerativeModel({
- model,
- ...params,
- }),
- ])
-);
-
-export default (model: string = 'gemini-1.5-flash'): GenerativeModel => models.get(model)!;
diff --git a/next.config.ts b/next.config.ts
index b832b5c..956aecc 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,43 +1,20 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
- // add all url available in your app
- images: {
- remotePatterns: [
- {
- protocol: 'https',
- hostname: '*', // Allow images from all domains
- },
- ],
- },
- eslint: {
- ignoreDuringBuilds: true,
- },
- typescript: {
- ignoreBuildErrors: true,
- },
- experimental: {
- staleTimes: {
- dynamic: 0,
- },
- serverActions: {
- bodySizeLimit: '20mb',
- },
- },
+ allowedDevOrigins: ['127.0.0.1'],
+ poweredByHeader: false,
+ reactStrictMode: true,
+ serverExternalPackages: ['@libsql/client', 'sharp'],
async headers() {
- return [
- {
- source: '/:path*', // Match all routes
- headers: [
- {
- key: 'Cache-Control',
- value: 'no-cache, must-revalidate, proxy-revalidate',
- },
- { key: 'Pragma', value: 'no-cache' },
- { key: 'Expires', value: '0' },
- ],
- },
- ];
+ return [{
+ source: '/:path*',
+ headers: [
+ { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
+ { key: 'X-Content-Type-Options', value: 'nosniff' },
+ { key: 'X-Frame-Options', value: 'DENY' },
+ { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
+ ],
+ }];
},
};
diff --git a/package.json b/package.json
index 0413176..a8f9899 100644
--- a/package.json
+++ b/package.json
@@ -1,67 +1,65 @@
{
- "name": "dashboard",
- "version": "0.1.0",
+ "name": "autoblog-cms",
+ "version": "2.0.0",
"private": true,
+ "description": "Workspace-isolated AI-assisted editorial CMS with immutable revisions.",
+ "packageManager": "bun@1.3.12",
+ "engines": {
+ "node": ">=20.9.0",
+ "bun": ">=1.3.12"
+ },
"scripts": {
- "dev": "next dev",
+ "dev": "bun run db:setup && next dev",
+ "dev:e2e": "bun scripts/db/setup.ts --reset-demo && next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "start:performance": "bun scripts/db/setup.ts --reset-demo && next start -p 3100",
+ "typecheck": "next typegen && tsc --noEmit --pretty false",
+ "lint": "eslint . --max-warnings 0",
+ "test": "vitest run --config vitest.config.ts",
+ "test:integration": "vitest run --config vitest.integration.config.ts",
+ "test:e2e": "playwright test tests/e2e --project=chromium",
+ "test:a11y": "playwright test tests/accessibility --project=chromium",
+ "test:visual": "playwright test --config=playwright.visual.config.ts --project=chromium",
+ "test:performance": "playwright test --config=playwright.performance.config.ts",
+ "test:lighthouse": "playwright test --config=playwright.lighthouse.config.ts",
+ "audit": "bun audit --production",
+ "security:secrets": "bun scripts/security/scan-secrets.ts",
+ "db:generate": "drizzle-kit generate",
+ "db:migrate": "bun scripts/db/migrate.ts",
+ "db:seed": "bun scripts/db/seed.ts",
+ "db:setup": "bun scripts/db/setup.ts",
+ "jobs:run": "bun scripts/jobs/run.ts"
},
"dependencies": {
- "@google/generative-ai": "^0.21.0",
- "@radix-ui/react-alert-dialog": "^1.1.6",
- "@radix-ui/react-avatar": "^1.1.3",
- "@radix-ui/react-checkbox": "^1.1.4",
- "@radix-ui/react-collapsible": "^1.1.2",
- "@radix-ui/react-context-menu": "^2.2.4",
- "@radix-ui/react-dialog": "^1.1.6",
- "@radix-ui/react-dropdown-menu": "^2.1.6",
- "@radix-ui/react-icons": "^1.3.2",
- "@radix-ui/react-label": "^2.1.2",
- "@radix-ui/react-popover": "^1.1.6",
- "@radix-ui/react-radio-group": "^1.2.3",
- "@radix-ui/react-scroll-area": "^1.2.3",
- "@radix-ui/react-select": "^2.1.4",
- "@radix-ui/react-separator": "^1.1.2",
- "@radix-ui/react-slot": "^1.1.2",
- "@radix-ui/react-switch": "^1.1.2",
- "@radix-ui/react-tabs": "^1.1.3",
- "@radix-ui/react-toast": "^1.2.4",
- "@radix-ui/react-toggle": "^1.1.2",
- "@radix-ui/react-toggle-group": "^1.1.2",
- "@radix-ui/react-tooltip": "^1.1.6",
- "@vercel/speed-insights": "^1.1.0",
- "class-variance-authority": "^0.7.1",
- "cloudinary": "^2.5.1",
- "clsx": "^2.1.1",
- "cmdk": "^1.0.4",
- "date-fns": "^3.6.0",
- "framer-motion": "^12.4.1",
- "html2canvas": "^1.4.1",
- "lucide-react": "^0.469.0",
- "mongoose": "^8.9.3",
- "next": "15.1.11",
- "next-cloudinary": "^6.16.0",
- "react": "^19.0.0",
- "react-day-picker": "^8.10.1",
- "react-dom": "^19.0.0",
- "react-icons": "^5.4.0",
- "react-loading-indicators": "^1.0.0",
- "react-markdown": "^9.0.3",
- "tailwind-merge": "^2.6.0",
- "tailwindcss-animate": "^1.0.7",
- "vaul": "^1.1.2"
+ "@google/genai": "2.13.0",
+ "@libsql/client": "0.17.4",
+ "better-auth": "1.6.23",
+ "drizzle-orm": "0.45.2",
+ "next": "16.2.11",
+ "react": "19.2.8",
+ "react-dom": "19.2.8",
+ "sharp": "0.35.3",
+ "zod": "4.4.3"
},
"devDependencies": {
- "@eslint/eslintrc": "^3",
- "@types/node": "^20",
- "@types/react": "^19",
- "@types/react-dom": "^19",
- "eslint": "^9",
- "eslint-config-next": "15.1.1",
- "postcss": "^8",
- "tailwindcss": "^3.4.1",
- "typescript": "^5"
+ "@axe-core/playwright": "4.12.1",
+ "@playwright/test": "1.61.1",
+ "@types/bun": "1.3.14",
+ "@types/node": "24.10.13",
+ "@types/react": "19.2.14",
+ "@types/react-dom": "19.2.3",
+ "drizzle-kit": "0.31.10",
+ "eslint": "9.39.5",
+ "eslint-config-next": "16.2.11",
+ "lighthouse": "13.4.1",
+ "typescript": "6.0.3",
+ "vitest": "4.1.10"
+ },
+ "overrides": {
+ "esbuild": "0.28.1",
+ "picomatch": "4.0.5",
+ "postcss": "8.5.21",
+ "sharp": "0.35.3"
}
}
diff --git a/playwright.config.ts b/playwright.config.ts
new file mode 100644
index 0000000..157a485
--- /dev/null
+++ b/playwright.config.ts
@@ -0,0 +1,37 @@
+import { defineConfig, devices } from '@playwright/test';
+
+const testDatabaseUrl = process.env.PLAYWRIGHT_DATABASE_URL ?? `file:./data/e2e-${process.pid}.db`;
+
+export default defineConfig({
+ testDir: './tests',
+ testMatch: '**/*.spec.ts',
+ testIgnore: ['**/visual/**', '**/performance/**'],
+ fullyParallel: false,
+ workers: 1,
+ forbidOnly: Boolean(process.env.CI),
+ retries: process.env.CI ? 2 : 0,
+ reporter: process.env.CI ? [['html', { open: 'never' }], ['github']] : 'list',
+ snapshotPathTemplate: '{testDir}/{testFilePath}-snapshots/{arg}{ext}',
+ use: {
+ baseURL: 'http://127.0.0.1:3000',
+ trace: 'retain-on-failure',
+ screenshot: 'only-on-failure',
+ video: 'retain-on-failure',
+ },
+ projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
+ webServer: {
+ command: 'bun run dev:e2e',
+ url: 'http://127.0.0.1:3000',
+ reuseExistingServer: !process.env.CI,
+ timeout: 120_000,
+ env: {
+ DATABASE_URL: testDatabaseUrl,
+ BETTER_AUTH_SECRET: 'e2e-only-secret-with-at-least-32-characters',
+ NEXT_PUBLIC_APP_URL: 'http://127.0.0.1:3000',
+ DEMO_ENABLED: 'true',
+ AI_MODE: 'mock',
+ CRON_SECRET: 'e2e-only-distinct-cron-secret-0000000',
+ AUTH_SIGN_IN_RATE_LIMIT: '60',
+ },
+ },
+});
diff --git a/playwright.lighthouse.config.ts b/playwright.lighthouse.config.ts
new file mode 100644
index 0000000..6706a2d
--- /dev/null
+++ b/playwright.lighthouse.config.ts
@@ -0,0 +1,25 @@
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests/lighthouse',
+ workers: 1,
+ fullyParallel: false,
+ retries: 0,
+ reporter: 'list',
+ timeout: 180_000,
+ webServer: {
+ command: 'bun run start:performance',
+ url: 'http://127.0.0.1:3100',
+ reuseExistingServer: false,
+ timeout: 120_000,
+ env: {
+ DATABASE_URL: 'file:./data/lighthouse.db',
+ BETTER_AUTH_SECRET: 'lighthouse-only-secret-with-32-characters',
+ NEXT_PUBLIC_APP_URL: 'http://127.0.0.1:3100',
+ DEMO_ENABLED: 'true',
+ AI_MODE: 'mock',
+ CRON_SECRET: 'lighthouse-only-cron-secret-000000',
+ AUTH_SIGN_IN_RATE_LIMIT: '30',
+ },
+ },
+});
diff --git a/playwright.performance.config.ts b/playwright.performance.config.ts
new file mode 100644
index 0000000..492cb70
--- /dev/null
+++ b/playwright.performance.config.ts
@@ -0,0 +1,24 @@
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests/performance',
+ workers: 1,
+ fullyParallel: false,
+ retries: 0,
+ reporter: 'list',
+ use: { baseURL: 'http://127.0.0.1:3100', ...devices['Desktop Chrome'] },
+ webServer: {
+ command: 'bun run start:performance',
+ url: 'http://127.0.0.1:3100',
+ reuseExistingServer: false,
+ timeout: 120_000,
+ env: {
+ DATABASE_URL: 'file:./data/performance.db',
+ BETTER_AUTH_SECRET: 'performance-only-secret-with-32-characters',
+ NEXT_PUBLIC_APP_URL: 'http://127.0.0.1:3100',
+ DEMO_ENABLED: 'true', AI_MODE: 'mock',
+ CRON_SECRET: 'performance-only-cron-secret-000000',
+ AUTH_SIGN_IN_RATE_LIMIT: '30',
+ },
+ },
+});
diff --git a/playwright.visual.config.ts b/playwright.visual.config.ts
new file mode 100644
index 0000000..abfb2b6
--- /dev/null
+++ b/playwright.visual.config.ts
@@ -0,0 +1,10 @@
+import base from './playwright.config';
+import { defineConfig } from '@playwright/test';
+
+export default defineConfig({
+ ...base,
+ testDir: './tests/visual',
+ testIgnore: [],
+ retries: 0,
+ workers: 1,
+});
diff --git a/plugins/dynamic-size.ts b/plugins/dynamic-size.ts
deleted file mode 100644
index 1e728a9..0000000
--- a/plugins/dynamic-size.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import plugin from 'tailwindcss/plugin';
-
-export default plugin(({ matchUtilities, theme }) => {
- const toRem = rem => `${rem}rem`; // Converts px to rem for min and max values
- const dynamicClamp = (value, type) => {
- if (!value) return value;
- const min = parseFloat(value) * 0.8 + value.split(parseFloat(value).toString())[1]; // Minimum value in rem
- const max = value; // Maximum value in rem
- const preferred = type === 'width' ? `${parseFloat(value) * 1.6}vw` : `${parseFloat(value) * 1.6}vh`; // Preferred viewport-based value
-
- return `clamp(${min}, ${preferred}, ${max})`;
- };
-
- // Replaces default Tailwind sizing utilities with clamp-based variants
- matchUtilities(
- {
- 'w-clamp': value => ({
- width: dynamicClamp(value, 'width'),
- }),
- 'h-clamp': value => ({
- height: dynamicClamp(value, 'height'),
- }),
- 'size-clamp': value => ({
- width: dynamicClamp(value, 'width'),
- height: dynamicClamp(value, 'height'),
- }),
- },
- { values: theme('spacing'), type: 'any' },
- );
-});
diff --git a/postcss.config.mjs b/postcss.config.mjs
deleted file mode 100644
index 1a69fd2..0000000
--- a/postcss.config.mjs
+++ /dev/null
@@ -1,8 +0,0 @@
-/** @type {import('postcss-load-config').Config} */
-const config = {
- plugins: {
- tailwindcss: {},
- },
-};
-
-export default config;
diff --git a/public/ai.png b/public/ai.png
deleted file mode 100644
index 5157a3f..0000000
Binary files a/public/ai.png and /dev/null differ
diff --git a/public/ai.svg b/public/ai.svg
deleted file mode 100644
index 74cd7e6..0000000
--- a/public/ai.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/public/auth/bg.jpg b/public/auth/bg.jpg
deleted file mode 100644
index de66b5b..0000000
Binary files a/public/auth/bg.jpg and /dev/null differ
diff --git a/public/demo/architecture-design-01.jpg b/public/demo/architecture-design-01.jpg
deleted file mode 100644
index 1435cb9..0000000
Binary files a/public/demo/architecture-design-01.jpg and /dev/null differ
diff --git a/public/demo/board-culture.jpg b/public/demo/board-culture.jpg
deleted file mode 100644
index de66b5b..0000000
Binary files a/public/demo/board-culture.jpg and /dev/null differ
diff --git a/public/demo/community-engagement-01.jpg b/public/demo/community-engagement-01.jpg
deleted file mode 100644
index 9a60410..0000000
Binary files a/public/demo/community-engagement-01.jpg and /dev/null differ
diff --git a/public/demo/digital-innovation-01.webp b/public/demo/digital-innovation-01.webp
deleted file mode 100644
index be5765f..0000000
Binary files a/public/demo/digital-innovation-01.webp and /dev/null differ
diff --git a/public/demo/digital-interface-01.png b/public/demo/digital-interface-01.png
deleted file mode 100644
index b4b7975..0000000
Binary files a/public/demo/digital-interface-01.png and /dev/null differ
diff --git a/public/demo/digital-interface-02.png b/public/demo/digital-interface-02.png
deleted file mode 100644
index 12b98c0..0000000
Binary files a/public/demo/digital-interface-02.png and /dev/null differ
diff --git a/public/demo/fashion-exhibition-01.jpg b/public/demo/fashion-exhibition-01.jpg
deleted file mode 100644
index 4bf796a..0000000
Binary files a/public/demo/fashion-exhibition-01.jpg and /dev/null differ
diff --git a/public/demo/installation-art-01.jpg b/public/demo/installation-art-01.jpg
deleted file mode 100644
index fea58e1..0000000
Binary files a/public/demo/installation-art-01.jpg and /dev/null differ
diff --git a/public/file.svg b/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
deleted file mode 100644
index 567f17b..0000000
--- a/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/iphone.png b/public/iphone.png
deleted file mode 100644
index 6a65dce..0000000
Binary files a/public/iphone.png and /dev/null differ
diff --git a/public/logo.png b/public/logo.png
deleted file mode 100644
index d85fc9a..0000000
Binary files a/public/logo.png and /dev/null differ
diff --git a/public/logo.svg b/public/logo.svg
deleted file mode 100644
index f19b9ba..0000000
--- a/public/logo.svg
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/public/window.svg b/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/scripts/db/migrate.ts b/scripts/db/migrate.ts
new file mode 100644
index 0000000..3b9c3fd
--- /dev/null
+++ b/scripts/db/migrate.ts
@@ -0,0 +1,13 @@
+import { getEnvironment } from '@/src/platform/config/env';
+import { createDatabase } from '@/src/platform/db/client';
+import { migrateDatabase } from '@/src/platform/db/migrate';
+
+const environment = getEnvironment();
+const database = createDatabase(environment.DATABASE_URL, environment.DATABASE_AUTH_TOKEN);
+
+try {
+ const applied = await migrateDatabase(database.client);
+ console.log(applied.length > 0 ? `Applied migrations: ${applied.join(', ')}` : 'Database schema is current.');
+} finally {
+ database.client.close();
+}
diff --git a/scripts/db/seed.ts b/scripts/db/seed.ts
new file mode 100644
index 0000000..8ff2462
--- /dev/null
+++ b/scripts/db/seed.ts
@@ -0,0 +1,15 @@
+import { getEnvironment } from '@/src/platform/config/env';
+import { createDatabase } from '@/src/platform/db/client';
+import { migrateDatabase } from '@/src/platform/db/migrate';
+import { seedDemo } from '@/src/platform/db/seed';
+
+const environment = getEnvironment();
+const database = createDatabase(environment.DATABASE_URL, environment.DATABASE_AUTH_TOKEN);
+
+try {
+ await migrateDatabase(database.client);
+ await seedDemo(database);
+ console.log('Validated demo fixture is ready.');
+} finally {
+ database.client.close();
+}
diff --git a/scripts/db/setup.ts b/scripts/db/setup.ts
new file mode 100644
index 0000000..6850373
--- /dev/null
+++ b/scripts/db/setup.ts
@@ -0,0 +1,15 @@
+import { getEnvironment } from '@/src/platform/config/env';
+import { createDatabase } from '@/src/platform/db/client';
+import { migrateDatabase } from '@/src/platform/db/migrate';
+import { seedDemo } from '@/src/platform/db/seed';
+
+const environment = getEnvironment();
+const database = createDatabase(environment.DATABASE_URL, environment.DATABASE_AUTH_TOKEN);
+
+try {
+ const applied = await migrateDatabase(database.client);
+ if (environment.DEMO_ENABLED) await seedDemo(database, { reset: process.argv.includes('--reset-demo'), requestId: 'setup-script' });
+ console.log(`Database ready (${applied.length} migration${applied.length === 1 ? '' : 's'} applied).`);
+} finally {
+ database.client.close();
+}
diff --git a/scripts/jobs/run.ts b/scripts/jobs/run.ts
new file mode 100644
index 0000000..9c60693
--- /dev/null
+++ b/scripts/jobs/run.ts
@@ -0,0 +1,10 @@
+import { DrizzleEditorialRepository } from '@/src/modules/editorial/drizzle-repository';
+import { MediaCleanupWorker } from '@/src/modules/media/cleanup-worker';
+import { DatabaseMediaProvider } from '@/src/modules/media/database-provider';
+import { getDatabase } from '@/src/platform/db/client';
+
+const repository = new DrizzleEditorialRepository(getDatabase());
+const publication = await repository.runDuePublicationJobs(new Date(), 50);
+const media = await new MediaCleanupWorker(getDatabase(), new DatabaseMediaProvider(getDatabase())).run(new Date(), 50);
+process.stdout.write(`${JSON.stringify({ publication, media })}\n`);
+process.exitCode = publication.failed > 0 || media.failed > 0 ? 1 : 0;
diff --git a/scripts/security/scan-secrets.ts b/scripts/security/scan-secrets.ts
new file mode 100644
index 0000000..0b37423
--- /dev/null
+++ b/scripts/security/scan-secrets.ts
@@ -0,0 +1,33 @@
+export {};
+
+const patterns = [
+ '-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----',
+ 'AIza[0-9A-Za-z_-]{30,}',
+ 'gh[pousr]_[0-9A-Za-z]{30,}',
+ 'sk-[0-9A-Za-z]{24,}',
+ 'mongodb(\\+srv)?://[^[:space:]<>]+',
+].join('|');
+
+const child = Bun.spawn(['git', 'grep', '-nI', '-E', '-e', patterns, '--', ':!bun.lock'], {
+ stdout: 'pipe',
+ stderr: 'pipe',
+});
+const output = await new Response(child.stdout).text();
+const errorOutput = await new Response(child.stderr).text();
+const exitCode = await child.exited;
+
+if (exitCode === 0) {
+ const locations = output.trim().split(/\r?\n/u).map((line) => {
+ const match = /^([^:]+):(\d+):/u.exec(line);
+ return match ? `${match[1]}:${match[2]}` : 'tracked file (location withheld)';
+ });
+ console.error(`Potential secret material found at:\n${locations.join('\n')}`);
+ globalThis.process.exitCode = 1;
+}
+
+if (exitCode !== 1) {
+ console.error(errorOutput.trim() || 'Secret scan could not inspect tracked files.');
+ globalThis.process.exitCode = exitCode;
+}
+
+if (exitCode === 1) console.log('Current-tree secret scan passed.');
diff --git a/src/modules/ai/adapter.ts b/src/modules/ai/adapter.ts
new file mode 100644
index 0000000..2958d32
--- /dev/null
+++ b/src/modules/ai/adapter.ts
@@ -0,0 +1,6 @@
+import type { AIAdapterResult, AISuggestionInput } from '@/src/modules/ai/domain';
+
+export interface AIAdapter {
+ readonly mode: 'mock' | 'gemini';
+ suggest(input: AISuggestionInput, signal: AbortSignal): Promise;
+}
diff --git a/src/modules/ai/domain.ts b/src/modules/ai/domain.ts
new file mode 100644
index 0000000..cbb7848
--- /dev/null
+++ b/src/modules/ai/domain.ts
@@ -0,0 +1,38 @@
+import { z } from 'zod';
+
+export const AI_MAX_INPUT_CONTENT = 20_000;
+export const AI_MAX_OUTPUT_CHARACTERS = 24_000;
+
+export const aiSuggestionInputSchema = z.object({
+ postId: z.string().min(1).max(120),
+ title: z.string().trim().min(3).max(180),
+ excerpt: z.string().trim().max(320),
+ content: z.string().max(AI_MAX_INPUT_CONTENT),
+ instruction: z.string().trim().min(3).max(500),
+});
+export type AISuggestionInput = z.infer;
+
+export const aiSuggestionSchema = z.object({
+ title: z.string().trim().min(3).max(180),
+ excerpt: z.string().trim().max(320),
+ content: z.string().max(AI_MAX_INPUT_CONTENT),
+ rationale: z.string().trim().min(3).max(500),
+});
+export type AISuggestion = z.infer;
+
+export type AIAdapterResult = Readonly<{
+ suggestion: AISuggestion;
+ provider: string;
+ model: string;
+ inputTokens?: number;
+ outputTokens?: number;
+}>;
+
+export type AISuggestionResult = Readonly<{
+ suggestion: AISuggestion;
+ mode: 'mock' | 'gemini';
+ provider: string;
+ model: string;
+ latencyMs: number;
+ remainingCharacters: number;
+}>;
diff --git a/src/modules/ai/drizzle-repository.ts b/src/modules/ai/drizzle-repository.ts
new file mode 100644
index 0000000..611eaa1
--- /dev/null
+++ b/src/modules/ai/drizzle-repository.ts
@@ -0,0 +1,70 @@
+import { and, eq, sql } from 'drizzle-orm';
+
+import type { AIRepository } from '@/src/modules/ai/repository';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { aiQuotaWindows, aiUsage, auditEvents, rateLimits } from '@/src/platform/db/schema';
+import { AppError } from '@/src/platform/observability/errors';
+
+function monthStart(now: Date): Date {
+ return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
+}
+
+export class DrizzleAIRepository implements AIRepository {
+ constructor(private readonly database: DatabaseContext) {}
+
+ async consumeRateLimit(key: string, limit: number, windowMs: number, now: Date): Promise {
+ const windowStartedAt = new Date(Math.floor(now.getTime() / windowMs) * windowMs);
+ const expiresAt = new Date(windowStartedAt.getTime() + windowMs);
+ await this.database.db.transaction(async (transaction) => {
+ const inserted = await transaction.insert(rateLimits).values({ key, windowStartedAt, count: 1, expiresAt }).onConflictDoNothing();
+ if (inserted.rowsAffected === 1) return;
+ const updated = await transaction.update(rateLimits).set({ count: sql`${rateLimits.count} + 1` })
+ .where(and(eq(rateLimits.key, key), eq(rateLimits.windowStartedAt, windowStartedAt), sql`${rateLimits.count} < ${limit}`));
+ if (updated.rowsAffected !== 1) throw new AppError('RATE_LIMITED', { retryAfterSeconds: Math.ceil((expiresAt.getTime() - now.getTime()) / 1000) });
+ });
+ }
+
+ async reserveQuota(workspaceId: string, amount: number, limit: number, now: Date): Promise> {
+ const windowStartedAt = monthStart(now);
+ await this.database.db.transaction(async (transaction) => {
+ await transaction.insert(aiQuotaWindows).values({ workspaceId, windowStartedAt, reservedCharacters: 0, usedCharacters: 0, updatedAt: now }).onConflictDoNothing();
+ const updated = await transaction.update(aiQuotaWindows).set({ reservedCharacters: sql`${aiQuotaWindows.reservedCharacters} + ${amount}`, updatedAt: now })
+ .where(and(eq(aiQuotaWindows.workspaceId, workspaceId), eq(aiQuotaWindows.windowStartedAt, windowStartedAt), sql`${aiQuotaWindows.reservedCharacters} + ${aiQuotaWindows.usedCharacters} + ${amount} <= ${limit}`));
+ if (updated.rowsAffected !== 1) throw new AppError('QUOTA_EXCEEDED');
+ });
+ return { windowStartedAt };
+ }
+
+ async releaseQuota(workspaceId: string, windowStartedAt: Date, amount: number, now: Date): Promise {
+ await this.database.db.update(aiQuotaWindows).set({ reservedCharacters: sql`max(0, ${aiQuotaWindows.reservedCharacters} - ${amount})`, updatedAt: now })
+ .where(and(eq(aiQuotaWindows.workspaceId, workspaceId), eq(aiQuotaWindows.windowStartedAt, windowStartedAt)));
+ }
+
+ async complete(command: Parameters[0]): Promise {
+ const inputCharacters = command.input.title.length + command.input.excerpt.length + command.input.content.length + command.input.instruction.length;
+ const outputCharacters = command.adapter.suggestion.title.length + command.adapter.suggestion.excerpt.length + command.adapter.suggestion.content.length + command.adapter.suggestion.rationale.length;
+ const actual = inputCharacters + outputCharacters;
+ return this.database.db.transaction(async (transaction) => {
+ await transaction.update(aiQuotaWindows).set({
+ reservedCharacters: sql`max(0, ${aiQuotaWindows.reservedCharacters} - ${command.reservedCharacters})`,
+ usedCharacters: sql`${aiQuotaWindows.usedCharacters} + ${actual}`, updatedAt: command.now,
+ }).where(and(eq(aiQuotaWindows.workspaceId, command.workspaceId), eq(aiQuotaWindows.windowStartedAt, command.windowStartedAt)));
+ await transaction.insert(aiUsage).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, userId: command.userId,
+ mode: command.mode, provider: command.adapter.provider, model: command.adapter.model,
+ latencyMs: command.latencyMs, inputCharacters, outputCharacters,
+ inputTokens: command.adapter.inputTokens, outputTokens: command.adapter.outputTokens, createdAt: command.now,
+ });
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.userId,
+ action: 'ai.suggested', targetType: 'post', targetId: command.input.postId,
+ requestId: command.requestId,
+ metadata: { mode: command.mode, provider: command.adapter.provider, model: command.adapter.model, inputCharacters, outputCharacters },
+ createdAt: command.now,
+ });
+ const [window] = await transaction.select({ used: aiQuotaWindows.usedCharacters, reserved: aiQuotaWindows.reservedCharacters }).from(aiQuotaWindows)
+ .where(and(eq(aiQuotaWindows.workspaceId, command.workspaceId), eq(aiQuotaWindows.windowStartedAt, command.windowStartedAt))).limit(1);
+ return Math.max(0, command.quotaLimit - (window?.used ?? command.quotaLimit) - (window?.reserved ?? 0));
+ });
+ }
+}
diff --git a/src/modules/ai/gemini-adapter.ts b/src/modules/ai/gemini-adapter.ts
new file mode 100644
index 0000000..bf9b63b
--- /dev/null
+++ b/src/modules/ai/gemini-adapter.ts
@@ -0,0 +1,71 @@
+import { GoogleGenAI } from '@google/genai';
+
+import type { AIAdapter } from '@/src/modules/ai/adapter';
+import { aiSuggestionSchema, type AIAdapterResult, type AISuggestionInput } from '@/src/modules/ai/domain';
+import { AppError } from '@/src/platform/observability/errors';
+
+type GeminiRawResult = Readonly<{ text?: string; inputTokens?: number; outputTokens?: number }>;
+type GeminiGenerate = (input: AISuggestionInput, signal: AbortSignal) => Promise;
+
+const responseJsonSchema = {
+ type: 'object',
+ additionalProperties: false,
+ required: ['title', 'excerpt', 'content', 'rationale'],
+ properties: {
+ title: { type: 'string', maxLength: 180 },
+ excerpt: { type: 'string', maxLength: 320 },
+ content: { type: 'string', maxLength: 20_000 },
+ rationale: { type: 'string', maxLength: 500 },
+ },
+} as const;
+
+export class GeminiAIAdapter implements AIAdapter {
+ readonly mode = 'gemini' as const;
+ private readonly generate: GeminiGenerate;
+
+ constructor(apiKey: string, private readonly model: string, generate?: GeminiGenerate) {
+ this.generate = generate ?? this.createGenerator(apiKey);
+ }
+
+ async suggest(input: AISuggestionInput, signal: AbortSignal): Promise {
+ try {
+ const result = await this.generate(input, signal);
+ if (!result.text) throw new AppError('PROVIDER_UNAVAILABLE');
+ const suggestion = aiSuggestionSchema.parse(JSON.parse(result.text));
+ return {
+ suggestion, provider: 'google-gemini', model: this.model,
+ ...(result.inputTokens === undefined ? {} : { inputTokens: result.inputTokens }),
+ ...(result.outputTokens === undefined ? {} : { outputTokens: result.outputTokens }),
+ };
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ throw new AppError('PROVIDER_UNAVAILABLE', undefined, error);
+ }
+ }
+
+ private createGenerator(apiKey: string): GeminiGenerate {
+ const client = new GoogleGenAI({ apiKey });
+ return async (input, signal) => {
+ const response = await client.models.generateContent({
+ model: this.model,
+ contents: JSON.stringify({
+ task: 'Return an editorial suggestion. Preserve factual meaning; do not claim external verification.',
+ instruction: input.instruction,
+ draft: { title: input.title, excerpt: input.excerpt, content: input.content },
+ }),
+ config: {
+ abortSignal: signal,
+ maxOutputTokens: 2_048,
+ temperature: 0.2,
+ responseMimeType: 'application/json',
+ responseJsonSchema,
+ },
+ });
+ return {
+ ...(response.text === undefined ? {} : { text: response.text }),
+ ...(response.usageMetadata?.promptTokenCount === undefined ? {} : { inputTokens: response.usageMetadata.promptTokenCount }),
+ ...(response.usageMetadata?.candidatesTokenCount === undefined ? {} : { outputTokens: response.usageMetadata.candidatesTokenCount }),
+ };
+ };
+ }
+}
diff --git a/src/modules/ai/mock-adapter.ts b/src/modules/ai/mock-adapter.ts
new file mode 100644
index 0000000..11d4c1a
--- /dev/null
+++ b/src/modules/ai/mock-adapter.ts
@@ -0,0 +1,23 @@
+import type { AIAdapter } from '@/src/modules/ai/adapter';
+import { aiSuggestionSchema, type AIAdapterResult, type AISuggestionInput } from '@/src/modules/ai/domain';
+
+export class MockAIAdapter implements AIAdapter {
+ readonly mode = 'mock' as const;
+
+ async suggest(input: AISuggestionInput, signal: AbortSignal): Promise {
+ signal.throwIfAborted();
+ const title = input.title.includes(':') ? input.title : `${input.title}: editorial proof`.slice(0, 180);
+ const excerpt = input.excerpt || `A concise editorial angle shaped around: ${input.instruction}`.slice(0, 320);
+ const content = input.content.trim()
+ ? `${input.content.trim()}\n\n## Editorial next step\n\n${input.instruction}`.slice(0, 20_000)
+ : `## Editorial brief\n\n${input.instruction}`;
+ return {
+ suggestion: aiSuggestionSchema.parse({
+ title, excerpt, content,
+ rationale: 'Deterministic demo adapter: structure is reproducible and no external provider was called.',
+ }),
+ provider: 'deterministic-mock',
+ model: 'editorial-suggestion-v1',
+ };
+ }
+}
diff --git a/src/modules/ai/repository.ts b/src/modules/ai/repository.ts
new file mode 100644
index 0000000..b0acb82
--- /dev/null
+++ b/src/modules/ai/repository.ts
@@ -0,0 +1,12 @@
+import type { AIAdapterResult, AISuggestionInput } from '@/src/modules/ai/domain';
+
+export interface AIRepository {
+ consumeRateLimit(key: string, limit: number, windowMs: number, now: Date): Promise;
+ reserveQuota(workspaceId: string, amount: number, limit: number, now: Date): Promise>;
+ releaseQuota(workspaceId: string, windowStartedAt: Date, amount: number, now: Date): Promise;
+ complete(command: Readonly<{
+ workspaceId: string; userId: string; requestId: string; mode: 'mock' | 'gemini';
+ input: AISuggestionInput; adapter: AIAdapterResult; latencyMs: number;
+ windowStartedAt: Date; reservedCharacters: number; quotaLimit: number; now: Date;
+ }>): Promise;
+}
diff --git a/src/modules/ai/service.ts b/src/modules/ai/service.ts
new file mode 100644
index 0000000..1a3c003
--- /dev/null
+++ b/src/modules/ai/service.ts
@@ -0,0 +1,81 @@
+import type { AIAdapter } from '@/src/modules/ai/adapter';
+import { AI_MAX_OUTPUT_CHARACTERS, aiSuggestionInputSchema, type AISuggestionResult } from '@/src/modules/ai/domain';
+import { DrizzleAIRepository } from '@/src/modules/ai/drizzle-repository';
+import { GeminiAIAdapter } from '@/src/modules/ai/gemini-adapter';
+import { MockAIAdapter } from '@/src/modules/ai/mock-adapter';
+import type { AIRepository } from '@/src/modules/ai/repository';
+import { DrizzleEditorialRepository } from '@/src/modules/editorial/drizzle-repository';
+import type { PostDetail } from '@/src/modules/editorial/domain';
+import type { MembershipContext } from '@/src/modules/identity/domain';
+import { authorize } from '@/src/modules/identity/policy';
+import { getEnvironment } from '@/src/platform/config/env';
+import { getDatabase } from '@/src/platform/db/client';
+import { AppError } from '@/src/platform/observability/errors';
+
+const AI_RATE_LIMIT = 5;
+const AI_RATE_WINDOW_MS = 60_000;
+const AI_TIMEOUT_MS = 8_000;
+
+type PostReader = (workspaceId: string, postId: string) => Promise;
+
+export class AIService {
+ constructor(
+ private readonly repository: AIRepository,
+ private readonly adapter: AIAdapter,
+ private readonly quotaLimit: number,
+ private readonly readPost: PostReader,
+ private readonly timeoutMs = AI_TIMEOUT_MS,
+ ) {}
+
+ async suggest(context: MembershipContext, rawInput: unknown, requestId: string): Promise {
+ authorize(context.role, 'ai.suggest');
+ const input = aiSuggestionInputSchema.parse(rawInput);
+ if (!await this.readPost(context.workspaceId, input.postId)) throw new AppError('NOT_FOUND');
+ const now = new Date();
+ await this.repository.consumeRateLimit(`ai:${context.workspaceId}:${context.userId}`, AI_RATE_LIMIT, AI_RATE_WINDOW_MS, now);
+ const inputCharacters = input.title.length + input.excerpt.length + input.content.length + input.instruction.length;
+ const reservedCharacters = inputCharacters + AI_MAX_OUTPUT_CHARACTERS;
+ const reservation = await this.repository.reserveQuota(context.workspaceId, reservedCharacters, this.quotaLimit, now);
+ const controller = new AbortController();
+ let timeout: ReturnType | undefined;
+ const startedAt = performance.now();
+ try {
+ const adapter = await Promise.race([
+ this.adapter.suggest(input, controller.signal),
+ new Promise((_resolve, reject) => {
+ timeout = setTimeout(() => { controller.abort(); reject(new AppError('PROVIDER_UNAVAILABLE')); }, this.timeoutMs);
+ }),
+ ]);
+ const latencyMs = Math.max(0, Math.round(performance.now() - startedAt));
+ const remainingCharacters = await this.repository.complete({
+ workspaceId: context.workspaceId, userId: context.userId, requestId, mode: this.adapter.mode,
+ input, adapter, latencyMs, windowStartedAt: reservation.windowStartedAt,
+ reservedCharacters, quotaLimit: this.quotaLimit, now: new Date(),
+ });
+ return { suggestion: adapter.suggestion, mode: this.adapter.mode, provider: adapter.provider, model: adapter.model, latencyMs, remainingCharacters };
+ } catch (error) {
+ await this.repository.releaseQuota(context.workspaceId, reservation.windowStartedAt, reservedCharacters, new Date());
+ if (error instanceof AppError) throw error;
+ throw new AppError('PROVIDER_UNAVAILABLE', undefined, error);
+ } finally {
+ if (timeout) clearTimeout(timeout);
+ }
+ }
+}
+
+let aiService: AIService | undefined;
+
+export function getAIService(): AIService {
+ if (!aiService) {
+ const database = getDatabase();
+ const environment = getEnvironment();
+ const adapter: AIAdapter = environment.AI_MODE === 'gemini'
+ ? environment.GEMINI_API_KEY
+ ? new GeminiAIAdapter(environment.GEMINI_API_KEY, environment.GEMINI_MODEL)
+ : (() => { throw new AppError('PROVIDER_UNAVAILABLE'); })()
+ : new MockAIAdapter();
+ const editorial = new DrizzleEditorialRepository(database);
+ aiService = new AIService(new DrizzleAIRepository(database), adapter, environment.AI_MONTHLY_CHARACTER_QUOTA, (workspaceId, postId) => editorial.find(workspaceId, postId));
+ }
+ return aiService;
+}
diff --git a/src/modules/editorial/domain.ts b/src/modules/editorial/domain.ts
new file mode 100644
index 0000000..1e38b7b
--- /dev/null
+++ b/src/modules/editorial/domain.ts
@@ -0,0 +1,85 @@
+import { z } from 'zod';
+import { transitionActionValues } from '@/src/modules/editorial/workflow';
+
+export const postStateSchema = z.enum(['Draft', 'InReview', 'ChangesRequested', 'Approved', 'Scheduled', 'Published', 'Archived']);
+export type PostState = z.infer;
+
+const titleSchema = z.string().trim().min(3).max(180);
+const excerptSchema = z.string().trim().max(320);
+const contentSchema = z.string().max(100_000);
+
+export const createPostSchema = z.object({
+ title: titleSchema,
+ excerpt: excerptSchema.default(''),
+ content: contentSchema.default(''),
+});
+export type CreatePostInput = z.infer;
+
+export const savePostSchema = z.object({
+ expectedVersion: z.number().int().positive(),
+ title: titleSchema,
+ excerpt: excerptSchema,
+ content: contentSchema,
+});
+export type SavePostInput = z.infer;
+
+export const transitionPostSchema = z.object({
+ expectedVersion: z.number().int().positive(),
+ action: z.enum(transitionActionValues),
+ scheduledFor: z.iso.datetime().optional(),
+ idempotencyKey: z.string().trim().min(8).max(120).optional(),
+}).superRefine((input, context) => {
+ if (input.action === 'schedule' && !input.scheduledFor) context.addIssue({ code: 'custom', path: ['scheduledFor'], message: 'Scheduling requires a date.' });
+ if ((input.action === 'schedule' || input.action === 'publish') && !input.idempotencyKey) context.addIssue({ code: 'custom', path: ['idempotencyKey'], message: 'Publication requires an idempotency key.' });
+});
+export type TransitionPostInput = z.infer;
+
+export const restoreRevisionSchema = z.object({
+ expectedVersion: z.number().int().positive(),
+ revisionId: z.string().min(1).max(120),
+});
+export type RestoreRevisionInput = z.infer;
+
+export type PostSummary = Readonly<{
+ id: string;
+ slug: string;
+ title: string;
+ excerpt: string;
+ state: PostState;
+ version: number;
+ authorId: string;
+ updatedAt: Date;
+}>;
+
+export type PostDetail = PostSummary & Readonly<{
+ content: string;
+ draftRevisionId: string;
+ publishedRevisionId: string | null;
+ createdAt: Date;
+ scheduledFor: Date | null;
+ publishedAt: Date | null;
+}>;
+
+export type RevisionDetail = Readonly<{
+ id: string;
+ postId: string;
+ version: number;
+ title: string;
+ excerpt: string;
+ content: string;
+ authorId: string;
+ restoredFromRevisionId: string | null;
+ createdAt: Date;
+}>;
+
+export type PublicPost = Readonly<{
+ workspaceName: string;
+ slug: string;
+ title: string;
+ excerpt: string;
+ content: string;
+ revisionId: string;
+ publishedAt: Date;
+}>;
+
+export type JobRunResult = Readonly<{ claimed: number; completed: number; failed: number }>;
diff --git a/src/modules/editorial/drizzle-repository.ts b/src/modules/editorial/drizzle-repository.ts
new file mode 100644
index 0000000..2371df9
--- /dev/null
+++ b/src/modules/editorial/drizzle-repository.ts
@@ -0,0 +1,400 @@
+import { and, asc, desc, eq, lt, lte, or, sql } from 'drizzle-orm';
+import { z } from 'zod';
+
+import { postStateSchema, type PostDetail, type PostSummary, type PublicPost, type RevisionDetail } from '@/src/modules/editorial/domain';
+import type { EditorialRepository } from '@/src/modules/editorial/repository';
+import { resolveTransition } from '@/src/modules/editorial/workflow';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { auditEvents, jobs, posts, publications, revisions, workspaces } from '@/src/platform/db/schema';
+import { AppError } from '@/src/platform/observability/errors';
+
+type PostRow = typeof posts.$inferSelect;
+type RevisionRow = typeof revisions.$inferSelect;
+
+const publishJobPayloadSchema = z.object({
+ postId: z.string().min(1),
+ publicationId: z.string().min(1),
+ revisionId: z.string().min(1),
+});
+
+function slugify(title: string): string {
+ const slug = title.toLowerCase().normalize('NFKD').replace(/[\u0300-\u036f]/gu, '').replace(/[^a-z0-9]+/gu, '-').replace(/^-|-$/gu, '').slice(0, 72);
+ return slug || `post-${crypto.randomUUID().slice(0, 8)}`;
+}
+
+function toSummary(row: PostRow): PostSummary {
+ return {
+ id: row.id,
+ slug: row.slug,
+ title: row.title,
+ excerpt: row.excerpt,
+ state: postStateSchema.parse(row.state),
+ version: row.version,
+ authorId: row.authorId,
+ updatedAt: row.updatedAt,
+ };
+}
+
+function toRevision(row: RevisionRow): RevisionDetail {
+ return {
+ id: row.id,
+ postId: row.postId,
+ version: row.version,
+ title: row.title,
+ excerpt: row.excerpt,
+ content: row.content,
+ authorId: row.authorId,
+ restoredFromRevisionId: row.restoredFromRevisionId,
+ createdAt: row.createdAt,
+ };
+}
+
+function isRevisionVersionConflict(error: unknown): boolean {
+ let current = error;
+ for (let depth = 0; depth < 4 && current instanceof Error; depth += 1) {
+ if (/revision_post_version_unique|UNIQUE constraint failed: revisions\.post_id, revisions\.version/iu.test(current.message)) return true;
+ current = current.cause;
+ }
+ return false;
+}
+
+export class DrizzleEditorialRepository implements EditorialRepository {
+ constructor(private readonly database: DatabaseContext) {}
+
+ async list(workspaceId: string): Promise {
+ const rows = await this.database.db.select().from(posts).where(eq(posts.workspaceId, workspaceId)).orderBy(desc(posts.updatedAt));
+ return rows.map(toSummary);
+ }
+
+ async find(workspaceId: string, postId: string): Promise {
+ const [row] = await this.database.db.select({ post: posts, content: revisions.content })
+ .from(posts)
+ .innerJoin(revisions, and(eq(revisions.id, posts.draftRevisionId), eq(revisions.workspaceId, posts.workspaceId)))
+ .where(and(eq(posts.workspaceId, workspaceId), eq(posts.id, postId)))
+ .limit(1);
+ if (!row || !row.post.draftRevisionId) return null;
+ return {
+ ...toSummary(row.post),
+ content: row.content,
+ draftRevisionId: row.post.draftRevisionId,
+ publishedRevisionId: row.post.publishedRevisionId,
+ createdAt: row.post.createdAt,
+ scheduledFor: row.post.scheduledFor,
+ publishedAt: row.post.publishedAt,
+ };
+ }
+
+ async create(command: Parameters[0]): Promise {
+ const postId = crypto.randomUUID();
+ const revisionId = crypto.randomUUID();
+ const now = new Date();
+ const baseSlug = slugify(command.input.title);
+ const slug = `${baseSlug}-${postId.slice(0, 6)}`;
+
+ await this.database.db.transaction(async (transaction) => {
+ await transaction.insert(posts).values({
+ id: postId,
+ workspaceId: command.workspaceId,
+ slug,
+ title: command.input.title,
+ excerpt: command.input.excerpt,
+ state: 'Draft',
+ version: 1,
+ authorId: command.actorId,
+ createdAt: now,
+ updatedAt: now,
+ });
+ await transaction.insert(revisions).values({
+ id: revisionId,
+ workspaceId: command.workspaceId,
+ postId,
+ version: 1,
+ title: command.input.title,
+ excerpt: command.input.excerpt,
+ content: command.input.content,
+ authorId: command.actorId,
+ createdAt: now,
+ });
+ await transaction.update(posts).set({ draftRevisionId: revisionId }).where(and(eq(posts.workspaceId, command.workspaceId), eq(posts.id, postId)));
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: 'post.created', targetType: 'post', targetId: postId, requestId: command.requestId,
+ metadata: { version: 1 }, createdAt: now,
+ });
+ });
+
+ const created = await this.find(command.workspaceId, postId);
+ if (!created) throw new AppError('INTERNAL_FAILURE');
+ return created;
+ }
+
+ async save(command: Parameters[0]): Promise {
+ const revisionId = crypto.randomUUID();
+ const newVersion = command.input.expectedVersion + 1;
+ const now = new Date();
+
+ try {
+ await this.database.db.transaction(async (transaction) => {
+ await transaction.insert(revisions).values({
+ id: revisionId,
+ workspaceId: command.workspaceId,
+ postId: command.postId,
+ version: newVersion,
+ title: command.input.title,
+ excerpt: command.input.excerpt,
+ content: command.input.content,
+ authorId: command.actorId,
+ createdAt: now,
+ });
+ const result = await transaction.update(posts).set({
+ title: command.input.title,
+ excerpt: command.input.excerpt,
+ state: sql`case when ${posts.state} = 'Published' then 'Draft' else ${posts.state} end`,
+ version: newVersion,
+ draftRevisionId: revisionId,
+ updatedAt: now,
+ }).where(and(eq(posts.workspaceId, command.workspaceId), eq(posts.id, command.postId), eq(posts.version, command.input.expectedVersion)));
+ if (result.rowsAffected !== 1) throw new AppError('VERSION_CONFLICT', { expectedVersion: command.input.expectedVersion });
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: 'post.saved', targetType: 'post', targetId: command.postId, requestId: command.requestId,
+ metadata: { version: newVersion }, createdAt: now,
+ });
+ });
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ if (isRevisionVersionConflict(error)) {
+ throw new AppError('VERSION_CONFLICT', { expectedVersion: command.input.expectedVersion });
+ }
+ throw error;
+ }
+
+ const saved = await this.find(command.workspaceId, command.postId);
+ if (!saved) throw new AppError('INTERNAL_FAILURE');
+ return saved;
+ }
+
+ async listRevisions(workspaceId: string, postId: string): Promise {
+ const rows = await this.database.db.select().from(revisions)
+ .where(and(eq(revisions.workspaceId, workspaceId), eq(revisions.postId, postId)))
+ .orderBy(desc(revisions.version));
+ return rows.map(toRevision);
+ }
+
+ async restore(command: Parameters[0]): Promise {
+ const [target] = await this.database.db.select().from(revisions)
+ .where(and(eq(revisions.workspaceId, command.workspaceId), eq(revisions.postId, command.postId), eq(revisions.id, command.input.revisionId)))
+ .limit(1);
+ if (!target) throw new AppError('NOT_FOUND');
+ const revisionId = crypto.randomUUID();
+ const newVersion = command.input.expectedVersion + 1;
+ const now = new Date();
+
+ try {
+ await this.database.db.transaction(async (transaction) => {
+ await transaction.insert(revisions).values({
+ id: revisionId, workspaceId: command.workspaceId, postId: command.postId,
+ version: newVersion, title: target.title, excerpt: target.excerpt, content: target.content,
+ authorId: command.actorId, restoredFromRevisionId: target.id, createdAt: now,
+ });
+ const result = await transaction.update(posts).set({
+ title: target.title, excerpt: target.excerpt, draftRevisionId: revisionId,
+ state: sql`case when ${posts.state} = 'Published' then 'Draft' else ${posts.state} end`,
+ version: newVersion, updatedAt: now,
+ }).where(and(eq(posts.workspaceId, command.workspaceId), eq(posts.id, command.postId), eq(posts.version, command.input.expectedVersion)));
+ if (result.rowsAffected !== 1) throw new AppError('VERSION_CONFLICT', { expectedVersion: command.input.expectedVersion });
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: 'revision.restored', targetType: 'post', targetId: command.postId,
+ requestId: command.requestId, metadata: { version: newVersion, restoredFromRevisionId: target.id }, createdAt: now,
+ });
+ });
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ if (isRevisionVersionConflict(error)) throw new AppError('VERSION_CONFLICT', { expectedVersion: command.input.expectedVersion });
+ throw error;
+ }
+ const restored = await this.find(command.workspaceId, command.postId);
+ if (!restored) throw new AppError('INTERNAL_FAILURE');
+ return restored;
+ }
+
+ async transition(command: Parameters[0]): Promise {
+ if (command.input.idempotencyKey) {
+ const existing = await this.findPublicationByKey(command.input.idempotencyKey);
+ if (existing) {
+ if (existing.workspaceId !== command.workspaceId || existing.postId !== command.postId) throw new AppError('VERSION_CONFLICT');
+ const post = await this.find(command.workspaceId, command.postId);
+ if (!post) throw new AppError('NOT_FOUND');
+ return post;
+ }
+ }
+
+ const current = await this.find(command.workspaceId, command.postId);
+ if (!current) throw new AppError('NOT_FOUND');
+ const rule = resolveTransition(current.state, command.input.action);
+ const now = new Date();
+ const newVersion = command.input.expectedVersion + 1;
+ const scheduledFor = command.input.scheduledFor ? new Date(command.input.scheduledFor) : null;
+ const idempotencyKey = command.input.idempotencyKey;
+ if (command.input.action === 'schedule' && (!scheduledFor || !idempotencyKey)) throw new AppError('VALIDATION_FAILED');
+ if (command.input.action === 'publish' && !idempotencyKey) throw new AppError('VALIDATION_FAILED');
+ const publicationId = crypto.randomUUID();
+
+ try {
+ await this.database.db.transaction(async (transaction) => {
+ const scheduledPublications = command.input.action === 'archive' && current.state === 'Scheduled'
+ ? await transaction.select({ idempotencyKey: publications.idempotencyKey }).from(publications)
+ .where(and(eq(publications.workspaceId, command.workspaceId), eq(publications.postId, command.postId), eq(publications.status, 'scheduled')))
+ : [];
+ const result = await transaction.update(posts).set({
+ state: rule.to,
+ version: newVersion,
+ updatedAt: now,
+ ...(command.input.action === 'submit' ? { submittedAt: now } : {}),
+ ...(command.input.action === 'request_changes' ? { approvedAt: null } : {}),
+ ...(command.input.action === 'approve' ? { approvedAt: now } : {}),
+ ...(command.input.action === 'schedule' ? { scheduledFor } : {}),
+ ...(command.input.action === 'publish' ? { publishedRevisionId: current.draftRevisionId, publishedAt: now, scheduledFor: null } : {}),
+ ...(command.input.action === 'archive' ? { archivedAt: now, scheduledFor: null } : {}),
+ }).where(and(eq(posts.workspaceId, command.workspaceId), eq(posts.id, command.postId), eq(posts.version, command.input.expectedVersion), eq(posts.state, current.state)));
+ if (result.rowsAffected !== 1) throw new AppError('VERSION_CONFLICT', { expectedVersion: command.input.expectedVersion });
+
+ if (command.input.action === 'schedule') {
+ if (!scheduledFor || !idempotencyKey) throw new AppError('VALIDATION_FAILED');
+ await transaction.insert(publications).values({
+ id: publicationId, workspaceId: command.workspaceId, postId: command.postId,
+ revisionId: current.draftRevisionId, status: 'scheduled', scheduledFor,
+ idempotencyKey, createdBy: command.actorId, createdAt: now,
+ });
+ await transaction.insert(jobs).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, type: 'publish',
+ payload: { postId: command.postId, publicationId, revisionId: current.draftRevisionId },
+ status: 'pending', runAt: scheduledFor, idempotencyKey: `job:${idempotencyKey}`,
+ createdAt: now, updatedAt: now,
+ });
+ }
+
+ if (command.input.action === 'publish') {
+ if (!idempotencyKey) throw new AppError('VALIDATION_FAILED');
+ await transaction.insert(publications).values({
+ id: publicationId, workspaceId: command.workspaceId, postId: command.postId,
+ revisionId: current.draftRevisionId, status: 'published', publishedAt: now,
+ idempotencyKey, createdBy: command.actorId, createdAt: now,
+ });
+ }
+
+ if (command.input.action === 'archive' && current.state === 'Scheduled') {
+ await transaction.update(publications).set({ status: 'cancelled' })
+ .where(and(eq(publications.workspaceId, command.workspaceId), eq(publications.postId, command.postId), eq(publications.status, 'scheduled')));
+ for (const publication of scheduledPublications) {
+ await transaction.update(jobs).set({ status: 'completed', leaseUntil: null, updatedAt: now })
+ .where(and(eq(jobs.workspaceId, command.workspaceId), eq(jobs.idempotencyKey, `job:${publication.idempotencyKey}`), or(eq(jobs.status, 'pending'), eq(jobs.status, 'failed'), eq(jobs.status, 'leased'))));
+ }
+ }
+
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: `post.${command.input.action}`, targetType: 'post', targetId: command.postId,
+ requestId: command.requestId,
+ metadata: { from: current.state, to: rule.to, version: newVersion, revisionId: current.draftRevisionId },
+ createdAt: now,
+ });
+ });
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ if (command.input.idempotencyKey && await this.findPublicationByKey(command.input.idempotencyKey)) {
+ const post = await this.find(command.workspaceId, command.postId);
+ if (post) return post;
+ }
+ throw error;
+ }
+
+ const transitioned = await this.find(command.workspaceId, command.postId);
+ if (!transitioned) throw new AppError('INTERNAL_FAILURE');
+ return transitioned;
+ }
+
+ async findPublic(workspaceSlug: string, postSlug: string): Promise {
+ const [row] = await this.database.db.select({
+ workspaceName: workspaces.name,
+ slug: posts.slug,
+ title: revisions.title,
+ excerpt: revisions.excerpt,
+ content: revisions.content,
+ revisionId: revisions.id,
+ publishedAt: posts.publishedAt,
+ }).from(posts)
+ .innerJoin(workspaces, eq(workspaces.id, posts.workspaceId))
+ .innerJoin(revisions, and(eq(revisions.id, posts.publishedRevisionId), eq(revisions.workspaceId, posts.workspaceId), eq(revisions.postId, posts.id)))
+ .where(and(eq(workspaces.slug, workspaceSlug), eq(posts.slug, postSlug)))
+ .limit(1);
+ if (!row || !row.publishedAt) return null;
+ return { ...row, publishedAt: row.publishedAt };
+ }
+
+ async runDuePublicationJobs(now: Date, limit = 10): Promise> {
+ const candidates = await this.database.db.select().from(jobs).where(and(
+ eq(jobs.type, 'publish'),
+ lte(jobs.runAt, now),
+ or(eq(jobs.status, 'pending'), and(eq(jobs.status, 'failed'), lt(jobs.attempts, 3)), and(eq(jobs.status, 'leased'), lte(jobs.leaseUntil, now))),
+ )).orderBy(asc(jobs.runAt)).limit(Math.min(Math.max(limit, 1), 100));
+ let claimed = 0;
+ let completed = 0;
+ let failed = 0;
+
+ for (const job of candidates) {
+ const leaseUntil = new Date(now.getTime() + 30_000);
+ const claim = await this.database.db.update(jobs).set({ status: 'leased', leaseUntil, attempts: sql`${jobs.attempts} + 1`, updatedAt: now })
+ .where(and(eq(jobs.id, job.id), eq(jobs.attempts, job.attempts), or(eq(jobs.status, 'pending'), eq(jobs.status, 'failed'), and(eq(jobs.status, 'leased'), lte(jobs.leaseUntil, now)))));
+ if (claim.rowsAffected !== 1) continue;
+ claimed += 1;
+ try {
+ await this.completePublicationJob(job.id, job.workspaceId, job.payload, now);
+ completed += 1;
+ } catch (error) {
+ failed += 1;
+ const code = error instanceof AppError ? error.code : 'INTERNAL_FAILURE';
+ await this.database.db.update(jobs).set({
+ status: 'failed', leaseUntil: null, lastErrorCode: code,
+ runAt: new Date(now.getTime() + Math.min(60_000, 1000 * 2 ** job.attempts)), updatedAt: now,
+ }).where(eq(jobs.id, job.id));
+ }
+ }
+ return { claimed, completed, failed };
+ }
+
+ private async findPublicationByKey(idempotencyKey: string): Promise {
+ const [publication] = await this.database.db.select().from(publications).where(eq(publications.idempotencyKey, idempotencyKey)).limit(1);
+ return publication ?? null;
+ }
+
+ private async completePublicationJob(jobId: string, workspaceId: string, rawPayload: unknown, now: Date): Promise {
+ const payload = publishJobPayloadSchema.parse(rawPayload);
+ await this.database.db.transaction(async (transaction) => {
+ const [publication] = await transaction.select().from(publications)
+ .where(and(eq(publications.id, payload.publicationId), eq(publications.workspaceId, workspaceId), eq(publications.postId, payload.postId), eq(publications.revisionId, payload.revisionId))).limit(1);
+ if (!publication) throw new AppError('NOT_FOUND');
+ if (publication.status === 'published') {
+ await transaction.update(jobs).set({ status: 'completed', leaseUntil: null, lastErrorCode: null, updatedAt: now }).where(eq(jobs.id, jobId));
+ return;
+ }
+ if (publication.status !== 'scheduled') throw new AppError('ILLEGAL_TRANSITION');
+ const [revision] = await transaction.select({ id: revisions.id }).from(revisions)
+ .where(and(eq(revisions.id, payload.revisionId), eq(revisions.workspaceId, workspaceId), eq(revisions.postId, payload.postId))).limit(1);
+ if (!revision) throw new AppError('NOT_FOUND');
+ const update = await transaction.update(posts).set({
+ state: 'Published', publishedRevisionId: payload.revisionId, publishedAt: now,
+ scheduledFor: null, version: sql`${posts.version} + 1`, updatedAt: now,
+ }).where(and(eq(posts.id, payload.postId), eq(posts.workspaceId, workspaceId), eq(posts.state, 'Scheduled')));
+ if (update.rowsAffected !== 1) throw new AppError('ILLEGAL_TRANSITION');
+ await transaction.update(publications).set({ status: 'published', publishedAt: now }).where(eq(publications.id, publication.id));
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId, actorId: publication.createdBy,
+ action: 'post.publish_scheduled', targetType: 'post', targetId: payload.postId,
+ requestId: `job:${jobId}`, metadata: { revisionId: payload.revisionId }, createdAt: now,
+ });
+ await transaction.update(jobs).set({ status: 'completed', leaseUntil: null, lastErrorCode: null, updatedAt: now }).where(eq(jobs.id, jobId));
+ });
+ }
+}
diff --git a/src/modules/editorial/repository.ts b/src/modules/editorial/repository.ts
new file mode 100644
index 0000000..6f88f14
--- /dev/null
+++ b/src/modules/editorial/repository.ts
@@ -0,0 +1,13 @@
+import type { CreatePostInput, JobRunResult, PostDetail, PostSummary, PublicPost, RestoreRevisionInput, RevisionDetail, SavePostInput, TransitionPostInput } from '@/src/modules/editorial/domain';
+
+export interface EditorialRepository {
+ list(workspaceId: string): Promise;
+ find(workspaceId: string, postId: string): Promise;
+ create(command: Readonly<{ workspaceId: string; actorId: string; requestId: string; input: CreatePostInput }>): Promise;
+ save(command: Readonly<{ workspaceId: string; postId: string; actorId: string; requestId: string; input: SavePostInput }>): Promise;
+ listRevisions(workspaceId: string, postId: string): Promise;
+ restore(command: Readonly<{ workspaceId: string; postId: string; actorId: string; requestId: string; input: RestoreRevisionInput }>): Promise;
+ transition(command: Readonly<{ workspaceId: string; postId: string; actorId: string; requestId: string; input: TransitionPostInput }>): Promise;
+ findPublic(workspaceSlug: string, postSlug: string): Promise;
+ runDuePublicationJobs(now: Date, limit?: number): Promise;
+}
diff --git a/src/modules/editorial/service.ts b/src/modules/editorial/service.ts
new file mode 100644
index 0000000..0557a52
--- /dev/null
+++ b/src/modules/editorial/service.ts
@@ -0,0 +1,81 @@
+import { createPostSchema, restoreRevisionSchema, savePostSchema, transitionPostSchema, type JobRunResult, type PostDetail, type PostSummary, type PublicPost, type RevisionDetail } from '@/src/modules/editorial/domain';
+import { TRANSITION_RULES } from '@/src/modules/editorial/workflow';
+import type { EditorialRepository } from '@/src/modules/editorial/repository';
+import type { MembershipContext } from '@/src/modules/identity/domain';
+import { authorize } from '@/src/modules/identity/policy';
+import { getDatabase } from '@/src/platform/db/client';
+import { DrizzleEditorialRepository } from '@/src/modules/editorial/drizzle-repository';
+import { AppError } from '@/src/platform/observability/errors';
+
+export class EditorialService {
+ constructor(private readonly repository: EditorialRepository) {}
+
+ async list(context: MembershipContext): Promise {
+ authorize(context.role, 'workspace.read');
+ return this.repository.list(context.workspaceId);
+ }
+
+ async get(context: MembershipContext, postId: string): Promise {
+ authorize(context.role, 'workspace.read');
+ const post = await this.repository.find(context.workspaceId, postId);
+ if (!post) throw new AppError('NOT_FOUND');
+ return post;
+ }
+
+ async create(context: MembershipContext, input: unknown, requestId: string): Promise {
+ authorize(context.role, 'post.create', { actorId: context.userId, ownerId: context.userId });
+ return this.repository.create({ workspaceId: context.workspaceId, actorId: context.userId, requestId, input: createPostSchema.parse(input) });
+ }
+
+ async save(context: MembershipContext, postId: string, input: unknown, requestId: string): Promise {
+ const existing = await this.repository.find(context.workspaceId, postId);
+ if (!existing) throw new AppError('NOT_FOUND');
+ authorize(context.role, 'post.update', { actorId: context.userId, ownerId: existing.authorId });
+ if (!['Draft', 'ChangesRequested', 'Published'].includes(existing.state)) throw new AppError('ILLEGAL_TRANSITION', { state: existing.state, operation: 'save' });
+ return this.repository.save({ workspaceId: context.workspaceId, postId, actorId: context.userId, requestId, input: savePostSchema.parse(input) });
+ }
+
+ async revisions(context: MembershipContext, postId: string): Promise {
+ authorize(context.role, 'workspace.read');
+ if (!await this.repository.find(context.workspaceId, postId)) throw new AppError('NOT_FOUND');
+ return this.repository.listRevisions(context.workspaceId, postId);
+ }
+
+ async restore(context: MembershipContext, postId: string, input: unknown, requestId: string): Promise {
+ const existing = await this.repository.find(context.workspaceId, postId);
+ if (!existing) throw new AppError('NOT_FOUND');
+ authorize(context.role, 'revision.restore', { actorId: context.userId, ownerId: existing.authorId });
+ if (!['Draft', 'ChangesRequested', 'Published'].includes(existing.state)) throw new AppError('ILLEGAL_TRANSITION', { state: existing.state, operation: 'restore' });
+ return this.repository.restore({ workspaceId: context.workspaceId, postId, actorId: context.userId, requestId, input: restoreRevisionSchema.parse(input) });
+ }
+
+ async transition(context: MembershipContext, postId: string, input: unknown, requestId: string): Promise {
+ const parsed = transitionPostSchema.parse(input);
+ const existing = await this.repository.find(context.workspaceId, postId);
+ if (!existing) throw new AppError('NOT_FOUND');
+ const rule = TRANSITION_RULES[parsed.action];
+ authorize(context.role, rule.permission, { actorId: context.userId, ownerId: existing.authorId });
+ if (parsed.action === 'submit' && existing.content.trim().length === 0) throw new AppError('VALIDATION_FAILED', { field: 'content' });
+ if (parsed.action === 'schedule') {
+ if (!parsed.scheduledFor || new Date(parsed.scheduledFor).getTime() <= Date.now()) throw new AppError('VALIDATION_FAILED', { field: 'scheduledFor' });
+ }
+ return this.repository.transition({ workspaceId: context.workspaceId, postId, actorId: context.userId, requestId, input: parsed });
+ }
+
+ async publicPost(workspaceSlug: string, postSlug: string): Promise {
+ const post = await this.repository.findPublic(workspaceSlug, postSlug);
+ if (!post) throw new AppError('NOT_FOUND');
+ return post;
+ }
+
+ async runDueJobs(now = new Date(), limit = 10): Promise {
+ return this.repository.runDuePublicationJobs(now, limit);
+ }
+}
+
+let editorialService: EditorialService | undefined;
+
+export function getEditorialService(): EditorialService {
+ if (!editorialService) editorialService = new EditorialService(new DrizzleEditorialRepository(getDatabase()));
+ return editorialService;
+}
diff --git a/src/modules/editorial/workflow.ts b/src/modules/editorial/workflow.ts
new file mode 100644
index 0000000..ebe80a4
--- /dev/null
+++ b/src/modules/editorial/workflow.ts
@@ -0,0 +1,27 @@
+import type { Permission } from '@/src/modules/identity/domain';
+import { AppError } from '@/src/platform/observability/errors';
+import type { PostState } from '@/src/modules/editorial/domain';
+
+export const transitionActionValues = ['submit', 'request_changes', 'approve', 'schedule', 'publish', 'archive'] as const;
+export type TransitionAction = (typeof transitionActionValues)[number];
+
+type TransitionRule = Readonly<{
+ from: readonly PostState[];
+ to: PostState;
+ permission: Permission;
+}>;
+
+export const TRANSITION_RULES: Readonly> = {
+ submit: { from: ['Draft', 'ChangesRequested'], to: 'InReview', permission: 'post.submit' },
+ request_changes: { from: ['InReview'], to: 'ChangesRequested', permission: 'review.request_changes' },
+ approve: { from: ['InReview'], to: 'Approved', permission: 'review.approve' },
+ schedule: { from: ['Approved'], to: 'Scheduled', permission: 'post.schedule' },
+ publish: { from: ['Approved', 'Scheduled'], to: 'Published', permission: 'post.publish' },
+ archive: { from: ['Draft', 'InReview', 'ChangesRequested', 'Approved', 'Scheduled', 'Published'], to: 'Archived', permission: 'post.archive' },
+};
+
+export function resolveTransition(state: PostState, action: TransitionAction): TransitionRule {
+ const rule = TRANSITION_RULES[action];
+ if (!rule.from.includes(state)) throw new AppError('ILLEGAL_TRANSITION', { from: state, action });
+ return rule;
+}
diff --git a/src/modules/identity/demo-repository.ts b/src/modules/identity/demo-repository.ts
new file mode 100644
index 0000000..9891dc4
--- /dev/null
+++ b/src/modules/identity/demo-repository.ts
@@ -0,0 +1,9 @@
+export type DemoResetResult = Readonly<{ fixtureVersion: number; resetAt: string }>;
+
+export interface DemoRepository {
+ isDemoWorkspace(workspaceId: string): Promise;
+ findReset(workspaceId: string, idempotencyKey: string): Promise;
+ consumeResetLimit(workspaceId: string, userId: string, now: Date): Promise;
+ reset(workspaceId: string, requestId: string): Promise;
+ recordReset(workspaceId: string, idempotencyKey: string, result: DemoResetResult): Promise;
+}
diff --git a/src/modules/identity/demo-service.ts b/src/modules/identity/demo-service.ts
new file mode 100644
index 0000000..d81183b
--- /dev/null
+++ b/src/modules/identity/demo-service.ts
@@ -0,0 +1,33 @@
+import { z } from 'zod';
+
+import { DrizzleDemoRepository } from '@/src/modules/identity/drizzle-demo-repository';
+import type { DemoRepository, DemoResetResult } from '@/src/modules/identity/demo-repository';
+import type { MembershipContext } from '@/src/modules/identity/domain';
+import { authorize } from '@/src/modules/identity/policy';
+import { getDatabase } from '@/src/platform/db/client';
+import { AppError } from '@/src/platform/observability/errors';
+
+const demoResetSchema = z.object({ idempotencyKey: z.string().trim().min(12).max(120) });
+export type DemoResetResponse = DemoResetResult & Readonly<{ alreadyApplied: boolean }>;
+
+export class DemoService {
+ constructor(private readonly repository: DemoRepository) {}
+
+ async reset(context: MembershipContext, rawInput: unknown, requestId: string): Promise {
+ authorize(context.role, 'demo.reset');
+ const input = demoResetSchema.parse(rawInput);
+ if (!await this.repository.isDemoWorkspace(context.workspaceId)) throw new AppError('FORBIDDEN');
+ const existing = await this.repository.findReset(context.workspaceId, input.idempotencyKey);
+ if (existing) return { ...existing, alreadyApplied: true };
+ await this.repository.consumeResetLimit(context.workspaceId, context.userId, new Date());
+ const result = await this.repository.reset(context.workspaceId, requestId);
+ await this.repository.recordReset(context.workspaceId, input.idempotencyKey, result);
+ return { ...result, alreadyApplied: false };
+ }
+}
+
+let demoService: DemoService | undefined;
+export function getDemoService(): DemoService {
+ if (!demoService) demoService = new DemoService(new DrizzleDemoRepository(getDatabase()));
+ return demoService;
+}
diff --git a/src/modules/identity/demo.ts b/src/modules/identity/demo.ts
new file mode 100644
index 0000000..eaa8153
--- /dev/null
+++ b/src/modules/identity/demo.ts
@@ -0,0 +1,13 @@
+import type { Role } from '@/src/modules/identity/domain';
+
+export const DEMO_WORKSPACE_ID = 'ws-demo';
+export const DEMO_WORKSPACE_SLUG = 'demo';
+export const DEMO_PASSWORD = 'AutoBlogDemo!2026';
+
+export const DEMO_IDENTITIES: readonly Readonly<{ id: string; name: string; email: string; role: Role }>[] = [
+ { id: 'demo-owner', name: 'Olivia Owner', email: 'owner@demo.autoblog.local', role: 'Owner' },
+ { id: 'demo-admin', name: 'Amir Admin', email: 'admin@demo.autoblog.local', role: 'Admin' },
+ { id: 'demo-editor', name: 'Elena Editor', email: 'editor@demo.autoblog.local', role: 'Editor' },
+ { id: 'demo-author', name: 'Avery Author', email: 'author@demo.autoblog.local', role: 'Author' },
+ { id: 'demo-reviewer', name: 'Ravi Reviewer', email: 'reviewer@demo.autoblog.local', role: 'Reviewer' },
+];
diff --git a/src/modules/identity/domain.ts b/src/modules/identity/domain.ts
new file mode 100644
index 0000000..e075489
--- /dev/null
+++ b/src/modules/identity/domain.ts
@@ -0,0 +1,35 @@
+import { z } from 'zod';
+
+export const roleSchema = z.enum(['Owner', 'Admin', 'Editor', 'Author', 'Reviewer']);
+export type Role = z.infer;
+
+export const permissionSchema = z.enum([
+ 'workspace.read',
+ 'post.create',
+ 'post.update',
+ 'post.delete',
+ 'post.submit',
+ 'review.request_changes',
+ 'review.approve',
+ 'post.schedule',
+ 'post.publish',
+ 'post.archive',
+ 'revision.restore',
+ 'media.upload',
+ 'media.delete',
+ 'ai.suggest',
+ 'membership.manage',
+ 'demo.reset',
+ 'jobs.run',
+]);
+export type Permission = z.infer;
+
+export type MembershipContext = Readonly<{
+ workspaceId: string;
+ workspaceSlug: string;
+ workspaceName: string;
+ userId: string;
+ userName: string;
+ userEmail: string;
+ role: Role;
+}>;
diff --git a/src/modules/identity/drizzle-demo-repository.ts b/src/modules/identity/drizzle-demo-repository.ts
new file mode 100644
index 0000000..b296007
--- /dev/null
+++ b/src/modules/identity/drizzle-demo-repository.ts
@@ -0,0 +1,58 @@
+import { and, eq, sql } from 'drizzle-orm';
+
+import type { DemoRepository, DemoResetResult } from '@/src/modules/identity/demo-repository';
+import { DEMO_WORKSPACE_ID } from '@/src/modules/identity/demo';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { idempotencyRecords, rateLimits, workspaces } from '@/src/platform/db/schema';
+import { seedDemo } from '@/src/platform/db/seed';
+import { AppError } from '@/src/platform/observability/errors';
+
+const RESET_LIMIT = 3;
+const RESET_WINDOW_MS = 60 * 60 * 1000;
+
+export class DrizzleDemoRepository implements DemoRepository {
+ constructor(private readonly database: DatabaseContext) {}
+
+ async isDemoWorkspace(workspaceId: string): Promise {
+ const [workspace] = await this.database.db.select({ isDemo: workspaces.isDemo }).from(workspaces).where(eq(workspaces.id, workspaceId)).limit(1);
+ return workspace?.isDemo === true;
+ }
+
+ async findReset(workspaceId: string, idempotencyKey: string): Promise {
+ const [record] = await this.database.db.select({ result: idempotencyRecords.result }).from(idempotencyRecords).where(and(
+ eq(idempotencyRecords.workspaceId, workspaceId), eq(idempotencyRecords.operation, 'demo.reset'), eq(idempotencyRecords.key, idempotencyKey),
+ )).limit(1);
+ if (!record) return null;
+ const result = record.result;
+ return typeof result.fixtureVersion === 'number' && typeof result.resetAt === 'string'
+ ? { fixtureVersion: result.fixtureVersion, resetAt: result.resetAt }
+ : null;
+ }
+
+ async consumeResetLimit(workspaceId: string, userId: string, now: Date): Promise {
+ const windowStartedAt = new Date(Math.floor(now.getTime() / RESET_WINDOW_MS) * RESET_WINDOW_MS);
+ const expiresAt = new Date(windowStartedAt.getTime() + RESET_WINDOW_MS);
+ const key = `demo-reset:${workspaceId}:${userId}`;
+ await this.database.db.transaction(async (transaction) => {
+ const inserted = await transaction.insert(rateLimits).values({ key, windowStartedAt, count: 1, expiresAt }).onConflictDoNothing();
+ if (inserted.rowsAffected === 1) return;
+ const updated = await transaction.update(rateLimits).set({ count: sql`${rateLimits.count} + 1` }).where(and(
+ eq(rateLimits.key, key), eq(rateLimits.windowStartedAt, windowStartedAt), sql`${rateLimits.count} < ${RESET_LIMIT}`,
+ ));
+ if (updated.rowsAffected !== 1) throw new AppError('RATE_LIMITED', { retryAfterSeconds: Math.ceil((expiresAt.getTime() - now.getTime()) / 1000) });
+ });
+ }
+
+ async reset(workspaceId: string, requestId: string): Promise {
+ if (workspaceId !== DEMO_WORKSPACE_ID) throw new AppError('FORBIDDEN');
+ const resetAt = new Date().toISOString();
+ await seedDemo(this.database, { reset: true, requestId });
+ return { fixtureVersion: 1, resetAt };
+ }
+
+ async recordReset(workspaceId: string, idempotencyKey: string, result: DemoResetResult): Promise {
+ await this.database.db.insert(idempotencyRecords).values({
+ workspaceId, operation: 'demo.reset', key: idempotencyKey, result, createdAt: new Date(result.resetAt),
+ }).onConflictDoNothing();
+ }
+}
diff --git a/src/modules/identity/policy.ts b/src/modules/identity/policy.ts
new file mode 100644
index 0000000..fd87bb9
--- /dev/null
+++ b/src/modules/identity/policy.ts
@@ -0,0 +1,35 @@
+import type { Permission, Role } from '@/src/modules/identity/domain';
+import { AppError } from '@/src/platform/observability/errors';
+
+type PermissionRule = Readonly<{ roles: readonly Role[]; ownOnlyFor?: readonly Role[] }>;
+
+export const PERMISSION_MATRIX: Readonly> = {
+ 'workspace.read': { roles: ['Owner', 'Admin', 'Editor', 'Author', 'Reviewer'] },
+ 'post.create': { roles: ['Owner', 'Admin', 'Editor', 'Author'] },
+ 'post.update': { roles: ['Owner', 'Admin', 'Editor', 'Author'], ownOnlyFor: ['Author'] },
+ 'post.delete': { roles: ['Owner', 'Admin', 'Editor'] },
+ 'post.submit': { roles: ['Owner', 'Admin', 'Editor', 'Author'], ownOnlyFor: ['Author'] },
+ 'review.request_changes': { roles: ['Owner', 'Admin', 'Editor', 'Reviewer'] },
+ 'review.approve': { roles: ['Owner', 'Admin', 'Editor', 'Reviewer'] },
+ 'post.schedule': { roles: ['Owner', 'Admin', 'Editor'] },
+ 'post.publish': { roles: ['Owner', 'Admin', 'Editor'] },
+ 'post.archive': { roles: ['Owner', 'Admin', 'Editor'] },
+ 'revision.restore': { roles: ['Owner', 'Admin', 'Editor', 'Author'], ownOnlyFor: ['Author'] },
+ 'media.upload': { roles: ['Owner', 'Admin', 'Editor', 'Author'], ownOnlyFor: ['Author'] },
+ 'media.delete': { roles: ['Owner', 'Admin', 'Editor'] },
+ 'ai.suggest': { roles: ['Owner', 'Admin', 'Editor', 'Author', 'Reviewer'] },
+ 'membership.manage': { roles: ['Owner', 'Admin'] },
+ 'demo.reset': { roles: ['Owner'] },
+ 'jobs.run': { roles: ['Owner', 'Admin'] },
+};
+
+export function can(role: Role, permission: Permission, context?: Readonly<{ actorId: string; ownerId?: string }>): boolean {
+ const rule = PERMISSION_MATRIX[permission];
+ if (!rule.roles.includes(role)) return false;
+ if (rule.ownOnlyFor?.includes(role)) return Boolean(context?.ownerId && context.ownerId === context.actorId);
+ return true;
+}
+
+export function authorize(role: Role, permission: Permission, context?: Readonly<{ actorId: string; ownerId?: string }>): void {
+ if (!can(role, permission, context)) throw new AppError('FORBIDDEN');
+}
diff --git a/src/modules/media/cleanup-worker.ts b/src/modules/media/cleanup-worker.ts
new file mode 100644
index 0000000..c1dd6ff
--- /dev/null
+++ b/src/modules/media/cleanup-worker.ts
@@ -0,0 +1,60 @@
+import { and, asc, eq, lt, lte, or, sql } from 'drizzle-orm';
+import { z } from 'zod';
+
+import type { JobRunResult } from '@/src/modules/editorial/domain';
+import type { MediaProvider } from '@/src/modules/media/provider';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { jobs, mediaAssets } from '@/src/platform/db/schema';
+import { AppError } from '@/src/platform/observability/errors';
+
+const cleanupPayloadSchema = z.object({ storageKey: z.string().min(1), assetId: z.string().min(1).optional() });
+
+export class MediaCleanupWorker {
+ constructor(private readonly database: DatabaseContext, private readonly provider: MediaProvider) {}
+
+ async run(now = new Date(), limit = 20): Promise {
+ const candidates = await this.database.db.select().from(jobs).where(and(
+ eq(jobs.type, 'media_cleanup'), lte(jobs.runAt, now),
+ or(eq(jobs.status, 'pending'), and(eq(jobs.status, 'failed'), lt(jobs.attempts, 3)), and(eq(jobs.status, 'leased'), lte(jobs.leaseUntil, now))),
+ )).orderBy(asc(jobs.runAt)).limit(Math.min(Math.max(limit, 1), 100));
+ let claimed = 0; let completed = 0; let failed = 0;
+ for (const job of candidates) {
+ const claim = await this.database.db.update(jobs).set({
+ status: 'leased', leaseUntil: new Date(now.getTime() + 30_000), attempts: sql`${jobs.attempts} + 1`, updatedAt: now,
+ }).where(and(eq(jobs.id, job.id), eq(jobs.attempts, job.attempts), or(eq(jobs.status, 'pending'), eq(jobs.status, 'failed'), and(eq(jobs.status, 'leased'), lte(jobs.leaseUntil, now)))));
+ if (claim.rowsAffected !== 1) continue;
+ claimed += 1;
+ try {
+ const payload = cleanupPayloadSchema.parse(job.payload);
+ if (payload.assetId) {
+ const [asset] = await this.database.db.select({ status: mediaAssets.status, storageKey: mediaAssets.storageKey }).from(mediaAssets)
+ .where(and(eq(mediaAssets.workspaceId, job.workspaceId), eq(mediaAssets.id, payload.assetId), eq(mediaAssets.storageKey, payload.storageKey))).limit(1);
+ if (asset?.status === 'active') throw new AppError('ILLEGAL_TRANSITION');
+ }
+ const controller = new AbortController();
+ let timeout: ReturnType | undefined;
+ try {
+ await Promise.race([
+ this.provider.delete(payload.storageKey, controller.signal),
+ new Promise((_resolve, reject) => {
+ timeout = setTimeout(() => { controller.abort(); reject(new AppError('PROVIDER_UNAVAILABLE')); }, 8_000);
+ }),
+ ]);
+ } finally { if (timeout) clearTimeout(timeout); }
+ await this.database.db.transaction(async (transaction) => {
+ if (payload.assetId) await transaction.update(mediaAssets).set({ status: 'deleted', updatedAt: now })
+ .where(and(eq(mediaAssets.workspaceId, job.workspaceId), eq(mediaAssets.id, payload.assetId)));
+ await transaction.update(jobs).set({ status: 'completed', leaseUntil: null, lastErrorCode: null, updatedAt: now }).where(eq(jobs.id, job.id));
+ });
+ completed += 1;
+ } catch (error) {
+ failed += 1;
+ await this.database.db.update(jobs).set({
+ status: 'failed', leaseUntil: null, lastErrorCode: error instanceof AppError ? error.code : 'PROVIDER_UNAVAILABLE',
+ runAt: new Date(now.getTime() + Math.min(60_000, 1000 * 2 ** job.attempts)), updatedAt: now,
+ }).where(eq(jobs.id, job.id));
+ }
+ }
+ return { claimed, completed, failed };
+ }
+}
diff --git a/src/modules/media/database-provider.ts b/src/modules/media/database-provider.ts
new file mode 100644
index 0000000..6c5277e
--- /dev/null
+++ b/src/modules/media/database-provider.ts
@@ -0,0 +1,27 @@
+import { eq } from 'drizzle-orm';
+
+import type { MediaProvider } from '@/src/modules/media/provider';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { mediaObjects } from '@/src/platform/db/schema';
+
+export class DatabaseMediaProvider implements MediaProvider {
+ constructor(private readonly database: DatabaseContext) {}
+
+ async put(storageKey: string, data: Buffer, signal: AbortSignal): Promise {
+ signal.throwIfAborted();
+ await this.database.db.insert(mediaObjects).values({ storageKey, data, createdAt: new Date() });
+ signal.throwIfAborted();
+ }
+
+ async get(storageKey: string, signal: AbortSignal): Promise {
+ signal.throwIfAborted();
+ const [object] = await this.database.db.select({ data: mediaObjects.data }).from(mediaObjects).where(eq(mediaObjects.storageKey, storageKey)).limit(1);
+ signal.throwIfAborted();
+ return object?.data ?? null;
+ }
+
+ async delete(storageKey: string, signal: AbortSignal): Promise {
+ signal.throwIfAborted();
+ await this.database.db.delete(mediaObjects).where(eq(mediaObjects.storageKey, storageKey));
+ }
+}
diff --git a/src/modules/media/domain.ts b/src/modules/media/domain.ts
new file mode 100644
index 0000000..7a2ffd5
--- /dev/null
+++ b/src/modules/media/domain.ts
@@ -0,0 +1,79 @@
+import sharp from 'sharp';
+import { z } from 'zod';
+import { createHash } from 'node:crypto';
+
+import { AppError } from '@/src/platform/observability/errors';
+
+export const MEDIA_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'] as const;
+export const MEDIA_MAX_DIMENSION = 4096;
+export const MEDIA_MAX_PIXELS = 16_000_000;
+
+export const mediaUploadFieldsSchema = z.object({
+ postId: z.string().min(1).max(120),
+ replaceAssetId: z.string().min(1).max(120).optional(),
+ altText: z.string().trim().max(500).default(''),
+});
+
+export type MediaAsset = Readonly<{
+ id: string;
+ workspaceId: string;
+ postId: string | null;
+ status: 'pending' | 'active' | 'replaced' | 'deleted' | 'cleanup_pending';
+ fileName: string;
+ mimeType: string;
+ byteSize: number;
+ width: number;
+ height: number;
+ altText: string;
+ createdBy: string;
+ replacesAssetId: string | null;
+ createdAt: Date;
+}>;
+
+export type VerifiedImage = Readonly<{
+ data: Buffer;
+ fileName: string;
+ mimeType: (typeof MEDIA_MIME_TYPES)[number];
+ byteSize: number;
+ width: number;
+ height: number;
+ checksum: string;
+}>;
+
+const FORMAT_MIME: Readonly> = {
+ jpeg: 'image/jpeg',
+ png: 'image/png',
+ webp: 'image/webp',
+};
+
+export function safeFileName(name: string): string {
+ const leaf = name.replaceAll('\\', '/').split('/').at(-1) ?? 'upload';
+ const normalized = leaf.normalize('NFKC').replace(/[^a-zA-Z0-9._-]+/gu, '-').replace(/^-+|-+$/gu, '').slice(0, 120);
+ return normalized || 'upload';
+}
+
+export async function verifyImage(input: Readonly<{ data: Buffer; fileName: string; declaredMimeType: string; maxBytes: number }>): Promise {
+ if (input.data.byteLength === 0 || input.data.byteLength > input.maxBytes) throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'size' });
+ if (!MEDIA_MIME_TYPES.includes(input.declaredMimeType as (typeof MEDIA_MIME_TYPES)[number])) throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'mime' });
+ try {
+ const metadata = await sharp(input.data, { failOn: 'warning', limitInputPixels: MEDIA_MAX_PIXELS }).metadata();
+ const detectedMime = metadata.format ? FORMAT_MIME[metadata.format] : undefined;
+ if (!detectedMime || detectedMime !== input.declaredMimeType) throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'content_type' });
+ if (!metadata.width || !metadata.height || metadata.width > MEDIA_MAX_DIMENSION || metadata.height > MEDIA_MAX_DIMENSION || metadata.width * metadata.height > MEDIA_MAX_PIXELS) {
+ throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'dimensions' });
+ }
+ const checksum = createHash('sha256').update(input.data).digest('hex');
+ return {
+ data: input.data,
+ fileName: safeFileName(input.fileName),
+ mimeType: detectedMime,
+ byteSize: input.data.byteLength,
+ width: metadata.width,
+ height: metadata.height,
+ checksum,
+ };
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ throw new AppError('VALIDATION_FAILED', { field: 'file', reason: 'decode' });
+ }
+}
diff --git a/src/modules/media/drizzle-repository.ts b/src/modules/media/drizzle-repository.ts
new file mode 100644
index 0000000..906e077
--- /dev/null
+++ b/src/modules/media/drizzle-repository.ts
@@ -0,0 +1,111 @@
+import { and, desc, eq } from 'drizzle-orm';
+
+import type { MediaAsset } from '@/src/modules/media/domain';
+import type { MediaRepository } from '@/src/modules/media/repository';
+import type { DatabaseContext } from '@/src/platform/db/client';
+import { auditEvents, jobs, mediaAssets, posts } from '@/src/platform/db/schema';
+import { AppError } from '@/src/platform/observability/errors';
+
+type MediaRow = typeof mediaAssets.$inferSelect;
+
+function toAsset(row: MediaRow): MediaAsset {
+ return {
+ id: row.id, workspaceId: row.workspaceId, postId: row.postId, status: row.status,
+ fileName: row.fileName, mimeType: row.mimeType, byteSize: row.byteSize,
+ width: row.width, height: row.height, altText: row.altText, createdBy: row.createdBy,
+ replacesAssetId: row.replacesAssetId, createdAt: row.createdAt,
+ };
+}
+
+export class DrizzleMediaRepository implements MediaRepository {
+ constructor(private readonly database: DatabaseContext) {}
+
+ async findPostOwner(workspaceId: string, postId: string): Promise {
+ const [post] = await this.database.db.select({ authorId: posts.authorId }).from(posts)
+ .where(and(eq(posts.workspaceId, workspaceId), eq(posts.id, postId))).limit(1);
+ return post?.authorId ?? null;
+ }
+
+ async list(workspaceId: string, postId: string): Promise {
+ const rows = await this.database.db.select().from(mediaAssets)
+ .where(and(eq(mediaAssets.workspaceId, workspaceId), eq(mediaAssets.postId, postId), eq(mediaAssets.status, 'active')))
+ .orderBy(desc(mediaAssets.createdAt));
+ return rows.map(toAsset);
+ }
+
+ async find(workspaceId: string, assetId: string): Promise<(MediaAsset & { storageKey: string }) | null> {
+ const [row] = await this.database.db.select().from(mediaAssets)
+ .where(and(eq(mediaAssets.workspaceId, workspaceId), eq(mediaAssets.id, assetId))).limit(1);
+ return row ? { ...toAsset(row), storageKey: row.storageKey } : null;
+ }
+
+ async finalize(command: Parameters[0]): Promise {
+ const assetId = crypto.randomUUID();
+ const now = new Date();
+ await this.database.db.transaction(async (transaction) => {
+ let replaced: MediaRow | undefined;
+ if (command.replaceAssetId) {
+ [replaced] = await transaction.select().from(mediaAssets).where(and(
+ eq(mediaAssets.id, command.replaceAssetId), eq(mediaAssets.workspaceId, command.workspaceId),
+ eq(mediaAssets.postId, command.postId), eq(mediaAssets.status, 'active'),
+ )).limit(1);
+ if (!replaced) throw new AppError('NOT_FOUND');
+ await transaction.update(mediaAssets).set({ status: 'replaced', updatedAt: now }).where(eq(mediaAssets.id, replaced.id));
+ }
+ await transaction.insert(mediaAssets).values({
+ id: assetId, workspaceId: command.workspaceId, postId: command.postId, status: 'active',
+ storageKey: command.storageKey, fileName: command.image.fileName, mimeType: command.image.mimeType,
+ byteSize: command.image.byteSize, width: command.image.width, height: command.image.height,
+ checksum: command.image.checksum, altText: command.altText, createdBy: command.actorId,
+ replacesAssetId: replaced?.id, createdAt: now, updatedAt: now,
+ });
+ if (replaced) await transaction.insert(jobs).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, type: 'media_cleanup',
+ payload: { assetId: replaced.id, storageKey: replaced.storageKey }, status: 'pending', runAt: now,
+ idempotencyKey: `media-cleanup:${replaced.id}`, createdAt: now, updatedAt: now,
+ });
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: replaced ? 'media.replaced' : 'media.uploaded', targetType: 'media', targetId: assetId,
+ requestId: command.requestId, metadata: { postId: command.postId, replacedAssetId: replaced?.id ?? null }, createdAt: now,
+ });
+ });
+ const [asset] = await this.database.db.select().from(mediaAssets)
+ .where(and(eq(mediaAssets.workspaceId, command.workspaceId), eq(mediaAssets.id, assetId))).limit(1);
+ if (!asset) throw new AppError('INTERNAL_FAILURE');
+ return toAsset(asset);
+ }
+
+ async markForDeletion(command: Parameters[0]): Promise {
+ const now = new Date();
+ await this.database.db.transaction(async (transaction) => {
+ const [asset] = await transaction.select().from(mediaAssets).where(and(eq(mediaAssets.workspaceId, command.workspaceId), eq(mediaAssets.id, command.assetId))).limit(1);
+ if (!asset) throw new AppError('NOT_FOUND');
+ if (asset.status === 'deleted' || asset.status === 'cleanup_pending') return;
+ await transaction.update(mediaAssets).set({ status: 'cleanup_pending', updatedAt: now }).where(eq(mediaAssets.id, asset.id));
+ await transaction.insert(jobs).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, type: 'media_cleanup',
+ payload: { assetId: asset.id, storageKey: asset.storageKey }, status: 'pending', runAt: now,
+ idempotencyKey: `media-delete:${asset.id}`, createdAt: now, updatedAt: now,
+ }).onConflictDoNothing();
+ await transaction.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId: command.workspaceId, actorId: command.actorId,
+ action: 'media.deleted', targetType: 'media', targetId: asset.id,
+ requestId: command.requestId, metadata: { postId: asset.postId }, createdAt: now,
+ });
+ });
+ }
+
+ async enqueueOrphanCleanup(workspaceId: string, storageKey: string, requestId: string): Promise {
+ const now = new Date();
+ await this.database.db.insert(jobs).values({
+ id: crypto.randomUUID(), workspaceId, type: 'media_cleanup', payload: { storageKey },
+ status: 'pending', runAt: now, idempotencyKey: `media-orphan:${storageKey}`,
+ createdAt: now, updatedAt: now,
+ }).onConflictDoNothing();
+ await this.database.db.insert(auditEvents).values({
+ id: crypto.randomUUID(), workspaceId, actorId: null, action: 'media.cleanup_queued',
+ targetType: 'media_object', targetId: storageKey, requestId, metadata: {}, createdAt: now,
+ });
+ }
+}
diff --git a/src/modules/media/provider.ts b/src/modules/media/provider.ts
new file mode 100644
index 0000000..d1be2c0
--- /dev/null
+++ b/src/modules/media/provider.ts
@@ -0,0 +1,5 @@
+export interface MediaProvider {
+ put(storageKey: string, data: Buffer, signal: AbortSignal): Promise;
+ get(storageKey: string, signal: AbortSignal): Promise;
+ delete(storageKey: string, signal: AbortSignal): Promise;
+}
diff --git a/src/modules/media/repository.ts b/src/modules/media/repository.ts
new file mode 100644
index 0000000..0ea6d14
--- /dev/null
+++ b/src/modules/media/repository.ts
@@ -0,0 +1,13 @@
+import type { MediaAsset, VerifiedImage } from '@/src/modules/media/domain';
+
+export interface MediaRepository {
+ findPostOwner(workspaceId: string, postId: string): Promise;
+ list(workspaceId: string, postId: string): Promise;
+ find(workspaceId: string, assetId: string): Promise<(MediaAsset & { storageKey: string }) | null>;
+ finalize(command: Readonly<{
+ workspaceId: string; postId: string; actorId: string; requestId: string; storageKey: string;
+ image: VerifiedImage; altText: string; replaceAssetId?: string;
+ }>): Promise;
+ markForDeletion(command: Readonly<{ workspaceId: string; assetId: string; actorId: string; requestId: string }>): Promise;
+ enqueueOrphanCleanup(workspaceId: string, storageKey: string, requestId: string): Promise;
+}
diff --git a/src/modules/media/service.ts b/src/modules/media/service.ts
new file mode 100644
index 0000000..8a1ed08
--- /dev/null
+++ b/src/modules/media/service.ts
@@ -0,0 +1,100 @@
+import { DrizzleMediaRepository } from '@/src/modules/media/drizzle-repository';
+import { DatabaseMediaProvider } from '@/src/modules/media/database-provider';
+import { mediaUploadFieldsSchema, verifyImage, type MediaAsset } from '@/src/modules/media/domain';
+import type { MediaProvider } from '@/src/modules/media/provider';
+import type { MediaRepository } from '@/src/modules/media/repository';
+import type { MembershipContext } from '@/src/modules/identity/domain';
+import { authorize } from '@/src/modules/identity/policy';
+import { getEnvironment } from '@/src/platform/config/env';
+import { getDatabase } from '@/src/platform/db/client';
+import { AppError } from '@/src/platform/observability/errors';
+
+const PROVIDER_TIMEOUT_MS = 8_000;
+
+async function providerOperation(operation: (signal: AbortSignal) => Promise, timeoutMs = PROVIDER_TIMEOUT_MS): Promise {
+ const controller = new AbortController();
+ let timeout: ReturnType | undefined;
+ try {
+ return await Promise.race([
+ operation(controller.signal),
+ new Promise((_resolve, reject) => {
+ timeout = setTimeout(() => { controller.abort(); reject(new AppError('PROVIDER_UNAVAILABLE')); }, timeoutMs);
+ }),
+ ]);
+ } catch (error) {
+ if (error instanceof AppError) throw error;
+ throw new AppError('PROVIDER_UNAVAILABLE', undefined, error);
+ } finally {
+ if (timeout) clearTimeout(timeout);
+ }
+}
+
+export class MediaService {
+ constructor(
+ private readonly repository: MediaRepository,
+ private readonly provider: MediaProvider,
+ private readonly maxBytes: number,
+ ) {}
+
+ async list(context: MembershipContext, postId: string): Promise {
+ authorize(context.role, 'workspace.read');
+ if (!await this.repository.findPostOwner(context.workspaceId, postId)) throw new AppError('NOT_FOUND');
+ return this.repository.list(context.workspaceId, postId);
+ }
+
+ async upload(context: MembershipContext, raw: Readonly<{
+ postId: unknown; replaceAssetId?: unknown; altText?: unknown; fileName: string;
+ declaredMimeType: string; data: Buffer;
+ }>, requestId: string): Promise {
+ const fields = mediaUploadFieldsSchema.parse({ postId: raw.postId, replaceAssetId: raw.replaceAssetId || undefined, altText: raw.altText });
+ const ownerId = await this.repository.findPostOwner(context.workspaceId, fields.postId);
+ if (!ownerId) throw new AppError('NOT_FOUND');
+ authorize(context.role, 'media.upload', { actorId: context.userId, ownerId });
+ const image = await verifyImage({ data: raw.data, fileName: raw.fileName, declaredMimeType: raw.declaredMimeType, maxBytes: this.maxBytes });
+ const storageKey = `${context.workspaceId}/${crypto.randomUUID()}`;
+ try {
+ await providerOperation((signal) => this.provider.put(storageKey, image.data, signal));
+ } catch (error) {
+ await this.repository.enqueueOrphanCleanup(context.workspaceId, storageKey, requestId);
+ throw error;
+ }
+ try {
+ return await this.repository.finalize({
+ workspaceId: context.workspaceId, postId: fields.postId, actorId: context.userId,
+ requestId, storageKey, image, altText: fields.altText,
+ ...(fields.replaceAssetId ? { replaceAssetId: fields.replaceAssetId } : {}),
+ });
+ } catch (error) {
+ try {
+ await providerOperation((signal) => this.provider.delete(storageKey, signal));
+ } catch {
+ await this.repository.enqueueOrphanCleanup(context.workspaceId, storageKey, requestId);
+ }
+ throw error;
+ }
+ }
+
+ async read(context: MembershipContext, assetId: string): Promise> {
+ authorize(context.role, 'workspace.read');
+ const asset = await this.repository.find(context.workspaceId, assetId);
+ if (!asset || asset.status !== 'active') throw new AppError('NOT_FOUND');
+ const data = await providerOperation((signal) => this.provider.get(asset.storageKey, signal));
+ if (!data) throw new AppError('NOT_FOUND');
+ return { asset, data };
+ }
+
+ async delete(context: MembershipContext, assetId: string, requestId: string): Promise