diff --git a/.github/workflows/studio-e2e.yml b/.github/workflows/studio-e2e.yml new file mode 100644 index 000000000..12e8263bf --- /dev/null +++ b/.github/workflows/studio-e2e.yml @@ -0,0 +1,94 @@ +name: studio-e2e + +# Run on every push and on PRs touching Studio source or its E2E specs. +# The mocked `studio` Playwright project does NOT require a Django backend or +# Postgres — it intercepts all API calls at the Playwright route level. +on: + push: + paths: + - "apps/studio/**" + - "packages/e2e/tests/studio/**" + - "packages/e2e/helpers/studio.ts" + - "packages/e2e/playwright.config.ts" + - ".github/workflows/studio-e2e.yml" + pull_request: + paths: + - "apps/studio/**" + - "packages/e2e/tests/studio/**" + - "packages/e2e/helpers/studio.ts" + - "packages/e2e/playwright.config.ts" + - ".github/workflows/studio-e2e.yml" + workflow_dispatch: + +permissions: + contents: read + +jobs: + studio-e2e: + runs-on: ubuntu-latest + timeout-minutes: 20 + + steps: + - uses: actions/checkout@v4 + with: + submodules: false + + - uses: actions/setup-node@v4 + with: + node-version: 22.x + cache: "npm" + cache-dependency-path: apps/studio/package-lock.json + + # Install Studio app dependencies (standalone lockfile, no turborepo). + - name: Install Studio deps + working-directory: apps/studio + run: npm ci + + # Build the Studio app so the dev server can serve the compiled output. + # `vite build` produces apps/studio/dist (client) + .output/server (SSR). + - name: Build Studio + working-directory: apps/studio + run: npm run build + + # Install Playwright + Chromium only (mocked suite only needs one browser). + - name: Install Playwright browsers + working-directory: packages/e2e + run: | + npm ci + npx playwright install chromium --with-deps + + # Start the Studio dev server in the background and wait for it. + # We use `npm run dev` (vite dev) instead of the built server so hot-reload + # is available; the mocked suite doesn't care which mode the server is in. + - name: Start Studio dev server + working-directory: apps/studio + run: npm run dev & + env: + PORT: 3003 + + - name: Wait for Studio to be ready + run: | + for i in $(seq 1 30); do + curl -sf http://localhost:3003 && break || true + echo "Waiting for Studio ($i/30)..." + sleep 2 + done + curl -sf http://localhost:3003 || (echo "Studio never became ready" && exit 1) + + # Run the mocked `studio` Playwright project. + # No KARRIO_LIVE or Django API needed — all API calls are intercepted. + - name: Run Studio E2E (mocked) + working-directory: packages/e2e + run: npx playwright test --project=studio --reporter=github,html + env: + KARRIO_STUDIO_URL: http://localhost:3003 + CI: "true" + + # Upload the HTML report so failures are inspectable via the Actions UI. + - name: Upload Playwright report + if: always() + uses: actions/upload-artifact@v4 + with: + name: studio-playwright-report + path: packages/e2e/playwright-report/ + retention-days: 14 diff --git a/.gitignore b/.gitignore index 874687ab2..9726ac7dc 100644 --- a/.gitignore +++ b/.gitignore @@ -157,3 +157,6 @@ htmlcov/ # Temporary files *.tmp *.backup + +# Claude Code harness-managed temporary agent worktrees +.claude/worktrees/ diff --git a/PRDs/KARRIO_STUDIO.md b/PRDs/KARRIO_STUDIO.md new file mode 100644 index 000000000..e1bedb8b7 --- /dev/null +++ b/PRDs/KARRIO_STUDIO.md @@ -0,0 +1,331 @@ +# Karrio Studio — Full-Stack TanStack Start Migration + +| Field | Value | +|-------|-------| +| Project | Karrio | +| Version | 1.0 | +| Date | 2026-05-29 | +| Status | Planning | +| Owner | Studio Program (dan@karrio.io) | +| Type | Architecture / Enhancement / Migration | +| Reference | [AGENTS.md](../AGENTS.md), design handoff `design_handoff_karrio_studio/` | +| Linear | Project "Karrio Studio" (tracked; agents/subagents = issues) | + +--- + +## Table of Contents + +1. [Executive Summary](#executive-summary) +2. [Open Questions & Decisions](#open-questions--decisions) +3. [Problem Statement](#problem-statement) +4. [Goals & Success Criteria](#goals--success-criteria) +5. [Existing Code Analysis](#existing-code-analysis) +6. [Technical Design](#technical-design) +7. [Modes, Screens & Feature Matrix](#modes-screens--feature-matrix) +8. [New Features: Carrier Integration, Editor, Agents & MCP](#new-features) +9. [Agent / Subagent Orchestration Plan (Linear)](#agent--subagent-orchestration-plan-linear) +10. [Implementation Plan](#implementation-plan) +11. [Testing Strategy (Playwright)](#testing-strategy-playwright) +12. [Risk Assessment](#risk-assessment) +13. [Migration & Rollback](#migration--rollback) + +--- + +## Executive Summary + +Karrio Studio replaces the Next.js dashboard (`apps/dashboard`) with a full-stack +**TanStack Start** application (`apps/studio`) that reimagines Karrio as a modular, +agent-friendly "studio" — a WordPress-like experience for managing shipping +operations, carrier plugins, commerce apps, and a developer platform from one +interface. + +The IA is organized around three **modes** — **Ship**, **Build**, **Govern** — driven +by a collapsible sidebar. Studio migrates every dashboard feature, adds **self-editable +/ customizable** app surfaces (theme/density/layout tweaks, a visual editor), and +introduces three net-new capability areas: **agent-first plugin/carrier integration +(Editor)**, **MCP server management**, and an **AI Assistant** woven through the app. + +Decisions locked with the owner: + +| Decision | Choice | +|----------|--------| +| App strategy | **New app `apps/studio`**, phased cutover; `apps/dashboard` stays live until parity | +| Repo coupling | **Standalone app** — `apps/studio` is excluded from the turborepo workspaces, has its own `package-lock.json`/`node_modules`, and depends on **no** `@karrio/*` packages. Moves fast, avoids monorepo dependency/version conflicts, and keeps it out of root `npm ci`/`turbo build`. | +| Data layer | **Pure Karrio backend passthrough** — ALL state (shipping data AND Studio-native state) persists on the Karrio backend via GraphQL/REST. Studio owns **no** database. Accessed through Studio's **own** fetch-based client + local types (not `@karrio/hooks`/`@karrio/types`). | +| UI | **shadcn/ui + Tailwind CSS** (not `@karrio/ui`). Enterprise theme tokens in `globals.css`; bespoke shell keeps its ported CSS. | +| Session 1+ scope | Linear project + agent plan, this PRD, the standalone `apps/studio` foundation + decoupled data layer + Playwright harness, then feature screens. | + +## Open Questions & Decisions + +| # | Question | Decision | Rationale | +|---|----------|----------|-----------| +| 1 | Replace dashboard in place or build new? | Build `apps/studio` new, deprecate dashboard at parity | Keeps production dashboard running during the multi-phase migration | +| 2 | Where does Studio-native state live? | **Karrio backend passthrough** — customization, agent, and MCP state persist as Karrio metafields/workspace-config (namespaced `studio.*`); shipping data via the Karrio API. Studio owns no DB. | Single source of truth; Studio stays a thin client; no separate DB to operate. Namespaced metafields avoid backend migrations to ship. | +| 3 | Auth model | TanStack Start server functions proxy Karrio auth (JWT) + httpOnly session cookie; reuse Karrio `mutation token_auth`/refresh | Server-side session beats the dashboard's client NextAuth for SSR + security | +| 4 | Component/UI strategy | **Build on shadcn/ui + Tailwind** in Studio. Do **not** depend on `@karrio/ui` (Next/bulma-coupled). | Studio aesthetic is distinct (sharp, dense, dark-default); shadcn+Tailwind is framework-agnostic and fast. | +| 5 | Data/hooks strategy | **Build a fresh, Next-free Karrio client + TanStack Query hooks inside Studio** (`src/lib/karrio/*`) with local types. Do **not** reuse `@karrio/hooks` (`"use client"` + NextAuth) or `@karrio/lib` (`next-runtime-env`, `next-auth`) or `@karrio/types` (workspace dep). | Standalone app can't use workspace deps; a clean fetch client avoids NextAuth coupling entirely and is simpler to reason about. | + +## Problem Statement + +The current dashboard is a Next.js app composed of many `@karrio/*` packages. It is +feature-rich but: (a) its visual language is generic, (b) it has no first-class +story for AI agents / MCP / self-customization, and (c) carrier/plugin integration +is a developer-CLI task, not an in-product experience. The Studio vision unifies +operations, extensibility, and governance with an enterprise-grade, agent-native UX. + +## Goals & Success Criteria + +| Goal | Success Criteria | +|------|------------------| +| Feature parity with dashboard | Every Ship/Build/Govern screen below implemented and wired to live Karrio APIs | +| Pixel-faithful enterprise UI | Design tokens from `styles.css` ported; sharp corners, 1px borders, dark-default, light theme | +| Full-stack TanStack Start | SSR routing, server functions, server-side auth/session, forms (TanStack Form), monitoring | +| New: Carrier/plugin Editor | Agent-first 3-pane IDE; scaffold + edit connectors via `@karrio/app-store`/SDK | +| New: MCP management | Start/stop server, tools table, install snippets, client + invocation monitoring (ref `packages/mcp`, `PRDs/KARRIO_MCP_SERVER.md`) | +| New: Agents | AI Assistant chat + agent sessions/runs, persisted via Karrio metafields (`studio.agents.*`) | +| Self-editable app | Theme/accent/density/font + layout customization persisted per user/org | +| Full test coverage | Playwright spec per feature, run against live Karrio GraphQL+REST | +| Tracked in Linear | Project with epics/issues mirroring the agent plan below | + +## Existing Code Analysis + +Because Studio is **standalone** (no `@karrio/*` deps), these are studied as +**reference/patterns**, not imported: + +| Asset | Location | Use | +|-------|----------|-----| +| Karrio API client | `@karrio/types` `KarrioClient`, `packages/hooks/karrio.tsx` | Reference for REST/GraphQL shapes; Studio ships its **own** fetch client (`src/lib/karrio/client.ts`) + local types | +| Data hooks (~50) | `packages/hooks/*` (shipment, tracker, order, pickup, connections, apps, webhooks, api-keys, admin-*, workflows…) | Reference for query/endpoint patterns; reimplemented Next-free in `src/lib/karrio/hooks.ts` | +| Endpoint/auth semantics | `@karrio/lib`, dashboard auth | Reference for `token_auth`, headers (`x-org-id`, `x-test-mode`); reimplemented in `src/server/auth.ts` + `src/lib/karrio/*` | +| App store / plugins | `packages/app-store`, `packages/mcp`, `plugins/` | Power Apps/Plugins/MCP screens via the Karrio API | +| MCP server | `packages/mcp`, `PRDs/KARRIO_MCP_SERVER.md`, `SPRINT_MCP.md` | Back the MCP management screen | +| Playwright harness | `packages/e2e` (config, `helpers/`, setup) | Extended with a `studio` project + per-feature specs | +| Design handoff | `design_handoff_karrio_studio/prototype/studio/*` | Source of truth for tokens, IA, screen layouts, data shapes | + +**Key point:** Studio depends on **no** `@karrio/*` package. Its session lives in +a small framework-agnostic context (`src/lib/karrio/session.tsx`); data hooks read +a per-request `KarrioCtx` (base URL + token + org + test-mode). All state — shipping +data AND Studio-native state — is persisted on the Karrio backend +(metafields/workspace-config, `studio.*`) via the Studio client and server functions. + +## Technical Design + +### Architecture overview + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ apps/studio (TanStack Start) │ +│ │ +│ ┌────────────┐ ┌──────────────────────────────────────────────┐ │ +│ │ Router │ │ App Shell (CSS grid: [sidebar] [main]) │ │ +│ │ (file-based │──▶│ Sidebar: workspace · Mode(Ship/Build/Govern) │ │ +│ │ routes, │ │ · nav · user footer │ │ +│ │ SSR) │ │ Topbar: ⌘K · test-mode · theme · workbench │ │ +│ └────────────┘ │ · app launcher · create │ │ +│ │ │ Page: routed screen (internal scroll) │ │ +│ │ └──────────────────────────────────────────────┘ │ +│ ▼ │ +│ ┌──────────────────────┐ ┌──────────────────────────────┐ │ +│ │ Server Functions │ │ Client islands │ │ +│ │ - auth/session (JWT) │ │ - @karrio/hooks (TanStack Q) │ │ +│ │ - SSR data loaders │ │ - forms (TanStack Form) │ │ +│ │ - agent/MCP proxy │ │ - sheets/overlays/palette │ │ +│ └─────────┬─────────────┘ └───────────────┬──────────────┘ │ +│ │ │ │ +└────────────┼───────────────────────────────────────┼─────────────────────┘ + └───────────────────┬───────────────────┘ + │ (single source of truth — no Studio DB) + ┌──────────▼───────────────────────────────┐ + │ Karrio Backend │ + │ - GraphQL (/graphql) · REST (/v1/*) │ + │ - auth (token_auth) │ + │ - metafields / workspace_config: │ + │ studio.customization · studio.agents │ + │ · studio.mcp (Studio-native state) │ + │ - MCP server (packages/mcp + Django) │ + └────────────────────────────────────────────┘ +``` + +### Auth & session sequence + +``` +Browser Studio server fn Karrio GraphQL Karrio meta + │ POST /login (email,pw) │ │ │ + ├─────────────────────────▶│ mutation token_auth│ │ + │ ├───────────────────▶│ │ + │ │◀── access+refresh ──┤ │ + │ │ set httpOnly cookie │ │ + │◀── 302 → /home ──────────┤ │ │ + │ (SSR pages read cookie → inject token into KarrioClient + hooks) │ +``` + +### Tech stack + +| Concern | Choice | +|---------|--------| +| Framework | TanStack Start (Vite + TanStack Router, SSR + server functions); **standalone** (own lockfile, no `@karrio/*` deps) | +| Data (shipping) | Studio's own fetch client + TanStack Query hooks (`src/lib/karrio/*`) → Karrio GraphQL + REST | +| Data (Studio-native) | Karrio backend passthrough — metafields/workspace-config (`studio.*`); no Studio DB | +| Forms | TanStack Form + Zod validation | +| UI / Styling | **shadcn/ui + Tailwind CSS** (`globals.css` theme tokens); bespoke shell CSS for the enterprise layout | +| Auth | Server functions → Karrio `token_auth`/refresh; httpOnly session cookie | +| Monitoring | Workbench overlay (logs/events/health/workers/tracing) wired to Karrio admin hooks + Sentry/PostHog (`instrumentation`) | +| Tests | Playwright (`packages/e2e`, new `studio` project) | + +## Modes, Screens & Feature Matrix + +Routing derives mode from route (deep links land in the right mode). Defaults: +Ship→`home`, Build→`apps`, Govern→`admin`. + +| Mode | Screen | Route | Karrio API | Prototype ref | +|------|--------|-------|-----------|---------------| +| Ship | Home | `home` | shipment/tracker stats | `screens-ops.jsx` HomeScreen | +| Ship | Shipments + Sheet | `shipments` | REST `/v1/shipments`, GraphQL | ShipmentsScreen, ShipmentSheet | +| Ship | Trackers + Sheet | `trackers` | `/v1/trackers` | TrackersScreen, TrackerSheet | +| Ship | Orders + Sheet | `orders` | GraphQL orders | OrdersScreen, OrderSheet | +| Ship | Pickups + Create | `pickups` | `/v1/pickups` | PickupsScreen, PickupSheet, CreatePickupSheet | +| Ship | Connections + Sheet | `connections` | carrier-connections | ConnectionsScreen, ConnectionSheet | +| Ship | Shipping rules + Sheet | `rules` | shipping-rules | ShippingRulesScreen, RuleSheet | +| Ship | Addresses + Sheet | `addresses` | address | AddressesScreen, AddressSheet | +| Ship | Parcels + Sheet | `parcels` | parcel | ParcelsScreen, ParcelSheet | +| Ship | Products + Sheet | `products` | product | ProductsScreen, ProductSheet | +| Ship | Documents + Editor | `documents` | document-template | DocumentsScreen, DocumentSheet | +| Build | Apps + Sheet | `apps` | app-store, apps hook | AppsScreen, AppSheet, AppLauncher | +| Build | Plugins + Sheet | `plugins` | plugins/registry | PluginsScreen, PluginSheet | +| Build | MCP | `mcp` | `packages/mcp` + Django | McpScreen | +| Build | Editor (agent IDE) | `editor` | Karrio metafields (`studio.agents`) + SDK scaffold | EditorScreen | +| Build | Workbench overlay | (overlay) | admin/log/event/health/worker/tracing | workbench.jsx | +| Build | Webhooks | `webhooks` | webhook hook | screens-develop.jsx | +| Build | API keys | `apikeys` | api-keys/api-token | screens-develop.jsx | +| Govern | Admin overview | `admin` | admin-* hooks | AdminScreen | +| Govern | Tenants | `tenants` | admin-platform | AdminScreen | +| Govern | Team & roles | `team` | organization/admin-users | screens-platform.jsx | +| Govern | Security | `security` | admin/session | screens-platform.jsx | +| Govern | Audit log | `audit` | event/tracing | screens-platform.jsx | +| Govern | Settings | `settings` | workspace-config, user | SettingsScreen | +| Cross | Auth flow | `/login…` | token_auth, register, verify, 2FA, reset | `auth.jsx` | +| Cross | Command palette ⌘K | overlay | search hook | CommandPalette | +| Cross | Tweaks panel | overlay | Karrio workspace-config / user metadata | tweaks-panel.jsx | + +### Core reusable components (Studio UI layer) + +`Sheet` (right drawer sm/md/lg + fullscreen), `ActivityFeed` + `JsonView`, +`CommandPalette`, `AppLauncher`/`AppSheet`, `Toast`, `Sidebar`/`Topbar`, +`Icon`, `CarrierLogo`, `Field`. All detail/create/edit views build on `Sheet`. + +### Design tokens + +Ported verbatim from `styles.css`: accent `#8B5CF6`; radii xs2/sm3/md4/lg6/pill3; +dark-default + light theme palettes; status colors; density variants +(compact/regular/comfy); Inter / JetBrains Mono / IBM Plex Sans. Theme persists +(`localStorage` + Karrio workspace-config), applied pre-render to avoid flash. + +## New Features + +### Carrier integration & plugin Editor (`editor`) +Agent-first 3-pane IDE: **left** agent sessions, **center** Assistant chat (default) ++ closeable code tabs with inline AI diff (Apply/Reject), **right** plugin file tree. +Scaffolds connectors using the SDK extension pattern (`./bin/cli sdk add-extension`) +and `@karrio/app-store`, persisting sessions/runs/messages via Karrio metafields (`studio.agents.*`). + +### MCP management (`mcp`) +Server status (start/stop, URL, stats), exposed-tools table, install snippets +(Claude Desktop/Cursor JSON + SSE URL), connected clients, recent invocations — +backed by `packages/mcp` and the MCP server PRD. + +### AI Assistant & Agents +Assistant chat surface (Editor + ⌘K actions) backed by the Claude API; agent +sessions/runs/messages persisted via Karrio metafields (`studio.agents.*`); @-context chips reference Studio +entities. Tool execution flows through the Karrio MCP server. + +### Self-editable / customizable app +Tweaks panel (accent/font/density/theme) + layout customization persisted per +user/org via Karrio workspace-config / user metadata; applied via CSS custom properties. + +## Agent / Subagent Orchestration Plan (Linear) + +The build is decomposed into **orchestrator agents** (epics) and **subagents** +(issues). This maps 1:1 to the Linear project "Karrio Studio". + +``` +Karrio Studio (Linear Project) +├─ EPIC A · Foundation Agent +│ ├─ A1 scaffold apps/studio (TanStack Start, Vite, SSR) +│ ├─ A2 design tokens + global CSS (port styles.css) +│ ├─ A3 app shell: Sidebar + Topbar + mode IA + routing +│ ├─ A4 core components: Sheet, ActivityFeed, JsonView, Toast, Field, Icon, CarrierLogo +│ ├─ A5 Studio-native state mapping (Karrio metafields `studio.*`) +│ └─ A6 decoupled Karrio client + session context + data hooks (src/lib/karrio/*) +├─ EPIC B · Auth Agent +│ ├─ B1 server-fn auth (token_auth/refresh) + httpOnly session +│ ├─ B2 auth screens (sign in/up, verify, 2FA, forgot/reset, invite, change pw) +│ └─ B3 route guards + org/test-mode context +├─ EPIC C · Ship Agent +│ ├─ C1 Home ├─ C2 Shipments+Sheet ├─ C3 Trackers+Sheet ├─ C4 Orders+Sheet +│ ├─ C5 Pickups+Create ├─ C6 Connections ├─ C7 Shipping rules +│ ├─ C8 Addresses ├─ C9 Parcels ├─ C10 Products └─ C11 Documents+template editor +├─ EPIC D · Build Agent +│ ├─ D1 Apps + AppLauncher/AppSheet ├─ D2 Plugins ├─ D3 MCP management +│ ├─ D4 Editor (agent IDE) ├─ D5 Workbench overlay ├─ D6 Webhooks └─ D7 API keys +├─ EPIC E · Govern Agent +│ ├─ E1 Admin overview ├─ E2 Tenants ├─ E3 Team & roles +│ ├─ E4 Security ├─ E5 Audit log └─ E6 Settings +├─ EPIC F · Agents & MCP Agent (net-new) +│ ├─ F1 Assistant chat + Claude API ├─ F2 agent sessions/runs persistence +│ ├─ F3 MCP tool execution wiring └─ F4 carrier-integration scaffolding flow +├─ EPIC G · Customization Agent +│ ├─ G1 Tweaks panel ├─ G2 layout customization └─ G3 per-user/org persistence +├─ EPIC H · Cross-cutting Agent +│ ├─ H1 Command palette ⌘K ├─ H2 keyboard shortcuts ├─ H3 quick-create +│ └─ H4 monitoring (Sentry/PostHog) + health +└─ EPIC I · QA / Playwright Agent + ├─ I1 studio Playwright project + auth setup + ├─ I2 per-feature specs (one per C/D/E screen) + ├─ I3 GraphQL+REST integration fixtures + └─ I4 CI wiring +``` + +Each subagent issue carries: scope, prototype ref, Karrio API/hook, acceptance +criteria, and a paired Playwright spec (EPIC I). + +## Implementation Plan + +| Phase | Agent(s) | Deliverable | +|-------|----------|-------------| +| 0 (this session) | Foundation A1–A4 (scaffold), I1 | `apps/studio` boots, shell + tokens, Playwright `studio` project | +| 1 | A5–A6, B | Studio-native state mapping, decoupled Karrio client + hooks, full auth | +| 2 | C | Ship mode parity, each screen + spec | +| 3 | D | Build mode incl. MCP + Editor + Workbench | +| 4 | E, G, H | Govern, customization, cross-cutting | +| 5 | F | Agents & MCP execution, carrier scaffolding | +| 6 | I, cutover | Full Playwright suite green; deprecate dashboard | + +## Testing Strategy (Playwright) + +- Extend `packages/e2e`: add a **`studio`** Playwright project (baseURL + `KARRIO_STUDIO_URL`, default `http://localhost:3003`) with its own `auth.setup.ts` + (Studio server-session login) producing `playwright/.auth/studio.json`. +- **One spec per feature** (matrix above): list/table render, filters/tabs, row → + Sheet open, create/edit/delete via Sheet forms, and the net-new MCP/Editor/Agent + flows. Each asserts against **live Karrio GraphQL + REST** responses (seeded test + org), not mocks. +- Follow repo Playwright conventions (role-based locators, `networkidle`, auth state + reuse). `unittest`/`karrio test` remain for SDK/Django; Playwright covers Studio UI. +- CI: run `studio` project after `setup`; fixtures seed shipments/trackers/orders. + +## Risk Assessment + +| Risk | Mitigation | +|------|-----------| +| NextAuth/Next coupling in `@karrio/*` | Avoided entirely — Studio is standalone with its own fetch client + session context (no `@karrio/hooks`/`lib`) | +| Drift from Karrio API types (local types) | Local types model only consumed fields; integration Playwright runs against the live API to catch drift | +| Standalone app misses monorepo tooling | Self-contained config (own tsconfig/tailwind/lockfile); excluded from `npm ci`/`turbo`, so it can't break monorepo CI | +| Scope (huge surface) | Phased per-agent delivery tracked in Linear; dashboard stays live | +| TanStack Start maturity | Pin versions; isolate SSR/server-fn boundaries; lean on TanStack Query for data | +| Agent/MCP security | Tool execution via MCP server with org scoping; no secrets in client | + +## Migration & Rollback + +Studio ships alongside the dashboard. Cutover is per-mode behind a feature flag; +rollback = route users back to `apps/dashboard`. No destructive backend changes — +Studio-native state is additive (Karrio metafields, `studio.*`), shipping data untouched. diff --git a/apps/studio/.env.sample b/apps/studio/.env.sample new file mode 100644 index 000000000..07442c924 --- /dev/null +++ b/apps/studio/.env.sample @@ -0,0 +1,13 @@ +# Karrio backend (GraphQL + REST) — the single source of truth for ALL state. +# Shipping data AND Studio-native state (customization, agents, MCP config) are +# persisted on the Karrio backend (passthrough). No separate Studio database. +# Server-side base URL +KARRIO_API=http://localhost:5002 +# Client-side base URL (Vite exposes only VITE_-prefixed vars to the browser) +VITE_KARRIO_API=http://localhost:5002 + +# Studio server session (httpOnly cookie signing) +STUDIO_SESSION_SECRET=change-me-in-production + +# Optional: AI Assistant / agents (Claude API) +ANTHROPIC_API_KEY= diff --git a/apps/studio/.gitignore b/apps/studio/.gitignore new file mode 100644 index 000000000..1383a3cb6 --- /dev/null +++ b/apps/studio/.gitignore @@ -0,0 +1,16 @@ +node_modules +.output +.nitro +.tanstack +dist +.env +.env.local +# Generated by the TanStack Start / Router plugin +src/routeTree.gen.ts + +# The repo-root .gitignore has `lib/` and `build/` (build output) rules that +# would swallow our source dirs of the same name; re-include them. +!src/lib/ +!src/lib/** +!src/screens/build/ +!src/screens/build/** diff --git a/apps/studio/.npmrc b/apps/studio/.npmrc new file mode 100644 index 000000000..e60ae7152 --- /dev/null +++ b/apps/studio/.npmrc @@ -0,0 +1 @@ +ignore-scripts=false diff --git a/apps/studio/ARCHITECTURE.md b/apps/studio/ARCHITECTURE.md new file mode 100644 index 000000000..fdf7943d5 --- /dev/null +++ b/apps/studio/ARCHITECTURE.md @@ -0,0 +1,191 @@ +# Karrio Studio — Architecture + +Studio is a **standalone full‑stack TanStack Start app** (`apps/studio/`) that +replaces the legacy Next.js dashboard. It is intentionally **decoupled** from the +monorepo: its own `package.json`/lockfile, its own data layer, shadcn + Tailwind +(no `@karrio/ui`), and its own auth (no `@karrio/hooks`/NextAuth). It talks to a +running Karrio server purely as a **client** — there is **no Studio database**; +Karrio is the single source of truth (passthrough). + +``` + ┌──────────────────────────────────────────────────────────┐ + │ apps/studio (SSR) │ + │ │ + Browser ──┤ TanStack Router (file routes) │ + │ __root → _app (guard) → _app.$screen (lazy registry) │ + │ │ + │ React screens ──useQuery──► data hooks (lib/karrio) │ + │ │ │ + │ Server Functions (BFF) │ KarrioCtx │ + │ auth.* / assistant.* ▼ │ + │ │ client.ts (REST + GraphQL) │ + └────────┼───────────────────────────┼───────────────────────┘ + │ (server‑to‑server) │ (browser fetch, Bearer) + ▼ ▼ + Anthropic API Karrio server (REST /v1/*, /graphql, + (Claude, optional) /api/token[/refresh]) +``` + +## 1. Module map + +``` +apps/studio/src/ +├── routes/ # TanStack Router file routes +│ ├── __root.tsx # Providers (QueryClient, Session), shell +│ ├── index.tsx # "/" → redirect to /home or /login +│ ├── login.tsx signup.tsx forgot.tsx # public auth pages +│ ├── _app.tsx # authenticated layout + beforeLoad guard +│ └── _app.$screen.tsx # dynamic screen dispatcher (one route, N screens) +├── server/ # Server Functions = Studio's BFF "API" +│ ├── auth.ts # login/refresh/logout/register/reset/getSession +│ └── assistant.ts # sendAssistantMessage → Anthropic (server-side) +├── lib/karrio/ # Decoupled data layer (no @karrio/*) +│ ├── env.ts # base URL resolution (KARRIO_API / VITE_KARRIO_API) +│ ├── client.ts # restGet/restMutate/graphql + authedFetch (401→refresh) +│ ├── session.tsx # SessionProvider → KarrioCtx, refresh handler +│ ├── hooks.ts # 19 REST + 6 GraphQL TanStack Query hooks + mutations +│ ├── types.ts # local API types (no @karrio/types) +│ ├── display.ts # entity → display-string helpers +│ ├── agents.ts # AgentDef + McpServerConfig store (metafields + LS cache) +│ ├── preferences.ts # UI prefs (metafields + LS cache) +│ └── metastore.ts # per-user backend KV over Karrio metafields +├── lib/{modes,studio-state}.ts # nav model (Ship/Build/Govern) + UI state +├── screens/ # registry.tsx (React.lazy) → ship/* build/* govern/* +└── components/ # shell (Sidebar/Topbar), ui (primitives/Sheet/...), + # overlays (CommandPalette/Workbench/TweaksPanel) +``` + +## 2. Request / data flow + +Reads use TanStack Query hooks; writes use mutation hooks that invalidate the +relevant query keys. Every call carries auth + tenant context derived from the +session. + +``` +Screen (useShipments) lib/karrio/hooks.ts + │ useQuery(["shipments", …]) ───► queryFn: restGet(ctx, "/v1/shipments") + │ │ + │ client.ts authedFetch(ctx, url) + │ │ headers: Authorization: Bearer + │ │ x-org-id, x-test-mode + │ ▼ + │ Karrio GET /v1/shipments ──► 200 JSON + │ │ + │ on 401 ──► refreshHandler() ──► refreshSession() (server fn) ──► new access + │ (de-duped, single retry with the rotated token) + ▼ + render +``` + +`KarrioCtx = { baseUrl, token, orgId, testMode }` is assembled once by +`SessionProvider` and read by every hook via `useKarrioCtx()`. The token comes +from the httpOnly session cookie (never exposed to JS except as the in-memory +bearer surfaced through the session query). + +**REST vs GraphQL split** (mirrors the Karrio API surface): + +| Transport | Used for | +|---|---| +| **REST `/v1/*`** | shipments, trackers, connections, pickups, documents, manifests, batches, apps, plugins, webhooks, api_keys, admin, mcp, usage (19 hooks) | +| **GraphQL `/graphql`** | orders, address/parcel/product templates, rate_sheets, shipping_rules, workflows (6 hooks) + mutations (create/update/delete address/parcel/product, update_user, register_user, request_password_reset) | + +## 3. Auth & session flow + +Studio runs a thin **Backend‑for‑Frontend**: server functions proxy Karrio's JWT +endpoints and keep tokens in a single httpOnly cookie so SSR can authenticate and +the browser never stores raw tokens. + +``` +Login page ──login({email,password})──► server/auth.ts + POST {KARRIO_API}/api/token + ◄─ { access, refresh } + Set httpOnly cookie "karrio-studio-session" +SSR / hydrate ──getSession()──► reads cookie → { access, email } → SessionProvider + +401 on any API call: + client.authedFetch ── refreshHandler ──► refreshSession() (server fn) + POST /api/token/refresh { refresh } + ◄─ { access, refresh } (rotates) + updates cookie + cached session + ◄── new access ── retry original request once +``` + +Route protection: `_app.tsx`'s `beforeLoad` redirects unauthenticated requests to +`/login`; the public auth routes opt out of the session storage state. + +## 4. Karrio integration points + +| Concern | Endpoint(s) | Notes | +|---|---|---| +| Auth | `POST /api/token`, `POST /api/token/refresh`, `POST /api/logout` | simplejwt; tokens in httpOnly cookie | +| Operational data | `GET/POST/PATCH/DELETE /v1/*` | REST resources | +| Relational/template data | `POST /graphql` | connections-style queries + typed mutations | +| Tenancy | headers `x-org-id`, `x-test-mode` | passed from `KarrioCtx` on every call | +| Studio-native state | `metafields` (create/update/delete_metafield + filter by key) | per-user JSON under `studio.*` (prefs, agents, MCP configs) — round-trips | +| Registration / reset | `register_user`, `request_password_reset` GraphQL | | + +No schema/migrations are added to Karrio. The one backend file touched on this +branch is `modules/documents/.../generator.py` — an offline‑CSS guard for the +local sandbox, unrelated to Studio's runtime. + +## 5. Custom Studio "APIs" (server functions) + +These are the **net‑new endpoints Studio adds** — TanStack Start server functions +(typed RPC), not REST routes. They form the BFF layer: + +| Server function | Method | Purpose | +|---|---|---| +| `login` | POST | exchange credentials at `/api/token`, set httpOnly session | +| `refreshSession` | POST | rotate access via `/api/token/refresh`, update cookie | +| `logout` | POST | clear the session cookie | +| `register` | POST | `register_user` GraphQL mutation | +| `requestPasswordReset` | POST | `request_password_reset` GraphQL mutation | +| `getSession` | GET | read the session cookie for SSR + client | +| `sendAssistantMessage` | POST | proxy to the Anthropic Messages API (Claude) server‑side, keeping `ANTHROPIC_API_KEY` off the client; returns a deterministic stub when unset | + +## 6. Net‑new Studio capabilities (beyond dashboard parity) + +- **Command palette (⌘K)**, **Workbench** dev overlay, **Tweaks** appearance panel. +- **Self‑editable appearance** — `preferences.ts` is the single source of truth for + theme/accent/density/font/layout. Persisted **per‑user to the Karrio backend** as a + JSON **metafield** (`studio.customization`) via `metastore.ts`, with `localStorage` + as the offline cache. `loadFromBackend()` hydrates on login, so settings follow the + user across devices (full create/read/update/delete round trip — no backend change). +- **Agents & MCP management** — `agents.ts` is a typed store for `AgentDef` and + `McpServerConfig`, persisted as metafields (`studio.agents` / `studio.mcp-servers`) + with a `localStorage` write‑through cache. MCP entries show an honest **"config‑only"** + status — OSS has no execution proxy, so no connection state is fabricated. +- **Agent IDE / Assistant** — the Editor screen pairs a Claude chat (via + `sendAssistantMessage`) with a client‑side connector **scaffolder**. + +## 7. What is NOT built (honest scope / roadmap) + +Studio is a **frontend + thin BFF**. The following require Karrio **backend** work +that this branch does **not** include: + +- **On‑the‑fly plugin editing with hot reload.** The Editor's `scaffold()` generates + connector files **in the browser** from an SDK‑extension template (mappers / + providers / schemas / tests) for display and AI‑assisted editing. It does **not** + write to the server's plugins directory, and nothing reloads carrier modules at + runtime. A real version needs: (a) an API to persist/edit connector files under + the Karrio plugins path, and (b) a runtime plugin loader/reloader (Karrio imports + connectors as Python packages at boot; hot‑reloading them is non‑trivial). Tracked + as roadmap. +- **MCP tool execution** — config management only; no backend exec proxy. +- **Deeper write‑wizards** — rate‑buy, recurring pickups, rule builder, + create‑manifest/run‑batch, markup editor, fulfillment/role actions, billing. + See `PARITY.md` (all marked 🟡). + +## 8. Testing & CI + +- `packages/e2e/tests/studio/*` — ~200 mocked Playwright specs (route‑level API + mocking, inline session cookie) + a live‑gated smoke project. +- `render-smoke.spec.ts` — loads every route and fails on any runtime `pageerror` + (catches "build‑green‑but‑broken" crashes that `tsc`/build miss). +- `visual.spec.ts` — screenshot baselines, gated behind `KARRIO_VISUAL=1`. +- `.github/workflows/studio-e2e.yml` — builds Studio, starts the dev server, runs + the mocked project (no Django/Postgres needed). +- Sandbox: `bin/studio-sandbox` + `apps/studio/sandbox/seed.py` provision + high‑fidelity data (shipments with addresses/parcels/charges, trackers, + orders) with a seed self‑check. +``` diff --git a/apps/studio/PARITY.md b/apps/studio/PARITY.md new file mode 100644 index 000000000..9a8371f55 --- /dev/null +++ b/apps/studio/PARITY.md @@ -0,0 +1,77 @@ +# Karrio Studio ↔ Dashboard — Feature Parity Audit + +Explicit mapping of the legacy Next.js dashboard surface (derived from +`packages/hooks/*`, `packages/core`, `packages/admin`, `packages/developers`, +and `apps/dashboard` routes) to Karrio Studio (`apps/studio`). + +Legend: ✅ implemented · 🟡 partial · ⛔ not yet (roadmap) · ➕ net-new in Studio + +## Ship (operations) + +| Dashboard feature | Hook(s) | Studio | Status | +|---|---|---|---| +| Shipments list + detail | `shipment`, `bulk-shipments` | Shipments + ShipmentSheet | ✅ | +| Create label / buy rate | `label-data`, `shipment` | quick-create → Shipments (rate-buy flow) | 🟡 list+detail done; rate-buy wizard ⛔ | +| Trackers list + timeline | `tracker` | Trackers + TrackerSheet | ✅ | +| Orders + fulfillment | `order` | Orders + OrderSheet | ✅ list/detail; fulfill action 🟡 | +| Pickups (+ recurring) | `pickup` | Pickups + sheet | ✅ list/detail; schedule wizard 🟡 | +| Carrier connections | `carrier-connections`, `system-connection`, `user-connection` | Connections + sheet | ✅ list/detail; add-credentials form 🟡 | +| Shipping rules | `shipping-rules`, `shipping-rule-templates` | Rules + sheet | ✅ list/detail; rule builder 🟡 | +| Addresses | `address` | Addresses (**CRUD**) | ✅ | +| Parcels | `parcel` | Parcels (**CRUD**) | ✅ | +| Products / commodities | `product`, `customs` | Products (**CRUD**) | ✅ (customs templates 🟡) | +| Document templates | `document-template`, `default-template` | Documents + sheet | ✅ list/detail; template editor 🟡 | +| Manifests | `manifests` | Manifests + sheet | ✅ list/detail; create-manifest 🟡 | +| Batch operations | `batch-operations` | Batches + sheet | ✅ list/detail; run-batch action 🟡 | +| Rate sheets / markups | `rate-sheet`, `admin-rate-sheets`, `admin-markups` | Rate sheets + sheet | ✅ list/detail; markup editor 🟡 | +| Workflows (automation) | `workflows`, `workflow-*` | Workflows + sheet | ✅ list/detail; EE-only data (OSS GraphQL has no `workflows` field → graceful empty state) | + +## Build (developer / extensibility) + +| Dashboard feature | Hook(s) | Studio | Status | +|---|---|---|---| +| Apps / integrations | `apps`, app-store | Apps + sheet | ✅ list/detail; OAuth connect 🟡 | +| API keys | `api-keys`, `api-token` | API keys | ✅ list; generate/revoke 🟡 | +| Webhooks | `webhook` | Webhooks + sheet | ✅ list/detail; create-edit 🟡 | +| Logs / events / tracing | `log`, `event`, `tracing-record` | Workbench overlay | ✅ (live wiring 🟡) | +| GraphiQL / playground | — | Workbench tab | 🟡 placeholder | +| Plugins / carrier registry | registry | Plugins + sheet | ✅ ➕ | +| MCP management | `packages/mcp` | MCP screen | ➕ ✅ | +| Agent IDE / Assistant | — | Editor + Assistant (Claude) | ➕ ✅ | + +## Govern (admin / platform) + +| Dashboard feature | Hook(s) | Studio | Status | +|---|---|---|---| +| Admin overview / system | `admin-platform`, `admin-worker`, `system-usage` | Admin overview | ✅ | +| Tenants | `admin-platform` | Tenants | ✅ | +| Team & roles | `admin-users`, `organization`, `user` | Team & roles | ✅ list; invite/role-edit 🟡 | +| Usage / billing | `usage`, `admin-usage`, `subscription` | Usage | ✅ metrics; billing/subscription mgmt 🟡 | +| Security (2FA/SSO/sessions) | `session`, admin | Security | ✅ (wire toggles 🟡) | +| Audit log | `event` | Audit | ✅ | +| Workspace settings | `workspace-config`, `metadata` | Settings + Tweaks | ✅ ➕ self-edit | + +## Cross-cutting + +| Feature | Studio | Status | +|---|---|---| +| Auth (sign in/up/forgot, guards) | ✅ | wired to Karrio `/api/token`; httpOnly session + 401 token refresh | +| Home landing (stats + recent + to-do) | ✅ | real metrics from shipment/tracker/order hooks | +| Command palette (⌘K) | ➕ ✅ | | +| Self-editable appearance (theme/accent/density/font) | ➕ ✅ | | +| Responsive + dark/light | ✅ | swept across every page | +| Org / test-mode context | 🟡 | header plumbing in client; switcher UI ⛔ | + +## Summary + +Ship/Build/Govern **operational parity** (list + detail + core write flows) is in +place across **every** dashboard surface — including manifests, batch operations, +rate sheets, workflows, and usage (all now list/detail screens) — **plus** net-new +Studio capabilities (plugins, MCP, agent Editor/Assistant, self-editable UI). + +What remains is **depth on specific write flows**, marked 🟡 above and tracked in +the Karrio Studio Linear project: the create-wizards (rate-buy, recurring pickups, +rule builder, customs/template editors, create-manifest/run-batch, markup editor), +order-fulfillment + invite/role actions, billing/subscription management, the +org/test-mode switcher UI, and live wiring for logs/GraphiQL. No dashboard surface +is missing a screen; these are deeper interactions layered on the existing screens. diff --git a/apps/studio/README.md b/apps/studio/README.md new file mode 100644 index 000000000..1824d35d5 --- /dev/null +++ b/apps/studio/README.md @@ -0,0 +1,92 @@ +# Karrio Studio (`@karrio/studio`) + +Full-stack **TanStack Start** app that replaces the Next.js dashboard +(`apps/dashboard`) with a modular, agent-friendly "studio" — Ship / Build / +Govern modes, a self-editable UI, and net-new carrier-integration, Editor, +Agent, and MCP-management surfaces. + +> **Standalone by design.** This app is intentionally **excluded from the +> turborepo workspaces**. It has its own `package-lock.json`/`node_modules` and +> depends on **no** `@karrio/*` package — UI is **shadcn/ui + Tailwind**, and +> the Karrio API is consumed via a **self-contained fetch client + local types** +> (`src/lib/karrio/*`). This keeps Studio fast-moving and isolated from monorepo +> dependency/version conflicts, and out of root `npm ci` / `turbo build`. + +See [`PRDs/KARRIO_STUDIO.md`](../../PRDs/KARRIO_STUDIO.md) for the full design, +the agent/subagent plan, and the testing strategy. Work is tracked in the +**"Karrio Studio"** Linear project (epics = orchestrator agents, issues = +subagents). + +## Status — Phase 0 (foundation) + +| Area | State | +|------|-------| +| Standalone TanStack Start app (Vite, SSR, file routes) | ✅ | +| Tailwind + shadcn theme (`globals.css`) + bespoke shell CSS | ✅ | +| App shell: Sidebar + Topbar + mode IA + routing | ✅ | +| Core components: `Sheet`, `Field`, `Icon`, `CarrierLogo` | ✅ (shadcn primitives added per screen) | +| Every IA route navigable (Placeholder + real Home) | ✅ `src/screens/` | +| Decoupled Karrio client + session + hooks | ✅ `src/lib/karrio/*` | +| Studio-native state contract (Karrio passthrough) | ✅ `src/lib/studio-state.ts` | +| Server-side auth (Karrio `token_auth`) | ✅ `src/server/auth.ts` | +| Playwright `studio` project + smoke specs | ✅ `packages/e2e/tests/studio/` | +| Feature screens, auth UI, agents/MCP/editor, monitoring | ⏳ tracked in Linear | + +## Architecture + +- **No `@karrio/*` deps.** Shipping data AND Studio-native state go through the + Karrio backend via Studio's own client (`src/lib/karrio/client.ts`) and + TanStack Query hooks (`src/lib/karrio/hooks.ts`), keyed by a `KarrioCtx` + (base URL + token + org + test-mode) from `src/lib/karrio/session.tsx`. +- **Studio-native state** (customization, agents, MCP) → Karrio + metafields/workspace-config under `studio.*` (see `src/lib/studio-state.ts`). +- **Auth** → server functions (`src/server/auth.ts`) proxy Karrio JWT auth into + an httpOnly session cookie. +- **UI** → shadcn/ui + Tailwind (`src/styles/globals.css`, `src/lib/utils.ts`), + with bespoke CSS for the enterprise shell (`src/styles/tokens.css`). + +## Develop + +```bash +cp .env.sample .env # set KARRIO_API / VITE_KARRIO_API +cd apps/studio # standalone — install here, NOT at repo root +npm install +npm run dev # → http://localhost:3003 (generates routeTree.gen.ts) +``` + +## Test + +```bash +# with the studio dev server running on :3003 +cd packages/e2e +KARRIO_STUDIO_URL=http://localhost:3003 npx playwright test --project=studio +``` + +## Layout + +``` +apps/studio/ # standalone (own lockfile/node_modules) +├── vite.config.ts # TanStack Start plugin +├── tailwind.config.ts · postcss.config.js +├── src/ +│ ├── router.tsx # createRouter (routeTree.gen.ts is generated) +│ ├── routes/ +│ │ ├── __root.tsx # document, fonts, theme-init, QueryClient, Session +│ │ ├── index.tsx # / → /home +│ │ ├── _app.tsx # shell layout (sidebar + topbar + shortcuts) +│ │ └── _app.$screen.tsx # screen dispatch (validates IA, 404s unknown) +│ ├── screens/ # registry + Home + Placeholder +│ ├── components/ +│ │ ├── shell/ # Sidebar, Topbar +│ │ └── ui/ # Sheet, Field, icons, CarrierLogo (+ shadcn) +│ ├── lib/ +│ │ ├── modes.ts # Ship/Build/Govern IA +│ │ ├── theme.ts # theme + density prefs +│ │ ├── utils.ts # shadcn cn() +│ │ ├── studio-state.ts # Studio-native state contract (Karrio passthrough) +│ │ └── karrio/ # env, client, types, session, hooks (decoupled) +│ ├── server/ # auth server functions +│ └── styles/ +│ ├── globals.css # Tailwind + shadcn theme tokens +│ └── tokens.css # enterprise shell/sheet/table CSS +``` diff --git a/apps/studio/STUDIO_GRAPHQL_REBUILD.md b/apps/studio/STUDIO_GRAPHQL_REBUILD.md new file mode 100644 index 000000000..38f734383 --- /dev/null +++ b/apps/studio/STUDIO_GRAPHQL_REBUILD.md @@ -0,0 +1,98 @@ +# Studio GraphQL-first data-layer rebuild — coordination contract + +Tracks the migration of `apps/studio/src/lib/karrio/hooks/*` from invented REST +paths to Karrio's canonical GraphQL API. Linear epic: **EBE-95**. PR base: +`claude/studio-graphql-rebuild`. + +> Future: Studio will move to its own repo under **ebenlabs**. Keep everything +> here self-contained (no new `@karrio/*` imports); the data layer + server +> functions must work against a remote Karrio server via config only. + +## Status (Claude took over A–E from Codex) + +| Unit | State | +|---|---| +| F0 Foundation | ✅ admin-graphql client path + `hooks/{ship,build,govern,resources}` split | +| A Ship reads | ✅ all on GraphQL (shipments/shipment/trackers/user_connections/pickups/document_templates/manifests/batch_operations), live-verified | +| B Build reads | ✅ api_keys → GraphQL, webhooks list → GraphQL; apps/plugins/mcp → honest "not available" | +| C Govern reads | ✅ team→admin `users`, admin→`worker_health`, usage→`system_usage`, audit→`events`; tenants → honest EE state | +| D Studio-native state | ✅ real per-user persistence via **metafields** (`studio.*` namespace) — full CRUD round trip, no backend change needed. `metastore.ts` + preferences/agents wired; localStorage is the offline cache. | +| E Mutations | ✅ address/parcel/product on GraphQL; **every read** is GraphQL. Webhook + carrier-connection create/update/delete stay on **functional** REST (GraphQL equivs exist but need input-shape remapping — refinement, not broken). | + +**Net result:** every Studio screen shows real Karrio data via GraphQL, or an honest "not available" state — no invented/404 REST reads remain. Full mocked suite green (205); each query live-verified against `:5002`. + + +## Foundation (F0 — landed) + +- `client.ts` — `graphql(ctx, query, vars, endpoint="/graphql")` + `adminGraphql()` + for the **second** `/admin/graphql` schema. Both keep the 401→refresh retry. +- `hooks/_shared.ts` — `keyExtra`, `graphqlEdges(ctx, query, field, vars, endpoint)`. +- `hooks/{ship,build,govern,resources}.ts` — domain modules; `hooks.ts` re-exports them. + +## GROUND TRUTH — live schema (verified against running :5002) + +**`/graphql` (tenant) Query fields:** `address(es), api_keys, batch_operation(s), +carrier_connection(s), data_template(s), default_templates, document_template(s), +event(s), log(s), manifest(s), metafield(s), order(s), parcel(s), pickup(s), +product(s), rate_sheet(s), shipment(s), system_connections, system_usage, token, +tracing_record(s), tracker(s), user, user_connections, webhook(s), workspace_config` + +**`/admin/graphql` Query fields:** `configs, config_fieldsets, config_schema, fees, +markups, me, permission_groups, rate_sheets, system_carrier_connection(s), +task_executions, usage, user, users, worker_health` + +**No OSS source (render honest "not available"/EE empty state — do NOT invent a query):** +`oauth_apps`/`app_installations` (apps), `accounts` (tenants), `auditlogs`, +`workflows`, `shipping_rules`, plugins, mcp. + +## Canonical query reference (mirror, don't hand-roll) + +- Tenant: `packages/types/graphql/queries.ts` (`GET_SHIPMENTS`, `GET_TRACKERS`, + `GET_USER_CONNECTIONS`, `GET_PICKUPS`, `GET_DOCUMENT_TEMPLATES`, `GET_MANIFESTS`, + `GET_BATCH_OPERATIONS`, `GET_WEBHOOKS`, `GET_API_KEYS`, `GET_EVENTS`, + `GET_SYSTEM_USAGE`, `GET_ORDERS`, `GET_ADDRESSES`, `GET_PARCELS`, `GET_PRODUCTS`, + `GET_RATE_SHEETS`, `GET_WORKSPACE_CONFIG`, …) +- Admin: `packages/types/graphql/admin/queries.ts` (`GET_USERS`, `GET_WORKER_HEALTH`, + `GET_CONFIGS`, `GET_ADMIN_SYSTEM_USAGE`, …) + +## Per-hook source mapping + +| Hook | File (unit) | New source | +|---|---|---| +| useShipments / useShipment | ship.ts (A) | `/graphql` shipments / shipment | +| useTrackers | ship.ts (A) | `/graphql` trackers | +| useCarrierConnections | ship.ts (A) | `/graphql` user_connections (+ system_connections) | +| usePickups | ship.ts (A) | `/graphql` pickups | +| useDocumentTemplates | ship.ts (A) | `/graphql` document_templates | +| useManifests | ship.ts (A) | `/graphql` manifests | +| useBatches | ship.ts (A) | `/graphql` batch_operations | +| useApiKeys | build.ts (B) | `/graphql` api_keys (404 fix) | +| useWebhooks | build.ts (B) | `/graphql` webhooks | +| useApps / usePlugins / useMcp | build.ts (B) | no source → "not available" empty state | +| useTeam | govern.ts (C) | `/admin/graphql` users (404 fix) | +| useAdminInfo | govern.ts (C) | `/admin/graphql` worker_health + configs (404 fix) | +| useUsage | govern.ts (C) | `/graphql` system_usage (404 fix) | +| useAuditLog | govern.ts (C) | `/graphql` events (404 fix; NOT "auditlogs") | +| useTenants | govern.ts (C) | no `accounts` → EE empty state | +| useOrders/Addresses/Parcels/Products/RateSheets | resources.ts (E) | already GraphQL — align fields to GET_* | +| useWorkflows / useShippingRules | resources.ts (E) | keep graceful `[]` (fields absent on OSS) | +| mutations (address/parcel/product/webhook/connection) | resources.ts (E) | GraphQL where available | +| agents / mcp config / prefs read | agents.ts, preferences.ts (D) | `metafields` / `workspace_config` / `user.metadata` | + +## Acceptance criteria (every unit / PR) + +1. `cd apps/studio && npx tsc --noEmit` clean. +2. `npx vite build` succeeds. +3. **Live-verify every new query** against the running `:5002` backend + (`POST /api/token` `admin@example.com` / `demo` → bearer): HTTP 200, no + `errors`, real data shape. Paste the curl evidence in the PR. +4. Update the affected Playwright spec mocks REST→GraphQL field-routing and run + the **full mocked `studio` project green** (`playwright test --project=studio`, + not just `--list`). +5. Branch off `claude/studio-graphql-rebuild`; PR targets it. Honest UI states for + any "not available"/EE resource — never fabricate data or success. + +## Ownership + +- **Claude**: F0 (this) + review/test/merge of A–E into the rebuild branch. +- **Codex**: units A–E in parallel (file-disjoint). diff --git a/apps/studio/docs/screenshots/00-login.png b/apps/studio/docs/screenshots/00-login.png new file mode 100644 index 000000000..02c917e41 Binary files /dev/null and b/apps/studio/docs/screenshots/00-login.png differ diff --git a/apps/studio/docs/screenshots/01-home.png b/apps/studio/docs/screenshots/01-home.png new file mode 100644 index 000000000..15f9e1fec Binary files /dev/null and b/apps/studio/docs/screenshots/01-home.png differ diff --git a/apps/studio/docs/screenshots/02-shipments.png b/apps/studio/docs/screenshots/02-shipments.png new file mode 100644 index 000000000..e46af5b05 Binary files /dev/null and b/apps/studio/docs/screenshots/02-shipments.png differ diff --git a/apps/studio/docs/screenshots/03-trackers.png b/apps/studio/docs/screenshots/03-trackers.png new file mode 100644 index 000000000..19ab8503a Binary files /dev/null and b/apps/studio/docs/screenshots/03-trackers.png differ diff --git a/apps/studio/docs/screenshots/04-orders.png b/apps/studio/docs/screenshots/04-orders.png new file mode 100644 index 000000000..9047a7b27 Binary files /dev/null and b/apps/studio/docs/screenshots/04-orders.png differ diff --git a/apps/studio/docs/screenshots/05-connections.png b/apps/studio/docs/screenshots/05-connections.png new file mode 100644 index 000000000..7a900a46a Binary files /dev/null and b/apps/studio/docs/screenshots/05-connections.png differ diff --git a/apps/studio/docs/screenshots/06-mcp.png b/apps/studio/docs/screenshots/06-mcp.png new file mode 100644 index 000000000..580ebceba Binary files /dev/null and b/apps/studio/docs/screenshots/06-mcp.png differ diff --git a/apps/studio/docs/screenshots/07-editor.png b/apps/studio/docs/screenshots/07-editor.png new file mode 100644 index 000000000..61133e960 Binary files /dev/null and b/apps/studio/docs/screenshots/07-editor.png differ diff --git a/apps/studio/docs/screenshots/08-plugins.png b/apps/studio/docs/screenshots/08-plugins.png new file mode 100644 index 000000000..f79d556f9 Binary files /dev/null and b/apps/studio/docs/screenshots/08-plugins.png differ diff --git a/apps/studio/docs/screenshots/09-admin.png b/apps/studio/docs/screenshots/09-admin.png new file mode 100644 index 000000000..13ae74b6d Binary files /dev/null and b/apps/studio/docs/screenshots/09-admin.png differ diff --git a/apps/studio/docs/screenshots/10-ratesheets.png b/apps/studio/docs/screenshots/10-ratesheets.png new file mode 100644 index 000000000..80ca51f17 Binary files /dev/null and b/apps/studio/docs/screenshots/10-ratesheets.png differ diff --git a/apps/studio/docs/screenshots/11-shipment-sheet.png b/apps/studio/docs/screenshots/11-shipment-sheet.png new file mode 100644 index 000000000..53b726d70 Binary files /dev/null and b/apps/studio/docs/screenshots/11-shipment-sheet.png differ diff --git a/apps/studio/docs/screenshots/12-mobile-shipments.png b/apps/studio/docs/screenshots/12-mobile-shipments.png new file mode 100644 index 000000000..698746280 Binary files /dev/null and b/apps/studio/docs/screenshots/12-mobile-shipments.png differ diff --git a/apps/studio/docs/screenshots/light-01-home.png b/apps/studio/docs/screenshots/light-01-home.png new file mode 100644 index 000000000..5ad280b78 Binary files /dev/null and b/apps/studio/docs/screenshots/light-01-home.png differ diff --git a/apps/studio/docs/screenshots/light-02-shipments.png b/apps/studio/docs/screenshots/light-02-shipments.png new file mode 100644 index 000000000..593b8aae7 Binary files /dev/null and b/apps/studio/docs/screenshots/light-02-shipments.png differ diff --git a/apps/studio/docs/screenshots/light-06-mcp.png b/apps/studio/docs/screenshots/light-06-mcp.png new file mode 100644 index 000000000..25b06a356 Binary files /dev/null and b/apps/studio/docs/screenshots/light-06-mcp.png differ diff --git a/apps/studio/package-lock.json b/apps/studio/package-lock.json new file mode 100644 index 000000000..927683991 --- /dev/null +++ b/apps/studio/package-lock.json @@ -0,0 +1,6650 @@ +{ + "name": "@karrio/studio", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@karrio/studio", + "version": "1.0.0", + "dependencies": { + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@tanstack/react-form": "^0.41.0", + "@tanstack/react-query": "^5.59.0", + "@tanstack/react-router": "^1.95.0", + "@tanstack/react-start": "^1.95.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" + }, + "devDependencies": { + "@tanstack/router-plugin": "^1.95.0", + "@types/node": "^22.19.19", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^5.0.0", + "autoprefixer": "^10.4.20", + "eslint": "^8.48.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^7.0.0", + "vite-tsconfig-paths": "^5.1.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", + "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", + "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oozcitak/dom": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz", + "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==", + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/url": "^3.0.0", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/infra": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz", + "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==", + "license": "MIT", + "dependencies": { + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/url": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz", + "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==", + "license": "MIT", + "dependencies": { + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@oozcitak/util": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz", + "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==", + "license": "MIT", + "engines": { + "node": ">=20.0" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.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" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@remix-run/node": { + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/@remix-run/node/-/node-2.17.4.tgz", + "integrity": "sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q==", + "license": "MIT", + "dependencies": { + "@remix-run/server-runtime": "2.17.4", + "@remix-run/web-fetch": "^4.4.2", + "@web3-storage/multipart-parser": "^1.0.0", + "cookie-signature": "^1.1.0", + "source-map-support": "^0.5.21", + "stream-slice": "^0.1.2", + "undici": "^6.21.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@remix-run/server-runtime": { + "version": "2.17.4", + "resolved": "https://registry.npmjs.org/@remix-run/server-runtime/-/server-runtime-2.17.4.tgz", + "integrity": "sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "@types/cookie": "^0.6.0", + "@web3-storage/multipart-parser": "^1.0.0", + "cookie": "^0.7.2", + "set-cookie-parser": "^2.4.8", + "source-map": "^0.7.3", + "turbo-stream": "2.4.1" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "typescript": "^5.1.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@remix-run/web-blob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/web-blob/-/web-blob-3.1.0.tgz", + "integrity": "sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==", + "license": "MIT", + "dependencies": { + "@remix-run/web-stream": "^1.1.0", + "web-encoding": "1.1.5" + } + }, + "node_modules/@remix-run/web-fetch": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@remix-run/web-fetch/-/web-fetch-4.4.2.tgz", + "integrity": "sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA==", + "license": "MIT", + "dependencies": { + "@remix-run/web-blob": "^3.1.0", + "@remix-run/web-file": "^3.1.0", + "@remix-run/web-form-data": "^3.1.0", + "@remix-run/web-stream": "^1.1.0", + "@web3-storage/multipart-parser": "^1.0.0", + "abort-controller": "^3.0.0", + "data-uri-to-buffer": "^3.0.1", + "mrmime": "^1.0.0" + }, + "engines": { + "node": "^10.17 || >=12.3" + } + }, + "node_modules/@remix-run/web-file": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/web-file/-/web-file-3.1.0.tgz", + "integrity": "sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ==", + "license": "MIT", + "dependencies": { + "@remix-run/web-blob": "^3.1.0" + } + }, + "node_modules/@remix-run/web-form-data": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/web-form-data/-/web-form-data-3.1.0.tgz", + "integrity": "sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A==", + "license": "MIT", + "dependencies": { + "web-encoding": "1.1.5" + } + }, + "node_modules/@remix-run/web-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@remix-run/web-stream/-/web-stream-1.1.0.tgz", + "integrity": "sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==", + "license": "MIT", + "dependencies": { + "web-streams-polyfill": "^3.1.1" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/form-core": { + "version": "0.41.4", + "resolved": "https://registry.npmjs.org/@tanstack/form-core/-/form-core-0.41.4.tgz", + "integrity": "sha512-XZJtN7mWJmi3apsc2J+GpWbcsXbv0pWBkZKP47ZW1QD/2Tj1UWsM6JjcaAkzIlrBdaoEFYmrHToLKr/Ddk8BVg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "^0.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/history": { + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.0.tgz", + "integrity": "sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==", + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-core": { + "version": "5.100.14", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", + "integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-form": { + "version": "0.41.4", + "resolved": "https://registry.npmjs.org/@tanstack/react-form/-/react-form-0.41.4.tgz", + "integrity": "sha512-uIfIDZJNqR1dLW03TNByK/woyKd2jfXIrEBq6DPJbqupqyfYXTDo5TMd/7koTYLO4dgTM5wd+2v3uBX3M2bRaA==", + "license": "MIT", + "dependencies": { + "@remix-run/node": "^2.15.0", + "@tanstack/form-core": "0.41.4", + "@tanstack/react-store": "^0.7.0", + "decode-formdata": "^0.8.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/start": "^1.43.13", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@tanstack/start": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-form/node_modules/@tanstack/react-store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.7.tgz", + "integrity": "sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.7", + "use-sync-external-store": "^1.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.100.14", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", + "integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.100.14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-router": { + "version": "1.170.9", + "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.9.tgz", + "integrity": "sha512-rXXztp6GyXFwResQR0jdvkf52wy/sAQqze3OR08+8uNM7nHir1P46qBrwg2TL5EEtX+0WUho4BZ/nMpAgQVB6A==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.0", + "@tanstack/react-store": "^0.9.3", + "@tanstack/router-core": "1.171.7", + "isbot": "^5.1.22" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-start": { + "version": "1.168.16", + "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.168.16.tgz", + "integrity": "sha512-ZkrGThxJG0SPg5V+VB4C+7KKx8kl1vo1lQY9FR2t6EqNKfZhl5N9qpkPOeyJNhse1pqe8Ihbv8LNL9c1/SohkQ==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.9", + "@tanstack/react-start-client": "1.168.6", + "@tanstack/react-start-rsc": "0.1.15", + "@tanstack/react-start-server": "1.167.11", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.5", + "@tanstack/start-plugin-core": "1.171.8", + "@tanstack/start-server-core": "1.169.6", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": "^2.0.0", + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0", + "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-start-client": { + "version": "1.168.6", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.168.6.tgz", + "integrity": "sha512-6G/ef89hx3yWfoHLKpDxDpgMsje889bh8tpy6SMeELrCW6ob8x0G+peoFpB5zhNVE03wCFXhImM6KkZFRwSyaQ==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.9", + "@tanstack/router-core": "1.171.7", + "@tanstack/start-client-core": "1.170.5" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-start-server": { + "version": "1.167.11", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.167.11.tgz", + "integrity": "sha512-7IOSbMJ7mvNbcI6/LAHsYbhR+oIxDfZlWSBau1E/l2tY2HSnKdtu+tj1yTY7zgwqEdn14IyGFSxpHe/eTFNB5g==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.0", + "@tanstack/react-router": "1.170.9", + "@tanstack/router-core": "1.171.7", + "@tanstack/start-client-core": "1.170.5", + "@tanstack/start-server-core": "1.169.6" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0" + } + }, + "node_modules/@tanstack/react-start/node_modules/@tanstack/react-start-rsc": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/@tanstack/react-start-rsc/-/react-start-rsc-0.1.15.tgz", + "integrity": "sha512-AdO+3OQicxFibsXEOIG2u2aRIAc1KIWMC98iiwaIUw/1ObP7uilZcYQynwdDeqsGKd0Ulr+tXZXebLdQwV4OeA==", + "license": "MIT", + "dependencies": { + "@tanstack/react-router": "1.170.9", + "@tanstack/react-start-server": "1.167.11", + "@tanstack/router-core": "1.171.7", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.5", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-plugin-core": "1.171.8", + "@tanstack/start-server-core": "1.169.6", + "@tanstack/start-storage-context": "1.167.9", + "pathe": "^2.0.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rspack/core": ">=2.0.0-0", + "@vitejs/plugin-rsc": ">=0.5.20", + "react": ">=18.0.0 || >=19.0.0", + "react-dom": ">=18.0.0 || >=19.0.0", + "react-server-dom-rspack": ">=0.0.2" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "@vitejs/plugin-rsc": { + "optional": true + }, + "react-server-dom-rspack": { + "optional": true + } + } + }, + "node_modules/@tanstack/react-store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.9.3.tgz", + "integrity": "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.9.3", + "use-sync-external-store": "^1.6.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/react-store/node_modules/@tanstack/store": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.9.3.tgz", + "integrity": "sha512-8reSzl/qGWGGVKhBoxXPMWzATSbZLZFWhwBAFO9NAyp0TxzfBP0mIrGb8CP8KrQTmvzXlR/vFPPUrHTLBGyFyw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-core": { + "version": "1.171.7", + "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.7.tgz", + "integrity": "sha512-AboyQD0OPIu0adJi6HeCupTU9Wx7zr4q4ceJYQdBL3mLH18m4M57XXdzJdtzOg/twl8DtWej0RGJ12ma8CV1iQ==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.0", + "cookie-es": "^3.0.0", + "seroval": "^1.5.4", + "seroval-plugins": "^1.5.4" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-generator": { + "version": "1.167.11", + "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.167.11.tgz", + "integrity": "sha512-Wnom12QTx0lreBQEL3zE2N88SDIT6xferScd46dnPCEYR8u+AkBBGRzlzZEpniIm+eF3byvIKBjYQgemdadoFg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.7", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "jiti": "^2.7.0", + "magic-string": "^0.30.21", + "prettier": "^3.5.0", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/router-generator/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@tanstack/router-plugin": { + "version": "1.168.12", + "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.168.12.tgz", + "integrity": "sha512-DqeJs+/LinTsTc6gIsloauHdCjePk6pmhpmcqrNWbNi8dS+QMj2UZ4HgU9JgOiYGznkd49i8BeQJFUxzASmr7Q==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", + "@tanstack/router-core": "1.171.7", + "@tanstack/router-generator": "1.167.11", + "@tanstack/router-utils": "1.162.1", + "@tanstack/virtual-file-routes": "1.162.0", + "chokidar": "^5.0.0", + "unplugin": "^3.0.0", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": ">=1.0.2 || ^2.0.0", + "@tanstack/react-router": "^1.170.9", + "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0", + "vite-plugin-solid": "^2.11.10 || ^3.0.0-0", + "webpack": ">=5.92.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "@tanstack/react-router": { + "optional": true + }, + "vite": { + "optional": true + }, + "vite-plugin-solid": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/@tanstack/router-plugin/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@tanstack/router-utils": { + "version": "1.162.1", + "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.162.1.tgz", + "integrity": "sha512-62layyTGmclHDQS/eidwKRfN1hhCKwViG7iEBcVmL0MXgcAB3OOucWCEcDDGd9Cu11H6b4QQ5oOo47MWIqwz0A==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/generator": "^7.28.5", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "ansis": "^4.1.0", + "babel-dead-code-elimination": "^1.0.12", + "diff": "^8.0.2", + "pathe": "^2.0.3", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-client-core": { + "version": "1.170.5", + "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.170.5.tgz", + "integrity": "sha512-rtL6JyEB7/zeylgOjwtFEtmTIwRUm4Mf4bEhp2yUVk8kB4zLlmYcpSAFbb/aIrLp/Yl9XS2dx3OxB7oIPUuplg==", + "license": "MIT", + "dependencies": { + "@tanstack/router-core": "1.171.7", + "@tanstack/start-fn-stubs": "1.162.0", + "@tanstack/start-storage-context": "1.167.9", + "seroval": "^1.5.4" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-fn-stubs": { + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.162.0.tgz", + "integrity": "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==", + "license": "MIT", + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-plugin-core": { + "version": "1.171.8", + "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.171.8.tgz", + "integrity": "sha512-Eoe83LD3N7AQgsjb+uRq+DjUE82+e8liYqzfIVoZ03t4+XfHtTbMBB+f6XK2kUzTv4QBG7ZmZU959jWwQHfVTQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "7.27.1", + "@babel/core": "^7.28.5", + "@babel/types": "^7.28.5", + "@rolldown/pluginutils": "1.0.1", + "@tanstack/router-core": "1.171.7", + "@tanstack/router-generator": "1.167.11", + "@tanstack/router-plugin": "1.168.12", + "@tanstack/router-utils": "1.162.1", + "@tanstack/start-client-core": "1.170.5", + "@tanstack/start-server-core": "1.169.6", + "exsolve": "^1.0.7", + "lightningcss": "^1.32.0", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "seroval": "^1.5.4", + "source-map": "^0.7.6", + "srvx": "^0.11.9", + "tinyglobby": "^0.2.15", + "ufo": "^1.5.4", + "vitefu": "^1.1.1", + "xmlbuilder2": "^4.0.3", + "zod": "^4.4.3" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@rsbuild/core": "^2.0.0", + "vite": ">=7.0.0" + }, + "peerDependenciesMeta": { + "@rsbuild/core": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@tanstack/start-plugin-core/node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/@tanstack/start-server-core": { + "version": "1.169.6", + "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.169.6.tgz", + "integrity": "sha512-Qf+Uinnc5Ak79QGD6pJsiK0IfUlWkyJNy+h9vTwOtVLBvmq/fR7GjBuf5ymidI/7jK/YQWVuw2aaEE6MHBC+iw==", + "license": "MIT", + "dependencies": { + "@tanstack/history": "1.162.0", + "@tanstack/router-core": "1.171.7", + "@tanstack/start-client-core": "1.170.5", + "@tanstack/start-storage-context": "1.167.9", + "fetchdts": "^0.1.6", + "h3-v2": "npm:h3@2.0.1-rc.20", + "seroval": "^1.5.4" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/start-storage-context": { + "version": "1.167.9", + "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.167.9.tgz", + "integrity": "sha512-RKxJA95HgZXu2lgTCzGi7pShtgz7vyvDTEPOJfJBBMHM2S7SX/WLYTHqfHcYvbOzREXOxEbDQ8Hvtt0Klq8VKg==", + "license": "MIT", + "dependencies": { + "@tanstack/router-core": "1.171.7" + }, + "engines": { + "node": ">=22.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", + "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/virtual-file-routes": { + "version": "1.162.0", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.162.0.tgz", + "integrity": "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==", + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", + "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", + "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@web3-storage/multipart-parser": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@web3-storage/multipart-parser/-/multipart-parser-1.0.0.tgz", + "integrity": "sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==", + "license": "(Apache-2.0 AND MIT)" + }, + "node_modules/@zxing/text-encoding": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz", + "integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==", + "license": "(Unlicense OR Apache-2.0)", + "optional": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ansis": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.0.tgz", + "integrity": "sha512-44mvgtPvohuU/70DdY5Oz2AIrLJ9k6/5x4KmoSvPwO+5Moijo0+N9D0fKbbYZQWP1hNm5CpOf+E01jhxG/r8xg==", + "license": "ISC", + "engines": { + "node": ">=14" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-dead-code-elimination": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", + "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.7", + "@babel/parser": "^7.23.6", + "@babel/traverse": "^7.23.7", + "@babel/types": "^7.23.6" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", + "license": "MIT", + "dependencies": { + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz", + "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==", + "license": "MIT" + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz", + "integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-formdata": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/decode-formdata/-/decode-formdata-0.8.0.tgz", + "integrity": "sha512-iUzDgnWsw5ToSkFY7VPFA5Gfph6ROoOxOB7Ybna4miUSzLZ4KaSJk6IAB2AdW6+C9vCVWhjjNA4gjT6wF3eZHQ==", + "license": "MIT" + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/diff": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.364", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.364.tgz", + "integrity": "sha512-G/dYE3+AYhyHwzTwg8UbnXf7zqMERYh7l2jJ3QujhFsH8agSYwtnGAR2aZ7f0AakIKJXd5En/Hre4igIUrdlYw==", + "license": "ISC" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "devOptional": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fetchdts": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/fetchdts/-/fetchdts-0.1.7.tgz", + "integrity": "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/h3-v2": { + "name": "h3", + "version": "2.0.1-rc.20", + "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.20.tgz", + "integrity": "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==", + "license": "MIT", + "dependencies": { + "rou3": "^0.8.1", + "srvx": "^0.11.13" + }, + "bin": { + "h3": "bin/h3.mjs" + }, + "engines": { + "node": ">=20.11.1" + }, + "peerDependencies": { + "crossws": "^0.4.1" + }, + "peerDependenciesMeta": { + "crossws": { + "optional": true + } + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isbot": { + "version": "5.1.40", + "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.1.40.tgz", + "integrity": "sha512-yNeeynhhtIVRBk12tBV4eHNxwB42HzR4Q3Ea7vCOiJhImGaAIdIMrbJtacQlBizGLjUPw+akkFI5Dn9T70XoVQ==", + "license": "Unlicense", + "engines": { + "node": ">=18" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", + "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rou3": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.8.1.tgz", + "integrity": "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==", + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/seroval": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.5.4.tgz", + "integrity": "sha512-46uFvgrXTVxZcUorgSSRZ4y+ieqLLQRMlG4bnCZKW3qI6BZm7Rg4ntMW4p1mILEEBZWrFlcpp0AyIIlM6jD9iw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/seroval-plugins": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.5.4.tgz", + "integrity": "sha512-S0xQPhUTefAhNvNWFg0c1J8qJArHt5KdtJ/cFAofo06KD1MVSeFWyl4iiu+ApDIuw0WhjpOfCdgConOfAnLgkw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "seroval": "^1.0" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/srvx": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.16.tgz", + "integrity": "sha512-bp07zRuycfTY43IjAvvTFnmnJi8ikW0VFiHwOhhYcVW/L4xQ1XY4PAd4Nuum1rsA17C39zL7x+CDhrn5AL32Rw==", + "license": "MIT", + "bin": { + "srvx": "bin/srvx.mjs" + }, + "engines": { + "node": ">=20.16.0" + } + }, + "node_modules/stream-slice": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", + "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", + "license": "MIT" + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "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.7", + "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 || ^5.0 || ^6.0", + "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" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tailwindcss/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/turbo-stream": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.1.tgz", + "integrity": "sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==", + "license": "ISC" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", + "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.0.0.tgz", + "integrity": "sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/web-encoding": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/web-encoding/-/web-encoding-1.1.5.tgz", + "integrity": "sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==", + "license": "MIT", + "dependencies": { + "util": "^0.12.3" + }, + "optionalDependencies": { + "@zxing/text-encoding": "0.9.0" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.21.tgz", + "integrity": "sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==", + "license": "MIT", + "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" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder2": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", + "integrity": "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==", + "license": "MIT", + "dependencies": { + "@oozcitak/dom": "^2.0.2", + "@oozcitak/infra": "^2.0.2", + "@oozcitak/util": "^10.0.0", + "js-yaml": "^4.1.1" + }, + "engines": { + "node": ">=20.0" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/apps/studio/package.json b/apps/studio/package.json new file mode 100644 index 000000000..5b97c2544 --- /dev/null +++ b/apps/studio/package.json @@ -0,0 +1,46 @@ +{ + "name": "@karrio/studio", + "version": "1.0.0", + "private": true, + "type": "module", + "comment": "Standalone app — intentionally OUTSIDE the turborepo workspaces. Has its own lockfile/node_modules. No @karrio/* deps; UI via shadcn + Tailwind; Karrio API via a self-contained client with local types.", + "scripts": { + "dev": "vite dev --port 3003", + "build": "vite build", + "start": "node .output/server/index.mjs", + "lint": "eslint ." + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.1.4", + "@radix-ui/react-dropdown-menu": "^2.1.4", + "@radix-ui/react-label": "^2.1.1", + "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", + "@tanstack/react-form": "^0.41.0", + "@tanstack/react-query": "^5.59.0", + "@tanstack/react-router": "^1.95.0", + "@tanstack/react-start": "^1.95.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwind-merge": "^2.5.5", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" + }, + "devDependencies": { + "@tanstack/router-plugin": "^1.95.0", + "@types/node": "^22.19.19", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^5.0.0", + "autoprefixer": "^10.4.20", + "eslint": "^8.48.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "^5.6.3", + "vite": "^7.0.0", + "vite-tsconfig-paths": "^5.1.0" + } +} diff --git a/apps/studio/postcss.config.js b/apps/studio/postcss.config.js new file mode 100644 index 000000000..2aa7205d4 --- /dev/null +++ b/apps/studio/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/studio/sandbox/README.md b/apps/studio/sandbox/README.md new file mode 100644 index 000000000..313beff27 --- /dev/null +++ b/apps/studio/sandbox/README.md @@ -0,0 +1,57 @@ +# Karrio Studio — Sandbox & seeded sample data + +Brings up a local Karrio backend (SQLite) with a default admin and **provisioned +sample data for every Studio feature**, so Studio can be driven end-to-end and +the live Playwright project (`studio-live`) can run against real data. + +## 1. Python env (one time) + +Official setup: + +```bash +./bin/install-dev +``` + +…or a lean install with uv: + +```bash +uv venv .venv/karrio --python 3.12 +source .venv/karrio/bin/activate +uv pip install -r requirements.server.dev.txt +``` + +> Restricted-egress note: the `documents` module fetches a CDN stylesheet at +> import. `generator.py` is offline-resilient (skips it when unreachable) so the +> server boots air-gapped. + +## 2. Bring up the sandbox + seed + +```bash +./bin/studio-sandbox # migrate → create admin → seed → run API :5002 +# or seed only (server already running): +./bin/studio-sandbox --seed-only +``` + +Admin: `admin@example.com` / `demo` (from `.env` `ADMIN_EMAIL`/`ADMIN_PASSWORD`). + +The seed (`apps/studio/sandbox/seed.py`, run via `karrio shell`) provisions: +addresses, parcels, products, a test carrier connection, trackers, shipments, +and orders — idempotently (safe to re-run). + +## 3. Run Studio against it + +```bash +cp apps/studio/.env.sample apps/studio/.env # KARRIO_API / VITE_KARRIO_API = http://localhost:5002 +npm run dev -w @karrio/studio # http://localhost:3003 +``` + +## 4. Live integration tests + +```bash +cd packages/e2e +KARRIO_LIVE=1 KARRIO_STUDIO_URL=http://localhost:3003 \ + npx playwright test --project=studio-live +``` + +The default (mocked) `studio` Playwright project needs no backend; `studio-live` +exercises the real login + API-backed screens against this seeded sandbox. diff --git a/apps/studio/sandbox/seed.py b/apps/studio/sandbox/seed.py new file mode 100644 index 000000000..9c212affb --- /dev/null +++ b/apps/studio/sandbox/seed.py @@ -0,0 +1,184 @@ +# seed.py — provision HIGH-FIDELITY sample data for Karrio Studio. +# Run: cat apps/studio/sandbox/seed.py | karrio shell +# Idempotent & defensive. Every shipment/order gets real shipper, recipient, +# parcels, line items, and itemized charges so no Studio section renders empty. +# +# Field types on this Karrio version (verified): +# Shipment.shipper / recipient -> JSON dict +# Shipment.parcels -> JSON list of dicts +# Shipment.selected_rate -> JSON dict (Rate; needs carrier_id) +# Order.shipping_to -> JSON dict +# Order.line_items -> JSON list of dicts +from django.contrib.auth import get_user_model + +U = get_user_model() +user = U.objects.order_by("date_joined").first() or U.objects.first() +print("seeding as:", getattr(user, "email", None)) + + +def log(section, fn): + try: + print(f" ok {section}: {fn()}") + except Exception as e: # noqa: BLE001 + import traceback + print(f" FAIL {section}: {type(e).__name__}: {e}") + traceback.print_exc() + + +SHIPPER = dict(person_name="Daniel Kovic", company_name="Acme Inc.", address_line1="432 Park Ave, Suite 4", + city="Brooklyn", state_code="NY", postal_code="11201", country_code="US", + email="ops@acme.shop", phone_number="+1 718 555 0142", residential=False) +RECIPIENTS = [ + dict(person_name="Alicia Romero", address_line1="55 Water St", city="Brooklyn", state_code="NY", + postal_code="11201", country_code="US", email="alicia@example.com", phone_number="+1 212 555 0198", residential=True), + dict(person_name="Jane Doe", company_name="Northwest Retail", address_line1="980 Howe St", city="Vancouver", + state_code="BC", postal_code="V6Z 1N9", country_code="CA", email="jane@example.com", phone_number="+1 604 555 0199"), + dict(person_name="Helmut Strauss", company_name="Strauss GmbH", address_line1="Friedrichstrasse 88", city="Berlin", + state_code="BE", postal_code="10117", country_code="DE", email="helmut@example.de", phone_number="+49 30 5550 1234"), + dict(person_name="Owen Wright", address_line1="700 Bay St", city="Toronto", state_code="ON", + postal_code="M5G 1Z8", country_code="CA", email="owen@example.ca", phone_number="+1 416 555 0110", residential=True), +] + + +def parcel_dict(ref): + return dict(weight=2.0, weight_unit="KG", width=30, height=20, length=25, + dimension_unit="CM", packaging_type="BOX", reference_number=ref, is_document=False) + + +def rate_dict(carrier, svc, charge, cur): + base, fuel = round(charge * 0.8, 2), round(charge * 0.15, 2) + return { + "id": "rate_" + carrier, "object_type": "rate", "carrier_name": carrier, + "carrier_id": carrier + "_acct", "service": svc, "total_charge": charge, "currency": cur, + "transit_days": 2, "test_mode": False, + "extra_charges": [ + {"name": "Base charge", "amount": base, "currency": cur}, + {"name": "Fuel surcharge", "amount": fuel, "currency": cur}, + ], + } + + +SHIPMENTS = [ + ("ups", "ups_2nd_day_air", "shipped", 12.40, "USD", "1Z999AA10123456784", "ORDER-11335", 0), + ("canadapost", "canadapost_expedited_parcel", "delivered", 9.80, "CAD", "CP123456789CA", "ORDER-11333", 1), + ("dhl_express", "dhl_express_worldwide", "in_transit", 42.80, "USD", "1231006943", "ORDER-11334", 2), + ("fedex", "fedex_priority_overnight", "shipped", 28.10, "USD", "794651413733", "REF-1112", 3), +] + + +def seed_shipments(): + import karrio.server.manager.models as m + created = 0 + for carrier, svc, status, charge, cur, tn, ref, ridx in SHIPMENTS: + if m.Shipment.objects.filter(tracking_number=tn).exists(): + continue + m.Shipment.objects.create( + created_by=user, status=status, test_mode=False, tracking_number=tn, reference=ref, + shipper=dict(SHIPPER), recipient=dict(RECIPIENTS[ridx]), + parcels=[parcel_dict(ref)], + selected_rate=rate_dict(carrier, svc, charge, cur), + ) + created += 1 + return f"{created} created, {m.Shipment.objects.count()} total" + + +def seed_trackers(): + import karrio.server.manager.models as m + samples = [ + ("1Z999AA10123456784", "ups", "in_transit", "2026-06-01", + [("Order processed", "Brooklyn, NY", "2026-05-28", "10:02"), + ("Departed facility", "Newark, NJ", "2026-05-28", "23:48"), + ("In transit", "Philadelphia, PA", "2026-05-29", "06:22")]), + ("794651413733", "fedex", "delivered", "2026-05-27", + [("Picked up", "Seattle, WA", "2026-05-26", "09:44"), + ("Out for delivery", "Toronto, ON", "2026-05-27", "08:10"), + ("Delivered — front desk", "Toronto, ON", "2026-05-27", "15:48")]), + ("1231006943", "dhl_express", "in_transit", "2026-05-30", + [("Shipment picked up", "Berlin, DE", "2026-05-28", "07:48"), + ("Customs cleared", "Leipzig, DE", "2026-05-29", "02:14")]), + ("CP123456789CA", "canadapost", "delivered", "2026-05-27", + [("Item processed", "Vancouver, BC", "2026-05-25", "06:03"), + ("Delivered", "Vancouver, BC", "2026-05-27", "11:20")]), + ] + created = 0 + for tn, carrier, status, eta, evs in samples: + if m.Tracking.objects.filter(tracking_number=tn).exists(): + continue + m.Tracking.objects.create( + created_by=user, tracking_number=tn, status=status, test_mode=False, estimated_delivery=eta, + events=[{"description": d, "location": loc, "date": dt, "time": tm} for d, loc, dt, tm in evs], + meta={"carrier_name": carrier}, + ) + created += 1 + return f"{created} created, {m.Tracking.objects.count()} total" + + +def seed_templates(): + import karrio.server.graph.models as g + n = 0 + addrs = [("Acme Brooklyn HQ", "Daniel Kovic", "Acme Inc.", "432 Park Ave", "Brooklyn", "NY", "11201", "US", True), + ("Vancouver Warehouse", "Mei Tanaka", "Acme Inc.", "980 Powell St", "Vancouver", "BC", "V6A 1H9", "CA", False), + ("Berlin Hub", "Helmut Strauss", "Acme GmbH", "Friedrichstrasse 88", "Berlin", "BE", "10117", "DE", False)] + for label, person, company, line1, city, state, postal, country, default in addrs: + if g.Template.objects.filter(label=label).exists(): + continue + a = g.Address.objects.create(created_by=user, person_name=person, company_name=company, address_line1=line1, + city=city, state_code=state, postal_code=postal, country_code=country) + g.Template.objects.create(created_by=user, label=label, is_default=default, address=a) + n += 1 + parcels = [("Small Box", 20, 15, 10, 0.5, True), ("Medium Box", 35, 25, 20, 2.0, False), ("Poly Mailer", 30, 25, 5, 0.3, False)] + for label, l, w, h, wt, default in parcels: + if g.Template.objects.filter(label=label).exists(): + continue + p = g.Parcel.objects.create(created_by=user, length=l, width=w, height=h, dimension_unit="CM", + weight=wt, weight_unit="KG", packaging_type="BOX") + g.Template.objects.create(created_by=user, label=label, is_default=default, parcel=p) + n += 1 + return f"{n} created, {g.Template.objects.count()} total" + + +def seed_orders(): + import karrio.server.orders.models as om + created = 0 + samples = [ + ("#11335", "unfulfilled", "shopify", 0, [("Wireless Headphones", "ACM-1001", 1, 84.00), ("USB-C Cable 2m", "ACM-2410", 2, 12.00)]), + ("#11334", "fulfilled", "medusa", 1, [("Widget Pro 2026", "WDG-PRO-001", 1, 199.00)]), + ("#11333", "partial", "bigcommerce", 3, [("Sticker Pack", "ACM-3300", 5, 4.00)]), + ] + for oid, status, source, ridx, items in samples: + if om.Order.objects.filter(order_id=oid).exists(): + continue + line_items = [ + {"title": title, "sku": sku, "quantity": qty, "value_amount": val, "value_currency": "USD", + "weight": 0.4, "weight_unit": "KG", "origin_country": "US", "hs_code": "8518.30"} + for title, sku, qty, val in items + ] + om.Order.objects.create(created_by=user, order_id=oid, status=status, source=source, + test_mode=False, shipping_to=dict(RECIPIENTS[ridx]), line_items=line_items) + created += 1 + return f"{created} created, {om.Order.objects.count()} total" + + +def selfcheck(): + # Fail loudly if any shipment is missing addresses/parcels/charges. + import karrio.server.manager.models as m + issues = [] + for s in m.Shipment.objects.all(): + r = s.selected_rate or {} + if not s.shipper or not s.recipient: + issues.append(f"{s.tracking_number}: missing address") + if not s.parcels: + issues.append(f"{s.tracking_number}: no parcels") + if not (r.get("extra_charges")): + issues.append(f"{s.tracking_number}: no charges") + if not r.get("carrier_id"): + issues.append(f"{s.tracking_number}: rate missing carrier_id") + return "all shipments complete" if not issues else "ISSUES: " + "; ".join(issues) + + +log("shipments", seed_shipments) +log("trackers", seed_trackers) +log("templates (addresses/parcels)", seed_templates) +log("orders", seed_orders) +log("selfcheck", selfcheck) +print("seed complete.") diff --git a/apps/studio/src/components/ErrorBoundary.tsx b/apps/studio/src/components/ErrorBoundary.tsx new file mode 100644 index 000000000..2ec0c467c --- /dev/null +++ b/apps/studio/src/components/ErrorBoundary.tsx @@ -0,0 +1,36 @@ +// ErrorBoundary.tsx — catches render errors, reports via the monitoring seam, +// and shows a friendly recoverable fallback (UX resilience). +import { Component, type ErrorInfo, type ReactNode } from "react"; +import { captureError } from "~/lib/monitoring"; + +type Props = { children: ReactNode }; +type State = { error: Error | null }; + +export class ErrorBoundary extends Component { + state: State = { error: null }; + + static getDerivedStateFromError(error: Error): State { + return { error }; + } + + componentDidCatch(error: Error, info: ErrorInfo) { + captureError(error, { componentStack: info.componentStack }); + } + + render() { + if (this.state.error) { + return ( +
+
+

Something went wrong

+

An unexpected error occurred. The team has been notified.

+ +
+
+ ); + } + return this.props.children; + } +} diff --git a/apps/studio/src/components/auth/AuthShell.tsx b/apps/studio/src/components/auth/AuthShell.tsx new file mode 100644 index 000000000..6fb9bab51 --- /dev/null +++ b/apps/studio/src/components/auth/AuthShell.tsx @@ -0,0 +1,58 @@ +// AuthShell.tsx — split-panel chrome for pre-auth screens. +import type { ReactNode } from "react"; + +export function AuthShell({ children }: { children: ReactNode }) { + return ( +
+ +
+
{children}
+
+
+ ); +} + +export function PasswordField({ + value, + onChange, + show, + onToggle, + id, + placeholder, + error, +}: { + value: string; + onChange: (v: string) => void; + show: boolean; + onToggle: () => void; + id: string; + placeholder?: string; + error?: boolean; +}) { + return ( +
+ onChange(e.target.value)} + autoComplete="current-password" + /> + +
+ ); +} diff --git a/apps/studio/src/components/overlays/CommandPalette.tsx b/apps/studio/src/components/overlays/CommandPalette.tsx new file mode 100644 index 000000000..02a4fe8b8 --- /dev/null +++ b/apps/studio/src/components/overlays/CommandPalette.tsx @@ -0,0 +1,117 @@ +// CommandPalette.tsx — ⌘K overlay: navigate + run actions. +import { useEffect, useMemo, useRef, useState } from "react"; +import { NAV, type Mode } from "~/lib/modes"; + +export type Command = { + id: string; + label: string; + kind: string; + run: () => void; +}; + +export function CommandPalette({ + open, + onClose, + onGo, + onAction, +}: { + open: boolean; + onClose: () => void; + onGo: (route: string) => void; + onAction: (id: string) => void; +}) { + const [q, setQ] = useState(""); + const [active, setActive] = useState(0); + const inputRef = useRef(null); + + const commands = useMemo(() => { + const navCmds: Command[] = (Object.keys(NAV) as Mode[]).flatMap((mode) => + NAV[mode].flatMap((g) => + g.items.map((it) => ({ + id: `nav:${it.route}`, + label: it.label, + kind: mode, + run: () => onGo(it.route), + })), + ), + ); + const actions: Command[] = [ + { id: "new-shipment", label: "New shipment", kind: "action", run: () => onAction("shipment") }, + { id: "track", label: "Track a shipment", kind: "action", run: () => onAction("tracker") }, + { id: "toggle-theme", label: "Toggle theme", kind: "action", run: () => onAction("theme") }, + { id: "appearance", label: "Customize appearance", kind: "action", run: () => onAction("appearance") }, + { id: "workbench", label: "Open Workbench", kind: "action", run: () => onAction("workbench") }, + ]; + // De-dupe nav routes that appear in multiple groups by id. + const seen = new Set(); + return [...navCmds, ...actions].filter((c) => (seen.has(c.id) ? false : (seen.add(c.id), true))); + }, [onGo, onAction]); + + const filtered = useMemo(() => { + const s = q.trim().toLowerCase(); + if (!s) return commands; + return commands.filter((c) => c.label.toLowerCase().includes(s) || c.kind.includes(s)); + }, [q, commands]); + + useEffect(() => { + if (open) { + setQ(""); + setActive(0); + requestAnimationFrame(() => inputRef.current?.focus()); + } + }, [open]); + + useEffect(() => setActive(0), [q]); + + if (!open) return null; + + const onKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Escape") return onClose(); + if (e.key === "ArrowDown") { + e.preventDefault(); + setActive((a) => Math.min(a + 1, filtered.length - 1)); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + setActive((a) => Math.max(a - 1, 0)); + } else if (e.key === "Enter") { + e.preventDefault(); + filtered[active]?.run(); + onClose(); + } + }; + + return ( +
+
e.stopPropagation()} role="dialog" aria-label="Command palette"> + setQ(e.target.value)} + onKeyDown={onKeyDown} + data-testid="cp-input" + aria-label="Command palette search" + /> +
+ {filtered.length === 0 &&
No results
} + {filtered.map((c, i) => ( +
setActive(i)} + onClick={() => { + c.run(); + onClose(); + }} + data-testid={`cp-item-${c.id}`} + > + {c.label} + {c.kind} +
+ ))} +
+
+
+ ); +} diff --git a/apps/studio/src/components/overlays/TweaksPanel.tsx b/apps/studio/src/components/overlays/TweaksPanel.tsx new file mode 100644 index 000000000..3f1d630b4 --- /dev/null +++ b/apps/studio/src/components/overlays/TweaksPanel.tsx @@ -0,0 +1,135 @@ +// TweaksPanel.tsx — self-editable appearance (G1). Accent / density / font / +// theme, applied live via CSS variables and persisted via preferences.ts +// (localStorage + async backend sync via update_user GraphQL mutation). +import { useState } from "react"; +import { Sheet } from "~/components/ui/Sheet"; +import { Section } from "~/components/ui/detail"; +import { + applyAccent, + applyDensity, + applyFont, + applyTheme, + loadPrefs, + savePrefs, + syncToBackend, + type Density, + type FontStack, +} from "~/lib/karrio/preferences"; +import { useKarrioCtx } from "~/lib/karrio/session"; + +// Canonical accent palette (kept here so the constant is co-located with the UI). +export const ACCENTS = ["#8B5CF6", "#3B82F6", "#10B981", "#F97316", "#E11D48"] as const; + +export function TweaksPanel({ + open, + onClose, + theme, + onTheme, +}: { + open: boolean; + onClose: () => void; + theme: "dark" | "light"; + onTheme: () => void; +}) { + const ctx = useKarrioCtx(); + const stored = loadPrefs(); + const [accent, setAccent] = useState(stored.accent); + const [density, setDensity] = useState(stored.density); + const [font, setFont] = useState(stored.font_stack); + + if (!open) return null; + + return ( + +
+
+
+ {(["dark", "light"] as const).map((t) => ( + + ))} +
+
+ +
+
+ {ACCENTS.map((c) => ( +
+
+ +
+ +
+ +
+ +
+
+
+ ); +} diff --git a/apps/studio/src/components/overlays/Workbench.tsx b/apps/studio/src/components/overlays/Workbench.tsx new file mode 100644 index 000000000..c27a072d0 --- /dev/null +++ b/apps/studio/src/components/overlays/Workbench.tsx @@ -0,0 +1,63 @@ +// Workbench.tsx — Stripe-style developer overlay (⌘`). Always dark. +import { useEffect, useState } from "react"; +import { Icon } from "~/components/ui/icons"; + +const NAV = ["Logs", "Events", "Webhooks", "GraphiQL", "Health", "Workers", "Tracing"] as const; + +const LOGS = [ + { id: "l1", method: "POST", status: 201, path: "/v1/shipments", ms: 612 }, + { id: "l2", method: "GET", status: 200, path: "/v1/trackers", ms: 84 }, + { id: "l3", method: "POST", status: 422, path: "/v1/shipments", ms: 38 }, + { id: "l4", method: "GET", status: 200, path: "/v1/connections", ms: 51 }, +]; + +export function Workbench({ open, onClose }: { open: boolean; onClose: () => void }) { + const [tab, setTab] = useState<(typeof NAV)[number]>("Logs"); + const [full, setFull] = useState(false); + + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, onClose]); + + return ( +
+
+ {NAV.map((n) => ( +
setTab(n)} data-testid={`wb-nav-${n.toLowerCase()}`}> + {n} +
+ ))} +
+
+
+ Workbench · {tab} +
+ + +
+
+
+ {tab === "Logs" ? ( +
+ {LOGS.map((l) => ( +
+ {l.method} + {l.status} + {l.path} + {l.ms}ms +
+ ))} +
+ ) : ( +
{tab} view — live data wires to the Karrio admin API.
+ )} +
+
+
+ ); +} diff --git a/apps/studio/src/components/shell/Sidebar.tsx b/apps/studio/src/components/shell/Sidebar.tsx new file mode 100644 index 000000000..c0fb4d1ed --- /dev/null +++ b/apps/studio/src/components/shell/Sidebar.tsx @@ -0,0 +1,92 @@ +// Sidebar.tsx — mode-driven navigation (Ship / Build / Govern). +import { Icon, MODE_LABELS, NAV, type Mode } from "~/lib/modes"; +import { UserMenu } from "~/components/shell/UserMenu"; +import { WorkspaceMenu } from "~/components/shell/WorkspaceMenu"; +import { useFeatureFlags, type FeatureFlag } from "~/lib/karrio/references"; + +const MODES: Mode[] = ["ship", "build", "govern"]; + +export function Sidebar({ + route, + mode, + collapsed, + onGo, + onMode, + onTweaks, +}: { + route: string; + mode: Mode; + collapsed: boolean; + onGo: (route: string) => void; + onMode: (mode: Mode) => void; + onTweaks: () => void; +}) { + const { isEnabled } = useFeatureFlags(); + return ( + + ); +} diff --git a/apps/studio/src/components/shell/Topbar.tsx b/apps/studio/src/components/shell/Topbar.tsx new file mode 100644 index 000000000..25a4f4172 --- /dev/null +++ b/apps/studio/src/components/shell/Topbar.tsx @@ -0,0 +1,134 @@ +// Topbar.tsx — search trigger, test-mode, theme, workbench, app launcher, create. +import { useEffect, useRef, useState } from "react"; +import { Icon } from "~/components/ui/icons"; +import type { Mode } from "~/lib/modes"; +import type { Theme } from "~/lib/theme"; + +export type CreateKind = + | "shipment" + | "tracker" + | "order" + | "pickup" + | "plugin" + | "apikey"; + +export function Topbar({ + mode, + route, + theme, + testMode, + onToggleSidebar, + onPalette, + onTestMode, + onTheme, + onTweaks, + onOpenWorkbench, + onCreate, +}: { + mode: Mode; + route: string; + theme: Theme; + testMode: boolean; + onToggleSidebar: () => void; + onPalette: () => void; + onTestMode: (on: boolean) => void; + onTheme: () => void; + onTweaks: () => void; + onOpenWorkbench: () => void; + onCreate: (kind: CreateKind) => void; +}) { + const [createOpen, setCreateOpen] = useState(false); + const createRef = useRef(null); + + useEffect(() => { + const onClick = (e: MouseEvent) => { + if (createRef.current && !createRef.current.contains(e.target as Node)) { + setCreateOpen(false); + } + }; + document.addEventListener("mousedown", onClick); + return () => document.removeEventListener("mousedown", onClick); + }, []); + + const showLauncher = (mode === "ship" || mode === "build") && route !== "apps"; + + return ( +
+ + +
+ + + + + {showLauncher && ( + + )} +
+ + {createOpen && ( +
+ } label="New shipment" kbd="⌘L" onClick={() => fire("shipment")} /> + } label="Track a shipment" kbd="⌘T" onClick={() => fire("tracker")} /> + } label="New order" onClick={() => fire("order")} /> + } label="Schedule a pickup" onClick={() => fire("pickup")} /> +
+ } label="Install a plugin" onClick={() => fire("plugin")} /> + } label="Generate API key" onClick={() => fire("apikey")} /> +
+ )} +
+
+
+ ); + + function fire(kind: CreateKind) { + setCreateOpen(false); + onCreate(kind); + } +} + +function MenuItem({ + icon, + label, + kbd, + onClick, +}: { + icon: React.ReactNode; + label: string; + kbd?: string; + onClick: () => void; +}) { + return ( +
+ {icon} + {label} + {kbd && {kbd}} +
+ ); +} diff --git a/apps/studio/src/components/shell/UserMenu.tsx b/apps/studio/src/components/shell/UserMenu.tsx new file mode 100644 index 000000000..bc23a5086 --- /dev/null +++ b/apps/studio/src/components/shell/UserMenu.tsx @@ -0,0 +1,75 @@ +// UserMenu.tsx — sidebar-foot account menu: real user identity + sign out. +import { useEffect, useRef, useState } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { Icon } from "~/components/ui/icons"; +import { useSession } from "~/lib/karrio/session"; +import { logout } from "~/server/auth"; + +export function UserMenu({ onGo, onTweaks }: { onGo: (route: string) => void; onTweaks: () => void }) { + const { email } = useSession(); + const qc = useQueryClient(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + const onDoc = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener("mousedown", onDoc); + return () => document.removeEventListener("mousedown", onDoc); + }, [open]); + + const name = email ?? "Account"; + const initials = (email ?? "U").replace(/@.*/, "").slice(0, 2).toUpperCase(); + + const signOut = async () => { + try { + await logout(); + } catch { + /* even if the server call fails, clear the client session */ + } + qc.setQueryData(["studio-session"], null); + window.location.assign("/login"); // hard nav clears all in-memory state + }; + + return ( +
+ + + {open && ( +
+
+
{name}
+
{email ? "Account" : "Not signed in"}
+
+
{ setOpen(false); onGo("settings"); }}> + Account settings +
+
{ setOpen(false); onTweaks(); }}> + Appearance +
+
+
+ Sign out +
+
+ )} +
+ ); +} diff --git a/apps/studio/src/components/shell/WorkspaceMenu.tsx b/apps/studio/src/components/shell/WorkspaceMenu.tsx new file mode 100644 index 000000000..6192ef9c3 --- /dev/null +++ b/apps/studio/src/components/shell/WorkspaceMenu.tsx @@ -0,0 +1,71 @@ +// WorkspaceMenu.tsx — sidebar workspace/org switcher. OSS Karrio is single-tenant +// (no `organizations` in the GraphQL schema), so this presents the connected +// workspace honestly + settings, and notes that multi-org is an Enterprise +// feature rather than faking an org switch. +import { useEffect, useRef, useState } from "react"; +import { Icon } from "~/components/ui/icons"; +import { useSession } from "~/lib/karrio/session"; + +export function WorkspaceMenu({ onGo }: { onGo: (route: string) => void }) { + const { email, ctx } = useSession(); + const [open, setOpen] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (!open) return; + const onDoc = (e: MouseEvent) => { + if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false); + }; + document.addEventListener("mousedown", onDoc); + return () => document.removeEventListener("mousedown", onDoc); + }, [open]); + + // Honest workspace identity: the connected Karrio deployment host. + let host = "workspace"; + try { + host = new URL(ctx.baseUrl).host; + } catch { + /* keep fallback */ + } + + return ( +
+ + + {open && ( +
+
+
Karrio Studio
+
{email ?? host}
+
+
{ setOpen(false); onGo("settings"); }}> + Workspace settings +
+
{ setOpen(false); onGo("connections"); }}> + Carrier connections +
+
+
+ + Multiple organizationsEnterprise +
+
+ )} +
+ ); +} diff --git a/apps/studio/src/components/ui/CarrierLogo.tsx b/apps/studio/src/components/ui/CarrierLogo.tsx new file mode 100644 index 000000000..313506f23 --- /dev/null +++ b/apps/studio/src/components/ui/CarrierLogo.tsx @@ -0,0 +1,39 @@ +// CarrierLogo.tsx — square carrier badge (ported from the design handoff) + +export const CARRIERS: Record = { + ups: { name: "UPS", bg: "#351B0E", fg: "#FFB81C", abbr: "UPS" }, + fedex: { name: "FedEx", bg: "#4D148C", fg: "#FF6600", abbr: "FDX" }, + dhl: { name: "DHL", bg: "#FFCC00", fg: "#D40511", abbr: "DHL" }, + usps: { name: "USPS", bg: "#004B87", fg: "#fff", abbr: "USPS" }, + canpost: { name: "Canada Post", bg: "#EE2D24", fg: "#fff", abbr: "CP" }, + purolator: { name: "Purolator", bg: "#1C3F94", fg: "#fff", abbr: "PUR" }, + royalmail: { name: "Royal Mail", bg: "#E60000", fg: "#fff", abbr: "RM" }, + landmark: { name: "Landmark", bg: "#E0153A", fg: "#fff", abbr: "LM" }, + smartkargo: { name: "SmartKargo", bg: "#FFFFFF", fg: "#1A66B5", abbr: "SK" }, + dpd: { name: "DPD", bg: "#DC0032", fg: "#fff", abbr: "DPD" }, + australia: { name: "Australia Post", bg: "#D70000", fg: "#fff", abbr: "AUS" }, + tnt: { name: "TNT", bg: "#FF6600", fg: "#fff", abbr: "TNT" }, + aramex: { name: "Aramex", bg: "#E10A17", fg: "#fff", abbr: "ARX" }, +}; + +export function CarrierLogo({ + carrier, + size = "md", +}: { + carrier: string; + size?: "sm" | "md" | "lg"; +}) { + const meta = CARRIERS[carrier] || { bg: "#52525B", fg: "#fff", abbr: "?" }; + return ( +
+ {meta.abbr} +
+ ); +} diff --git a/apps/studio/src/components/ui/Chart.tsx b/apps/studio/src/components/ui/Chart.tsx new file mode 100644 index 000000000..dcc2aacab --- /dev/null +++ b/apps/studio/src/components/ui/Chart.tsx @@ -0,0 +1,68 @@ +// Chart.tsx — dependency-free inline-SVG charts for usage time-series. +// Renders a smooth area/line series scaled to its container, with a baseline +// and an optional value formatter. Keeps Studio standalone (no chart lib). +import { useId } from "react"; +import type { UsagePoint } from "~/lib/karrio/types"; + +type Fmt = "number" | "currency"; + +const fmtValue = (n: number, fmt?: Fmt) => + fmt === "currency" ? `$${n.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : n.toLocaleString(); + +export function AreaChart({ + points, + format, + height = 64, + testId, +}: { + points: UsagePoint[]; + format?: Fmt; + height?: number; + testId?: string; +}) { + const gid = useId().replace(/[:]/g, ""); + const W = 100; // viewBox width units (path uses % via preserveAspectRatio="none") + const H = height; + const pad = 4; + + if (points.length === 0) { + return ( +
+ No data for this period. +
+ ); + } + + const vals = points.map((p) => p.count); + const max = Math.max(...vals, 1); + const min = Math.min(...vals, 0); + const span = max - min || 1; + const n = points.length; + const x = (i: number) => (n === 1 ? W / 2 : (i / (n - 1)) * W); + const y = (v: number) => pad + (1 - (v - min) / span) * (H - pad * 2); + + const line = points.map((p, i) => `${i === 0 ? "M" : "L"}${x(i).toFixed(2)},${y(p.count).toFixed(2)}`).join(" "); + const area = `${line} L${W},${H} L0,${H} Z`; + const last = points[points.length - 1]; + + return ( +
+ + + + + + + + + + + +
+ {points[0]?.date?.slice(0, 10)} + {fmtValue(last.count, format)} + {last.date?.slice(0, 10)} +
+
+ ); +} diff --git a/apps/studio/src/components/ui/NotAvailable.tsx b/apps/studio/src/components/ui/NotAvailable.tsx new file mode 100644 index 000000000..1bc280c78 --- /dev/null +++ b/apps/studio/src/components/ui/NotAvailable.tsx @@ -0,0 +1,33 @@ +// NotAvailable.tsx — honest empty state for Studio features that have no source +// in the open-source Karrio API (app marketplace, plugin registry, MCP server +// proxy, multi-tenant accounts). Never fabricate data/status for these. +import { Icon } from "~/components/ui/icons"; + +export function NotAvailableNotice({ + feature, + detail, +}: { + feature: string; + detail?: string; +}) { + return ( +
+ + + +
+
+ {feature} isn’t available in this Karrio deployment +
+
+ {detail ?? + "This feature has no endpoint in the open-source Karrio API. It’s available in Karrio Enterprise/Platform, or once the backend exposes it."} +
+
+
+ ); +} diff --git a/apps/studio/src/components/ui/Sheet.tsx b/apps/studio/src/components/ui/Sheet.tsx new file mode 100644 index 000000000..f58464669 --- /dev/null +++ b/apps/studio/src/components/ui/Sheet.tsx @@ -0,0 +1,139 @@ +// Sheet.tsx — the workhorse right drawer. Build ALL detail/create/edit views on +// this. sm/md/lg widths, fullscreen, ESC/backdrop close, and accessible focus +// management: focus moves in on open, is trapped while open, restored on close, +// and the closed sheet is `inert` so it's not tab-reachable. +import { useEffect, useRef, type ReactNode } from "react"; +import { Icon } from "~/components/ui/icons"; + +const FOCUSABLE = + 'button:not([disabled]),[href],input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'; + +export type SheetProps = { + open: boolean; + onClose: () => void; + size?: "sm" | "md" | "lg"; + fullscreen?: boolean; + onToggleFullscreen?: () => void; + crumb?: string; + title?: ReactNode; + id?: string; + headRight?: ReactNode; + footer?: ReactNode; + children?: ReactNode; +}; + +export function Sheet({ + open, + onClose, + size, + fullscreen, + onToggleFullscreen, + crumb, + title, + id, + headRight, + footer, + children, +}: SheetProps) { + const ref = useRef(null); + const restoreTo = useRef(null); + + // ESC to close. + useEffect(() => { + if (!open) return; + const onKey = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKey); + return () => window.removeEventListener("keydown", onKey); + }, [open, onClose]); + + // Focus management + inert. + useEffect(() => { + const el = ref.current; + if (!el) return; + if (open) { + restoreTo.current = (document.activeElement as HTMLElement) ?? null; + el.inert = false; + const focusables = el.querySelectorAll(FOCUSABLE); + (focusables[0] ?? el).focus({ preventScroll: true }); + } else { + el.inert = true; + restoreTo.current?.focus?.({ preventScroll: true }); + } + }, [open]); + + const onKeyDownTrap = (e: React.KeyboardEvent) => { + if (e.key !== "Tab" || !ref.current) return; + const f = Array.from(ref.current.querySelectorAll(FOCUSABLE)).filter( + (n) => n.offsetParent !== null, + ); + if (f.length === 0) return; + const first = f[0]; + const last = f[f.length - 1]; + if (e.shiftKey && document.activeElement === first) { + e.preventDefault(); + last.focus(); + } else if (!e.shiftKey && document.activeElement === last) { + e.preventDefault(); + first.focus(); + } + }; + + return ( + <> + + ); +} diff --git a/apps/studio/src/lib/csv.ts b/apps/studio/src/lib/csv.ts new file mode 100644 index 000000000..17a1ab7f8 --- /dev/null +++ b/apps/studio/src/lib/csv.ts @@ -0,0 +1,31 @@ +// csv.ts — client-side CSV generation + download (no dependency). Used for bulk +// export of selected resources. + +function escapeCell(v: unknown): string { + const s = v == null ? "" : String(v); + return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; +} + +export type CsvColumn = { key?: string; label: string; get?: (row: T) => unknown }; + +export function toCsv>(rows: T[], columns: CsvColumn[]): string { + const header = columns.map((c) => escapeCell(c.label)).join(","); + const body = rows + .map((row) => columns.map((c) => escapeCell(c.get ? c.get(row) : c.key ? (row as Record)[c.key] : "")).join(",")) + .join("\n"); + return `${header}\n${body}`; +} + +/** Trigger a browser download of `content` as `filename`. */ +export function downloadCsv(filename: string, content: string): void { + if (typeof document === "undefined") return; + const blob = new Blob([content], { type: "text/csv;charset=utf-8" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + setTimeout(() => URL.revokeObjectURL(url), 1000); +} diff --git a/apps/studio/src/lib/karrio/agents.ts b/apps/studio/src/lib/karrio/agents.ts new file mode 100644 index 000000000..e860bf2ce --- /dev/null +++ b/apps/studio/src/lib/karrio/agents.ts @@ -0,0 +1,368 @@ +// agents.ts — Typed store for agent definitions (F2) and MCP server configs (F3). +// +// Persistence: localStorage (single source of truth for OSS). +// +// Backend-adapter seam: the `BackendAdapter` interface below is the integration +// point for a future Karrio API endpoint (e.g. /v1/studio/agents). +// A `localStorageAdapter` is the default — it is the only real implementation +// today because no OSS backend endpoint exists for agent or MCP server configs. +// Replace `defaultAdapter` with a REST adapter once the backend ships. +// +// MCP execution seam: connecting to / invoking an external MCP server requires +// a backend proxy that does not exist in OSS Karrio. The `McpServerConfig` +// records stored here are *configuration only*. The `connectionStatus` field +// is set to "config-only" to make this explicit. +import { getStudioCtx, readMeta, writeMeta } from "~/lib/karrio/metastore"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +/** Transport variants for MCP server connections. */ +export type McpTransport = "stdio" | "sse" | "http"; + +/** + * A user-managed MCP server configuration entry. + * Storing this config does NOT establish a live connection; actual MCP + * execution requires a backend proxy (not available in OSS Karrio today). + */ +export type McpServerConfig = { + /** Unique identifier (UUID v4, client-generated). */ + id: string; + /** Human-readable label for the server. */ + name: string; + /** Transport type: stdio (local process) | sse / http (remote). */ + transport: McpTransport; + /** + * For stdio: the command to run (e.g. "npx @my/mcp-server"). + * For sse/http: the URL (e.g. "https://mcp.example.com/events"). + */ + endpoint: string; + /** Environment variables to pass to the server process (stdio only). */ + env: Record; + /** ISO timestamp when the record was created. */ + createdAt: string; + /** ISO timestamp of last edit. */ + updatedAt: string; + /** + * Always "config-only" in OSS — this library stores configuration only. + * A backend proxy would be required to actually connect and return "ok" or + * "error". Never display a fabricated "connected" status. + * + * TODO(F3-exec): replace with a real status once a backend proxy exists. + */ + connectionStatus: "config-only"; +}; + +/** Available LLM models for agent definitions. */ +export type AgentModel = + | "claude-opus-4-8" + | "claude-sonnet-4-6" + | "claude-haiku-3-5" + | string; + +/** A persisted agent definition (F2). */ +export type AgentDef = { + /** Unique identifier (UUID v4, client-generated). */ + id: string; + /** Human-readable display name. */ + name: string; + /** Short description shown in the agents list. */ + description: string; + /** The system prompt sent before user messages. */ + systemPrompt: string; + /** Model identifier to use for this agent. */ + model: AgentModel; + /** Whether this agent is active / surfaced in selectors. */ + enabled: boolean; + /** Tool IDs this agent is allowed to call. */ + enabledTools: string[]; + /** ISO timestamp when the record was created. */ + createdAt: string; + /** ISO timestamp of last edit. */ + updatedAt: string; +}; + +// --------------------------------------------------------------------------- +// Backend-adapter seam (TODO: swap to a REST implementation when backend ships) +// --------------------------------------------------------------------------- + +/** + * Adapter interface for agent + MCP config persistence. + * + * TODO(F2/F3-backend): implement a `restAdapter` using + * `restGet` / `restMutate` from ~/lib/karrio/client once the Karrio API + * exposes endpoints like /v1/studio/agents and /v1/studio/mcp-servers. + * The adapter should accept a `KarrioCtx` and delegate to those endpoints, + * falling back to localStorage on network errors so the UI stays usable. + */ +interface BackendAdapter { + // --- Agent definitions --- + listAgents(): Promise; + saveAgent(agent: AgentDef): Promise; + deleteAgent(id: string): Promise; + + // --- MCP server configs --- + listMcpServers(): Promise; + saveMcpServer(server: McpServerConfig): Promise; + deleteMcpServer(id: string): Promise; +} + +// --------------------------------------------------------------------------- +// localStorage implementation +// --------------------------------------------------------------------------- + +const LS_AGENTS_KEY = "karrio-studio:agents"; +const LS_MCP_KEY = "karrio-studio:mcp-servers"; + +function readJson(key: string, fallback: T): T { + try { + const raw = typeof localStorage !== "undefined" ? localStorage.getItem(key) : null; + return raw ? (JSON.parse(raw) as T) : fallback; + } catch { + return fallback; + } +} + +function writeJson(key: string, value: T): void { + try { + if (typeof localStorage !== "undefined") { + localStorage.setItem(key, JSON.stringify(value)); + } + } catch { + /* storage may be unavailable in SSR or private-browsing contexts */ + } +} + +const localStorageAdapter: BackendAdapter = { + listAgents: () => Promise.resolve(readJson(LS_AGENTS_KEY, [])), + + saveAgent: (agent) => { + const existing = readJson(LS_AGENTS_KEY, []); + const idx = existing.findIndex((a) => a.id === agent.id); + const next = + idx >= 0 + ? existing.map((a) => (a.id === agent.id ? agent : a)) + : [...existing, agent]; + writeJson(LS_AGENTS_KEY, next); + return Promise.resolve(agent); + }, + + deleteAgent: (id) => { + const next = readJson(LS_AGENTS_KEY, []).filter((a) => a.id !== id); + writeJson(LS_AGENTS_KEY, next); + return Promise.resolve(); + }, + + listMcpServers: () => Promise.resolve(readJson(LS_MCP_KEY, [])), + + saveMcpServer: (server) => { + const existing = readJson(LS_MCP_KEY, []); + const idx = existing.findIndex((s) => s.id === server.id); + const next = + idx >= 0 + ? existing.map((s) => (s.id === server.id ? server : s)) + : [...existing, server]; + writeJson(LS_MCP_KEY, next); + return Promise.resolve(server); + }, + + deleteMcpServer: (id) => { + const next = readJson(LS_MCP_KEY, []).filter((s) => s.id !== id); + writeJson(LS_MCP_KEY, next); + return Promise.resolve(); + }, +}; + +// --------------------------------------------------------------------------- +// Backend (metafield) adapter — real per-user persistence with localStorage cache +// --------------------------------------------------------------------------- +// Agent + MCP configs persist as per-user JSON metafields under the `studio.*` +// namespace (see metastore.ts), with localStorage as an immediate write-through +// cache and offline fallback. The ctx comes from SessionProvider via setStudioCtx. + +const META_AGENTS_KEY = "studio.agents"; +const META_MCP_KEY = "studio.mcp-servers"; + +async function loadList(metaKey: string, lsKey: string): Promise { + const ctx = getStudioCtx(); + if (ctx?.token) { + try { + const remote = await readMeta(ctx, metaKey); + if (remote) { + writeJson(lsKey, remote); // refresh the local cache + return remote; + } + } catch { + /* network/permission error — fall back to cache */ + } + } + return readJson(lsKey, []); +} + +async function persistList(metaKey: string, lsKey: string, next: T[]): Promise { + writeJson(lsKey, next); // immediate cache + const ctx = getStudioCtx(); + if (ctx?.token) { + try { + await writeMeta(ctx, metaKey, next); + } catch { + /* sync failure is non-fatal; cache holds and re-syncs on next write */ + } + } +} + +const backendAdapter: BackendAdapter = { + listAgents: () => loadList(META_AGENTS_KEY, LS_AGENTS_KEY), + + saveAgent: async (agent) => { + const existing = await loadList(META_AGENTS_KEY, LS_AGENTS_KEY); + const idx = existing.findIndex((a) => a.id === agent.id); + const next = idx >= 0 ? existing.map((a) => (a.id === agent.id ? agent : a)) : [...existing, agent]; + await persistList(META_AGENTS_KEY, LS_AGENTS_KEY, next); + return agent; + }, + + deleteAgent: async (id) => { + const next = (await loadList(META_AGENTS_KEY, LS_AGENTS_KEY)).filter((a) => a.id !== id); + await persistList(META_AGENTS_KEY, LS_AGENTS_KEY, next); + }, + + listMcpServers: () => loadList(META_MCP_KEY, LS_MCP_KEY), + + saveMcpServer: async (server) => { + const existing = await loadList(META_MCP_KEY, LS_MCP_KEY); + const idx = existing.findIndex((s) => s.id === server.id); + const next = idx >= 0 ? existing.map((s) => (s.id === server.id ? server : s)) : [...existing, server]; + await persistList(META_MCP_KEY, LS_MCP_KEY, next); + return server; + }, + + deleteMcpServer: async (id) => { + const next = (await loadList(META_MCP_KEY, LS_MCP_KEY)).filter((s) => s.id !== id); + await persistList(META_MCP_KEY, LS_MCP_KEY, next); + }, +}; + +// --------------------------------------------------------------------------- +// Active adapter — metafield-backed with localStorage cache/fallback. +// --------------------------------------------------------------------------- + +const defaultAdapter: BackendAdapter = backendAdapter; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function uuid(): string { + if (typeof crypto !== "undefined" && crypto.randomUUID) { + return crypto.randomUUID(); + } + return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; +} + +function now(): string { + return new Date().toISOString(); +} + +// --------------------------------------------------------------------------- +// Public API — agent definitions +// --------------------------------------------------------------------------- + +export const agents = { + list: (): Promise => defaultAdapter.listAgents(), + + create: ( + fields: Omit, + ): Promise => { + const agent: AgentDef = { ...fields, id: uuid(), createdAt: now(), updatedAt: now() }; + return defaultAdapter.saveAgent(agent); + }, + + update: ( + id: string, + fields: Partial>, + ): Promise => { + const current = readJson(LS_AGENTS_KEY, []).find((a) => a.id === id); + if (!current) return Promise.reject(new Error(`Agent not found: ${id}`)); + const updated: AgentDef = { ...current, ...fields, id, updatedAt: now() }; + return defaultAdapter.saveAgent(updated); + }, + + remove: (id: string): Promise => defaultAdapter.deleteAgent(id), +}; + +// --------------------------------------------------------------------------- +// Public API — MCP server configs +// --------------------------------------------------------------------------- + +export const mcpServers = { + list: (): Promise => defaultAdapter.listMcpServers(), + + create: ( + fields: Omit, + ): Promise => { + const server: McpServerConfig = { + ...fields, + id: uuid(), + createdAt: now(), + updatedAt: now(), + connectionStatus: "config-only", + }; + return defaultAdapter.saveMcpServer(server); + }, + + update: ( + id: string, + fields: Partial>, + ): Promise => { + const current = readJson(LS_MCP_KEY, []).find((s) => s.id === id); + if (!current) return Promise.reject(new Error(`MCP server not found: ${id}`)); + const updated: McpServerConfig = { + ...current, + ...fields, + id, + updatedAt: now(), + connectionStatus: "config-only", + }; + return defaultAdapter.saveMcpServer(updated); + }, + + remove: (id: string): Promise => defaultAdapter.deleteMcpServer(id), +}; + +// --------------------------------------------------------------------------- +// Default agent definitions seeded on first use +// --------------------------------------------------------------------------- + +const SEED_AGENTS: Array> = [ + { + name: "Carrier Connector Builder", + description: "Scaffolds and edits Karrio carrier connector files end-to-end.", + systemPrompt: + "You are an expert Karrio contributor. Help the user build carrier connector modules " + + "(mappers, providers, schemas, tests) following the Karrio SDK extension pattern " + + "(`bin/cli sdk add-extension`). Always use `import karrio.lib as lib`.", + model: "claude-opus-4-8", + enabled: true, + enabledTools: ["scaffold_connector", "read_file", "write_file", "run_tests"], + }, + { + name: "Shipping Assistant", + description: "Answers shipping questions and calls Karrio APIs on your behalf.", + systemPrompt: + "You are a helpful shipping operations assistant. You can look up shipments, " + + "create trackers, and fetch rates using the Karrio API. Be concise and accurate.", + model: "claude-sonnet-4-6", + enabled: true, + enabledTools: ["list_shipments", "track_shipment", "get_rates"], + }, +]; + +/** Seed default agents if the store is empty. Call once at app init. */ +export async function seedDefaultAgents(): Promise { + const existing = await agents.list(); + if (existing.length === 0) { + await Promise.all(SEED_AGENTS.map((a) => agents.create(a))); + } +} diff --git a/apps/studio/src/lib/karrio/client.ts b/apps/studio/src/lib/karrio/client.ts new file mode 100644 index 000000000..c1ff97fdc --- /dev/null +++ b/apps/studio/src/lib/karrio/client.ts @@ -0,0 +1,136 @@ +// client.ts — self-contained Karrio API client (REST + GraphQL) over fetch. +// No @karrio/* dependency, no Next.js. Auth/org/test-mode are passed per call +// via the request context, which the SessionProvider supplies. +import { joinUrl } from "~/lib/karrio/env"; + +export type KarrioCtx = { + baseUrl: string; + token?: string; + orgId?: string; + testMode?: boolean; +}; + +function authHeaders(ctx: KarrioCtx): Record { + return { + "content-type": "application/json", + ...(ctx.token ? { authorization: `Bearer ${ctx.token}` } : {}), + ...(ctx.orgId ? { "x-org-id": ctx.orgId } : {}), + ...(ctx.testMode ? { "x-test-mode": "true" } : {}), + }; +} + +export class KarrioError extends Error { + constructor( + message: string, + public status: number, + public body?: unknown, + ) { + super(message); + this.name = "KarrioError"; + } +} + +// The SessionProvider registers a refresh handler (client-side only). On a 401 +// it obtains a fresh access token via the refreshSession server function, so the +// failed request can be retried once transparently. Stays null on the server and +// while unauthenticated. +type RefreshHandler = () => Promise; +let refreshHandler: RefreshHandler | null = null; +let inFlightRefresh: Promise | null = null; + +export function setRefreshHandler(handler: RefreshHandler | null): void { + refreshHandler = handler; +} + +// Fetch with the ctx's auth headers; on 401, refresh the access token once +// (de-duping concurrent refreshes) and retry with the new token. +async function authedFetch(ctx: KarrioCtx, url: string, init: RequestInit = {}): Promise { + const withAuth = (token?: string): RequestInit => ({ + ...init, + headers: { ...authHeaders({ ...ctx, token }), ...(init.headers as Record | undefined) }, + }); + + let res = await fetch(url, withAuth(ctx.token)); + if (res.status === 401 && refreshHandler) { + inFlightRefresh ??= refreshHandler().finally(() => { + inFlightRefresh = null; + }); + const token = await inFlightRefresh; + if (token) res = await fetch(url, withAuth(token)); + } + return res; +} + +export async function restGet( + ctx: KarrioCtx, + path: string, + params?: Record, +): Promise { + const url = new URL(joinUrl(ctx.baseUrl, path)); + if (params) { + for (const [k, v] of Object.entries(params)) { + if (v !== undefined) url.searchParams.set(k, String(v)); + } + } + const res = await authedFetch(ctx, url.toString()); + if (!res.ok) { + throw new KarrioError(`GET ${path} failed (${res.status})`, res.status, await safeJson(res)); + } + return (await res.json()) as T; +} + +export async function restMutate( + ctx: KarrioCtx, + method: "POST" | "PATCH" | "PUT" | "DELETE", + path: string, + body?: unknown, +): Promise { + const res = await authedFetch(ctx, joinUrl(ctx.baseUrl, path), { + method, + body: body === undefined ? undefined : JSON.stringify(body), + }); + if (!res.ok) { + throw new KarrioError(`${method} ${path} failed (${res.status})`, res.status, await safeJson(res)); + } + return (await res.json()) as T; +} + +export async function graphql( + ctx: KarrioCtx, + query: string, + variables?: Record, + endpoint = "/graphql", +): Promise { + const res = await authedFetch(ctx, joinUrl(ctx.baseUrl, endpoint), { + method: "POST", + body: JSON.stringify({ query, variables }), + }); + const json = await res.json(); + if (json.errors?.length) { + throw new KarrioError( + json.errors.map((e: { message: string }) => e.message).join("; "), + res.status, + json.errors, + ); + } + return json.data as T; +} + +// Karrio exposes a SECOND GraphQL schema for platform/admin resources +// (users, accounts, worker_health, configs, usage…) at /admin/graphql. +// adminGraphql targets it with the same auth + 401-refresh behaviour. +export function adminGraphql( + ctx: KarrioCtx, + query: string, + variables?: Record, +): Promise { + return graphql(ctx, query, variables, "/admin/graphql"); +} + +async function safeJson(res: Response): Promise { + try { + return await res.json(); + } catch { + return undefined; + } +} diff --git a/apps/studio/src/lib/karrio/display.ts b/apps/studio/src/lib/karrio/display.ts new file mode 100644 index 000000000..f71d1f245 --- /dev/null +++ b/apps/studio/src/lib/karrio/display.ts @@ -0,0 +1,94 @@ +// display.ts — map Karrio API entities to display strings for Studio screens. +import { CARRIERS } from "~/components/ui/CarrierLogo"; +import type { Address, Shipment } from "~/lib/karrio/types"; + +const PILL_CLASSES = new Set([ + "created", + "purchased", + "delivered", + "pending", + "cancelled", + "exception", + "failed", + "draft", + "intransit", +]); + +// Normalize a Karrio status to a pill CSS class. +export function statusClass(status?: string): string { + if (!status) return "default"; + const s = status.toLowerCase().replace(/[\s_-]+/g, ""); + if (s === "intransit" || s === "transit") return "intransit"; + if (s === "canceled") return "cancelled"; + return PILL_CLASSES.has(s) ? s : "default"; +} + +export function statusLabel(status?: string): string { + if (!status) return "unknown"; + return status.replace(/_/g, " "); +} + +// Carrier key for the CarrierLogo badge (best-effort match against CARRIERS). +export function carrierKey(carrier?: string): string { + if (!carrier) return ""; + const c = carrier.toLowerCase().replace(/[\s-]+/g, "_"); + if (CARRIERS[c]) return c; + if (c.includes("canada") || c.includes("canpost")) return "canpost"; + if (c.includes("fedex")) return "fedex"; + if (c.includes("ups")) return "ups"; + if (c.includes("dhl")) return "dhl"; + if (c.includes("usps") || c.includes("postal")) return "usps"; + if (c.includes("purolator")) return "purolator"; + if (c.includes("royal")) return "royalmail"; + if (c.includes("landmark")) return "landmark"; + if (c.includes("smartkargo")) return "smartkargo"; + if (c.includes("dpd")) return "dpd"; + if (c.includes("australia") || c.includes("aupost")) return "australia"; + if (c.includes("tnt")) return "tnt"; + if (c.includes("aramex")) return "aramex"; + return c; +} + +// Carrier display name from any slug/name (humanized fallback for unknowns). +export function carrierName(carrier?: string): string { + if (!carrier) return "—"; + const key = carrierKey(carrier); + return CARRIERS[key]?.name ?? carrier.replace(/[_-]+/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase()); +} + +// Carrier for a shipment: top-level field, else the selected rate. +export function shipmentCarrier(s: { carrier_name?: string; selected_rate?: { carrier_name?: string } | null }): string { + return s.carrier_name || s.selected_rate?.carrier_name || ""; +} + +// Service label for a shipment (rate-first), humanized. +export function shipmentService(s: { service?: string; selected_rate?: { service?: string } | null }): string { + const svc = s.service || s.selected_rate?.service || ""; + return svc ? svc.replace(/[_-]+/g, " ").replace(/\b\w/g, (ch) => ch.toUpperCase()) : "—"; +} + +export function formatRate(shipment: Shipment): string { + const rate = shipment.selected_rate; + if (!rate || rate.total_charge == null) return "—"; + return `${rate.total_charge.toFixed(2)} ${rate.currency ?? ""}`.trim(); +} + +export function recipientName(addr?: Address): string { + return addr?.person_name || addr?.company_name || "—"; +} + +export function recipientAddr(addr?: Address): string { + return [addr?.city, addr?.state_code, addr?.country_code].filter(Boolean).join(", "); +} + +export function formatDate(iso?: string): string { + if (!iso) return "—"; + const d = new Date(iso); + if (Number.isNaN(d.getTime())) return iso; + return d.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} diff --git a/apps/studio/src/lib/karrio/env.ts b/apps/studio/src/lib/karrio/env.ts new file mode 100644 index 000000000..91f568c0f --- /dev/null +++ b/apps/studio/src/lib/karrio/env.ts @@ -0,0 +1,23 @@ +// env.ts — base URL resolution with NO Next.js coupling. +// Server reads process.env.KARRIO_API; the browser reads Vite's +// import.meta.env.VITE_KARRIO_API (only VITE_-prefixed vars are exposed). + +function fromVite(): string | undefined { + try { + return (import.meta as unknown as { env?: Record }).env + ?.VITE_KARRIO_API; + } catch { + return undefined; + } +} + +export function karrioBaseUrl(): string { + const fromProcess = + typeof process !== "undefined" ? process.env?.KARRIO_API : undefined; + return fromProcess || fromVite() || "http://localhost:5002"; +} + +// Normalize a base + path into a single URL (replaces @karrio/lib `url$`). +export function joinUrl(base: string, path = ""): string { + return `${base.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`; +} diff --git a/apps/studio/src/lib/karrio/hooks.ts b/apps/studio/src/lib/karrio/hooks.ts new file mode 100644 index 000000000..2b1e0c683 --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks.ts @@ -0,0 +1,14 @@ +// hooks.ts — TanStack Query data hooks over the decoupled Karrio client. +// This is the public barrel; the hooks live in domain modules so the GraphQL- +// first migration can be done file-by-file without conflicts: +// hooks/ship.ts (UNIT A) — shipments, trackers, connections, pickups, +// document templates, manifests, batches +// hooks/build.ts (UNIT B) — apps, plugins, webhooks, api keys, mcp +// hooks/govern.ts (UNIT C) — admin, tenants, team, audit, usage +// hooks/resources.ts (UNIT E) — orders + templates (GraphQL) + all mutations +// Studio-native state (UNIT D) lives in agents.ts / preferences.ts. +// See STUDIO_GRAPHQL_REBUILD.md for the per-hook source mapping. +export * from "~/lib/karrio/hooks/ship"; +export * from "~/lib/karrio/hooks/build"; +export * from "~/lib/karrio/hooks/govern"; +export * from "~/lib/karrio/hooks/resources"; diff --git a/apps/studio/src/lib/karrio/hooks/_shared.ts b/apps/studio/src/lib/karrio/hooks/_shared.ts new file mode 100644 index 000000000..514ec9787 --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks/_shared.ts @@ -0,0 +1,26 @@ +// hooks/_shared.ts — helpers shared by the domain hook modules (ship/build/ +// govern/resources). Keep this dependency-light so each domain file can be +// rewritten independently for the GraphQL-first migration (see +// STUDIO_GRAPHQL_REBUILD.md). +import { graphql, type KarrioCtx } from "~/lib/karrio/client"; + +export const keyExtra = (ctx: KarrioCtx) => ({ org: ctx.orgId, test: ctx.testMode }); + +// Map a GraphQL connection field (`{ field { edges { node } } }`) to a flat +// array of nodes. `endpoint` selects the tenant (/graphql) or admin +// (/admin/graphql) schema. +export async function graphqlEdges( + ctx: KarrioCtx, + query: string, + field: string, + variables?: Record, + endpoint = "/graphql", +): Promise { + const data = await graphql }>>( + ctx, + query, + variables, + endpoint, + ); + return (data[field]?.edges ?? []).map((e) => e.node); +} diff --git a/apps/studio/src/lib/karrio/hooks/build.ts b/apps/studio/src/lib/karrio/hooks/build.ts new file mode 100644 index 000000000..8b975c197 --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks/build.ts @@ -0,0 +1,92 @@ +// hooks/build.ts — Build-mode read hooks (UNIT B). api_keys + webhooks come +// from the tenant GraphQL schema; apps / plugins / mcp have NO source in the +// OSS Karrio schema (no oauth_apps/app_installations/plugins/mcp), so they +// resolve to an empty result and the screens render an honest "not available" +// state. See STUDIO_GRAPHQL_REBUILD.md. +import { useQuery } from "@tanstack/react-query"; +import { graphql } from "~/lib/karrio/client"; +import { useKarrioCtx } from "~/lib/karrio/session"; +import { graphqlEdges, keyExtra } from "~/lib/karrio/hooks/_shared"; +import type { ApiKey, App, McpInfo, Paginated, Plugin, Webhook } from "~/lib/karrio/types"; + +// --- API keys (GraphQL `api_keys`) ------------------------------------------ +// Live shape: a flat list of { key, label, test_mode, created } (NOT a +// connection). We key rows by `key` (unique) for the screen's row id. +const API_KEYS_QUERY = `query { api_keys { key label test_mode created } }`; + +export function useApiKeys() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["api-keys", keyExtra(ctx)], + queryFn: async (): Promise> => { + const data = await graphql<{ api_keys: Array> }>(ctx, API_KEYS_QUERY); + const results = (data.api_keys ?? []).map((k) => ({ ...k, id: k.key })); + return { count: results.length, next: null, previous: null, results }; + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Webhooks (GraphQL `webhooks`; mutations remain on functional REST) ------ +// Wire shape differs from the screens': map disabled->enabled, enabled_events->events. +const WEBHOOKS_QUERY = `query { webhooks { edges { node { + id url disabled description enabled_events +} } } }`; + +type RawWebhook = { id: string; url: string; disabled?: boolean; description?: string; enabled_events?: string[] }; + +export function useWebhooks() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["webhooks", keyExtra(ctx)], + queryFn: async (): Promise> => { + const raws = await graphqlEdges(ctx, WEBHOOKS_QUERY, "webhooks"); + const results: Webhook[] = raws.map((w) => ({ + id: w.id, + url: w.url, + description: w.description, + enabled: !w.disabled, + events: w.enabled_events ?? [], + })); + return { count: results.length, next: null, previous: null, results }; + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Apps / Plugins / MCP: no OSS source → honest empty --------------------- +// The OSS Karrio GraphQL/REST API exposes no app marketplace (oauth_apps/ +// app_installations are EE), plugin registry, or MCP server proxy. Resolve to +// empty (no fetch) so the screens render a "not available" notice instead of a +// failed request. Never fabricate rows or a "connected" status. +const empty = (): Paginated => ({ count: 0, next: null, previous: null, results: [] }); + +export function useApps() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["apps", keyExtra(ctx)], + queryFn: async (): Promise> => empty(), + enabled: Boolean(ctx.token), + staleTime: Infinity, + }); +} + +export function usePlugins() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["plugins", keyExtra(ctx)], + queryFn: async (): Promise> => empty(), + enabled: Boolean(ctx.token), + staleTime: Infinity, + }); +} + +export function useMcp() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["mcp", keyExtra(ctx)], + queryFn: async (): Promise => ({ status: "not_available", tools: [], clients: [], invocations: [] }), + enabled: Boolean(ctx.token), + staleTime: Infinity, + }); +} diff --git a/apps/studio/src/lib/karrio/hooks/govern.ts b/apps/studio/src/lib/karrio/hooks/govern.ts new file mode 100644 index 000000000..1346b1cb1 --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks/govern.ts @@ -0,0 +1,259 @@ +// hooks/govern.ts — Govern-mode read hooks (UNIT C). Sourced from Karrio's +// canonical GraphQL: team + admin overview from the ADMIN schema +// (/admin/graphql), usage + audit from the tenant schema. `tenants` has no OSS +// source (no `accounts`) → empty/EE state. See STUDIO_GRAPHQL_REBUILD.md. +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { adminGraphql, graphql } from "~/lib/karrio/client"; +import { useKarrioCtx } from "~/lib/karrio/session"; +import { graphqlEdges, keyExtra } from "~/lib/karrio/hooks/_shared"; +import type { + AdminInfo, + AuditEvent, + Paginated, + TeamMember, + Tenant, + UsageSummary, +} from "~/lib/karrio/types"; + +// --- Team & roles (ADMIN GraphQL `users`) ----------------------------------- +const ADMIN_USERS_QUERY = `query { users { edges { node { + id email full_name is_active is_staff is_superuser last_login +} } } }`; + +type RawUser = { + id: number | string; + email: string; + full_name?: string; + is_active?: boolean; + is_staff?: boolean; + is_superuser?: boolean; + last_login?: string; +}; + +export function useTeam() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["team", keyExtra(ctx)], + queryFn: async (): Promise> => { + const raws = await graphqlEdges(ctx, ADMIN_USERS_QUERY, "users", undefined, "/admin/graphql"); + const results: TeamMember[] = raws.map((u) => ({ + id: String(u.id), + name: u.full_name || undefined, + email: u.email, + role: u.is_superuser ? "owner" : u.is_staff ? "admin" : "member", + status: u.is_active ? "active" : "inactive", + })); + return { count: results.length, next: null, previous: null, results }; + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Team mutations (ADMIN GraphQL create/update/remove user) --------------- +// Role is encoded onto the Django flags the admin schema exposes: +// owner → is_superuser admin → is_staff member → neither. +type Role = "owner" | "admin" | "member"; +const roleFlags = (role: Role) => ({ + is_superuser: role === "owner", + is_staff: role === "owner" || role === "admin", +}); + +const CREATE_USER = `mutation($input: CreateUserMutationInput!) { + create_user(input: $input) { user { id email } errors { field messages } } +}`; +const UPDATE_USER = `mutation($input: UpdateUserMutationInput!) { + update_user(input: $input) { user { id email } errors { field messages } } +}`; +const REMOVE_USER = `mutation($input: DeleteUserMutationInput!) { + remove_user(input: $input) { id } +}`; + +export type InviteUserInput = { email: string; full_name?: string; role: Role; redirect_url?: string }; + +// create_user requires password1/password2; for an invite we generate a random +// one-time password (the user resets via the email flow / redirect_url). Uses +// the Web Crypto CSPRNG — never Math.random — since this is a credential. +function randomPassword(): string { + const charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; + const bytes = new Uint8Array(20); + crypto.getRandomValues(bytes); + const body = Array.from(bytes, (b) => charset[b % charset.length]).join(""); + // Prefix guarantees the Django validators (length, not-all-numeric, mixed) pass. + return `St!${body}`; +} + +export function useInviteUser() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: InviteUserInput) => { + const password = randomPassword(); + return adminGraphql(ctx, CREATE_USER, { + input: { + email: vars.email, + full_name: vars.full_name || undefined, + password1: password, + password2: password, + redirect_url: vars.redirect_url || (typeof window !== "undefined" ? window.location.origin : undefined), + ...roleFlags(vars.role), + }, + }); + }, + onSuccess: () => qc.invalidateQueries({ queryKey: ["team"] }), + }); +} + +export type UpdateUserInput = { id: string; role?: Role; is_active?: boolean; full_name?: string }; + +export function useUpdateUser() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: UpdateUserInput) => + adminGraphql(ctx, UPDATE_USER, { + input: { + id: Number(vars.id), + ...(vars.full_name !== undefined ? { full_name: vars.full_name } : {}), + ...(vars.is_active !== undefined ? { is_active: vars.is_active } : {}), + ...(vars.role ? roleFlags(vars.role) : {}), + }, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["team"] }), + }); +} + +export function useRemoveUser() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => adminGraphql(ctx, REMOVE_USER, { input: { id: Number(id) } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["team"] }), + }); +} + +// --- Admin overview (ADMIN GraphQL `worker_health`) ------------------------- +const ADMIN_OVERVIEW_QUERY = `query { + worker_health { is_available } + users { edges { node { id } } } + system_carrier_connections { edges { node { id active } } } +}`; + +type AdminOverviewRaw = { + worker_health?: { is_available?: boolean }; + users?: { edges: Array<{ node: { id: string } }> }; + system_carrier_connections?: { edges: Array<{ node: { id: string; active?: boolean } }> }; +}; + +export function useAdminInfo() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["admin-info", keyExtra(ctx)], + queryFn: async (): Promise => { + const data = await adminGraphql(ctx, ADMIN_OVERVIEW_QUERY); + const available = data.worker_health?.is_available; + const conns = data.system_carrier_connections?.edges ?? []; + return { + license: "Open Source", + users: data.users?.edges?.length ?? 0, + system_connections: conns.length, + worker_available: available, + runtimes: [ + { name: "Background worker", memory: available ? "available" : available === false ? "unavailable" : "—" }, + ], + resources: [], + }; + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Tenants: no OSS source (no `accounts`) → honest empty/EE state ---------- +export function useTenants() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["tenants", keyExtra(ctx)], + queryFn: async (): Promise> => ({ count: 0, next: null, previous: null, results: [] }), + enabled: Boolean(ctx.token), + staleTime: Infinity, + }); +} + +// --- Audit log (tenant GraphQL `events`) ------------------------------------ +const EVENTS_QUERY = `query { events { edges { node { + id type created_at created_by { email } +} } } }`; + +type RawEvent = { id: string; type?: string; created_at?: string; created_by?: { email?: string } }; + +export function useAuditLog() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["audit", keyExtra(ctx)], + queryFn: async (): Promise> => { + const raws = await graphqlEdges(ctx, EVENTS_QUERY, "events"); + const results: AuditEvent[] = raws.map((e) => ({ + id: e.id, + type: e.type, + description: e.type, + actor: e.created_by?.email, + at: e.created_at, + })); + return { count: results.length, next: null, previous: null, results }; + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Usage (tenant GraphQL `system_usage`) ---------------------------------- +const SYSTEM_USAGE_QUERY = `query { system_usage { + total_shipments total_trackers total_requests total_shipping_spend order_volume + shipment_count { date count } + shipping_spend { date count } + order_volumes { date count } + api_requests { date count } + tracker_count { date count } +} }`; + +type Point = { date: string; count: number }; +type RawUsage = { + total_shipments?: number; + total_trackers?: number; + total_requests?: number; + total_shipping_spend?: number; + order_volume?: number; + shipment_count?: Point[]; + shipping_spend?: Point[]; + order_volumes?: Point[]; + api_requests?: Point[]; + tracker_count?: Point[]; +}; + +const num = (n?: number) => (n == null ? "—" : Math.round(n).toLocaleString()); + +export function useUsage() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["usage", keyExtra(ctx)], + queryFn: async (): Promise => { + const u = (await graphql<{ system_usage?: RawUsage }>(ctx, SYSTEM_USAGE_QUERY)).system_usage ?? {}; + return { + plan: "Open Source", + period: "All time", + metrics: [ + { label: "Shipments", value: num(u.total_shipments) }, + { label: "Trackers", value: num(u.total_trackers) }, + { label: "Orders", value: num(u.order_volume) }, + { label: "API requests", value: num(u.total_requests) }, + { label: "Shipping spend", value: u.total_shipping_spend != null ? `$${u.total_shipping_spend.toLocaleString()}` : "—" }, + ], + series: [ + { key: "shipment_count", label: "Shipments", format: "number", points: u.shipment_count ?? [] }, + { key: "shipping_spend", label: "Shipping spend", format: "currency", points: u.shipping_spend ?? [] }, + { key: "api_requests", label: "API requests", format: "number", points: u.api_requests ?? [] }, + { key: "order_volumes", label: "Orders", format: "number", points: u.order_volumes ?? [] }, + ], + }; + }, + enabled: Boolean(ctx.token), + }); +} diff --git a/apps/studio/src/lib/karrio/hooks/resources.ts b/apps/studio/src/lib/karrio/hooks/resources.ts new file mode 100644 index 000000000..1a19c0e04 --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks/resources.ts @@ -0,0 +1,434 @@ +// hooks/resources.ts — GraphQL resource reads (orders + saved templates) and +// ALL write mutations. UNIT E aligns field selections to the dashboard GET_* +// queries and keeps mutations on GraphQL. See STUDIO_GRAPHQL_REBUILD.md. +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { graphql, restMutate } from "~/lib/karrio/client"; +import { useKarrioCtx } from "~/lib/karrio/session"; +import { graphqlEdges, keyExtra } from "~/lib/karrio/hooks/_shared"; +import type { + AddressTemplate, + Order, + ParcelTemplate, + ProductTemplate, + RateSheet, + ShippingRule, + Workflow, +} from "~/lib/karrio/types"; + +// === Orders (GraphQL) ======================================================= +const ORDERS_QUERY = `query Orders($filter: OrderFilter) { + orders(filter: $filter) { + edges { node { id order_id status source created_at + line_items { id title quantity } + shipping_to { city country_code person_name } + } } + } +}`; + +type OrdersResponse = { orders: { edges: Array<{ node: Order }> } }; + +export function useOrders(filter?: Record) { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["orders", filter, keyExtra(ctx)], + queryFn: async () => { + const data = await graphql(ctx, ORDERS_QUERY, { filter }); + return data.orders.edges.map((e) => e.node); + }, + enabled: Boolean(ctx.token), + }); +} + +// === Address templates (GraphQL) ============================================ +const ADDRESS_TEMPLATES_QUERY = `query { addresses { edges { node { + id meta + person_name company_name address_line1 address_line2 + city state_code postal_code country_code email phone_number residential +} } } }`; + +type RawAddress = { + id: string; + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + person_name?: string; company_name?: string; address_line1?: string; address_line2?: string; + city?: string; state_code?: string; postal_code?: string; country_code?: string; + email?: string; phone_number?: string; residential?: boolean; +}; + +function normaliseAddress(raw: RawAddress): AddressTemplate { + const { meta, person_name, company_name, address_line1, address_line2, + city, state_code, postal_code, country_code, email, phone_number, residential, ...rest } = raw; + return { + ...rest, + meta, + label: meta?.label, + is_default: meta?.is_default, + person_name, company_name, address_line1, address_line2, + city, state_code, postal_code, country_code, email, phone_number, residential, + address: { person_name, company_name, address_line1, address_line2, + city, state_code, postal_code, country_code, email, phone_number, residential }, + }; +} + +export function useAddresses() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["address-templates", keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, ADDRESS_TEMPLATES_QUERY, "addresses"); + return raws.map(normaliseAddress); + }, + enabled: Boolean(ctx.token), + }); +} + +// === Parcel templates (GraphQL) ============================================= +const PARCEL_TEMPLATES_QUERY = `query { parcels { edges { node { + id meta packaging_type width height length dimension_unit weight weight_unit +} } } }`; + +type RawParcel = { + id: string; + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + packaging_type?: string; width?: number; height?: number; length?: number; + dimension_unit?: string; weight?: number; weight_unit?: string; +}; + +function normaliseParcel(raw: RawParcel): ParcelTemplate { + return { ...raw, label: raw.meta?.label, is_default: raw.meta?.is_default }; +} + +export function useParcels() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["parcel-templates", keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, PARCEL_TEMPLATES_QUERY, "parcels"); + return raws.map(normaliseParcel); + }, + enabled: Boolean(ctx.token), + }); +} + +// === Product templates / commodities (GraphQL) ============================== +const PRODUCTS_QUERY = `query { products { edges { node { + id meta title sku hs_code weight weight_unit value_amount value_currency origin_country +} } } }`; + +type RawProduct = { + id: string; + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + title?: string; sku?: string; hs_code?: string; weight?: number; weight_unit?: string; + value_amount?: number; value_currency?: string; origin_country?: string; +}; + +function normaliseProduct(raw: RawProduct): ProductTemplate { + return { ...raw, label: raw.meta?.label, is_default: raw.meta?.is_default }; +} + +export function useProducts() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["products", keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, PRODUCTS_QUERY, "products"); + return raws.map(normaliseProduct); + }, + enabled: Boolean(ctx.token), + }); +} + +// === Shipping rules (GraphQL, EE-only → graceful []) ======================== +const SHIPPING_RULES_QUERY = `query { shipping_rules { edges { node { + id name priority is_active description action_type +} } } }`; + +export function useShippingRules() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["shipping-rules", keyExtra(ctx)], + queryFn: async (): Promise => { + try { + return await graphqlEdges(ctx, SHIPPING_RULES_QUERY, "shipping_rules"); + } catch { + return []; + } + }, + enabled: Boolean(ctx.token), + }); +} + +// === Workflows (GraphQL, EE-only → graceful []) ============================= +const WORKFLOWS_QUERY = `query { workflows { edges { node { + id name description is_active trigger action_count +} } } }`; + +export function useWorkflows() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["workflows", keyExtra(ctx)], + queryFn: async (): Promise => { + try { + return await graphqlEdges(ctx, WORKFLOWS_QUERY, "workflows"); + } catch { + return []; + } + }, + enabled: Boolean(ctx.token), + }); +} + +// === Rate sheets (GraphQL) ================================================== +const RATE_SHEETS_QUERY = `query { rate_sheets { edges { node { + id name slug carrier_name + services { id service_code service_name currency cost min_weight max_weight weight_unit transit_days } +} } } }`; + +export function useRateSheets() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["rate-sheets", keyExtra(ctx)], + queryFn: () => graphqlEdges(ctx, RATE_SHEETS_QUERY, "rate_sheets"), + enabled: Boolean(ctx.token), + }); +} + +// Rate sheet create/update/delete (GraphQL). services is a list of service levels. +const CREATE_RATE_SHEET = `mutation($input: CreateRateSheetMutationInput!) { + create_rate_sheet(input: $input) { rate_sheet { id slug } errors { field messages } } +}`; +const UPDATE_RATE_SHEET = `mutation($input: UpdateRateSheetMutationInput!) { + update_rate_sheet(input: $input) { rate_sheet { id slug } errors { field messages } } +}`; +const DELETE_RATE_SHEET = `mutation($input: DeleteMutationInput!) { delete_rate_sheet(input: $input) { id } }`; + +export function useSaveRateSheet() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_RATE_SHEET : CREATE_RATE_SHEET, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["rate-sheets"] }), + }); +} + +export function useDeleteRateSheet() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_RATE_SHEET, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["rate-sheets"] }), + }); +} + +// === Mutations: address / parcel / product (GraphQL) ======================== +const CREATE_ADDRESS = `mutation($input: CreateAddressInput!) { + create_address(input: $input) { + address { id meta person_name company_name address_line1 city state_code postal_code country_code } + errors { field messages } + } +}`; +const UPDATE_ADDRESS = `mutation($input: UpdateAddressInput!) { + update_address(input: $input) { + address { id meta person_name company_name address_line1 city state_code postal_code country_code } + errors { field messages } + } +}`; +const DELETE_ADDRESS = `mutation($input: DeleteMutationInput!) { delete_address(input: $input) { id } }`; + +export function useSaveAddress() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_ADDRESS : CREATE_ADDRESS, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["address-templates"] }), + }); +} + +export function useDeleteAddress() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_ADDRESS, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["address-templates"] }), + }); +} + +const CREATE_PARCEL = `mutation($input: CreateParcelInput!) { + create_parcel(input: $input) { + parcel { id meta weight weight_unit width height length dimension_unit packaging_type } + errors { field messages } + } +}`; +const UPDATE_PARCEL = `mutation($input: UpdateParcelInput!) { + update_parcel(input: $input) { + parcel { id meta weight weight_unit width height length dimension_unit packaging_type } + errors { field messages } + } +}`; +const DELETE_PARCEL = `mutation($input: DeleteMutationInput!) { delete_parcel(input: $input) { id } }`; + +export function useSaveParcel() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_PARCEL : CREATE_PARCEL, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["parcel-templates"] }), + }); +} + +export function useDeleteParcel() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_PARCEL, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["parcel-templates"] }), + }); +} + +const CREATE_PRODUCT = `mutation($input: CreateProductInput!) { + create_product(input: $input) { + product { id meta title sku hs_code weight weight_unit value_amount value_currency origin_country } + errors { field messages } + } +}`; +const UPDATE_PRODUCT = `mutation($input: UpdateProductInput!) { + update_product(input: $input) { + product { id meta title sku hs_code weight weight_unit value_amount value_currency origin_country } + errors { field messages } + } +}`; +const DELETE_PRODUCT = `mutation($input: DeleteMutationInput!) { delete_product(input: $input) { id } }`; + +export function useSaveProduct() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_PRODUCT : CREATE_PRODUCT, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["products"] }), + }); +} + +export function useDeleteProduct() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_PRODUCT, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["products"] }), + }); +} + +// === Mutations: webhooks / connections (REST today; UNIT E may move to GQL) == +export function useSaveWebhook() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + vars.id + ? restMutate(ctx, "PATCH", `/v1/webhooks/${vars.id}`, vars.data) + : restMutate(ctx, "POST", "/v1/webhooks", vars.data), + onSuccess: () => qc.invalidateQueries({ queryKey: ["webhooks"] }), + }); +} + +export function useDeleteWebhook() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => restMutate(ctx, "DELETE", `/v1/webhooks/${id}`), + onSuccess: () => qc.invalidateQueries({ queryKey: ["webhooks"] }), + }); +} + +// Carrier connection create/update/delete via GraphQL. `credentials` is a JSON +// map of the carrier's dynamic connection fields (see references.ts + ConnectionForm). +const CREATE_CONNECTION = `mutation($input: CreateCarrierConnectionMutationInput!) { + create_carrier_connection(input: $input) { + connection { id carrier_name carrier_id } + errors { field messages } + } +}`; +const UPDATE_CONNECTION = `mutation($input: UpdateCarrierConnectionMutationInput!) { + update_carrier_connection(input: $input) { + connection { id carrier_name carrier_id } + errors { field messages } + } +}`; +const DELETE_CONNECTION = `mutation($input: DeleteMutationInput!) { delete_carrier_connection(input: $input) { id } }`; + +export function useSaveConnection() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_CONNECTION : CREATE_CONNECTION, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["carrier-connections"] }), + }); +} + +export function useDeleteConnection() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_CONNECTION, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["carrier-connections"] }), + }); +} + +// === Document templates: create / update / delete (GraphQL) ================= +const CREATE_DOC_TEMPLATE = `mutation($input: CreateDocumentTemplateMutationInput!) { + create_document_template(input: $input) { template { id slug preview_url } errors { field messages } } +}`; +const UPDATE_DOC_TEMPLATE = `mutation($input: UpdateDocumentTemplateMutationInput!) { + update_document_template(input: $input) { template { id slug preview_url } errors { field messages } } +}`; +const DELETE_DOC_TEMPLATE = `mutation($input: DeleteMutationInput!) { delete_document_template(input: $input) { id } }`; + +export function useSaveDocumentTemplate() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (vars: { id?: string; data: Record }) => + graphql(ctx, vars.id ? UPDATE_DOC_TEMPLATE : CREATE_DOC_TEMPLATE, { + input: vars.id ? { id: vars.id, ...vars.data } : vars.data, + }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["document-templates"] }), + }); +} + +export function useDeleteDocumentTemplate() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (id: string) => graphql(ctx, DELETE_DOC_TEMPLATE, { input: { id } }), + onSuccess: () => qc.invalidateQueries({ queryKey: ["document-templates"] }), + }); +} + +// === Bulk label purchase (REST batch operation) ============================= +// POST /v1/batches/shipments with references to existing shipments → creates a +// batch operation that buys labels for the selected (purchasable) shipments. +export function useBulkBuyLabels() { + const ctx = useKarrioCtx(); + const qc = useQueryClient(); + return useMutation({ + mutationFn: (shipmentIds: string[]) => + restMutate(ctx, "POST", "/v1/batches/shipments", { shipments: shipmentIds.map((id) => ({ id })) }), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ["batches"] }); + qc.invalidateQueries({ queryKey: ["shipments"] }); + }, + }); +} diff --git a/apps/studio/src/lib/karrio/hooks/ship.ts b/apps/studio/src/lib/karrio/hooks/ship.ts new file mode 100644 index 000000000..faf457ead --- /dev/null +++ b/apps/studio/src/lib/karrio/hooks/ship.ts @@ -0,0 +1,164 @@ +// hooks/ship.ts — Ship-mode read hooks (UNIT A), all on the tenant GraphQL +// schema, mirroring the dashboard GET_* selections. Connection results are +// flattened to the Paginated shape the screens already consume. +// See STUDIO_GRAPHQL_REBUILD.md. +import { useQuery } from "@tanstack/react-query"; +import { graphql } from "~/lib/karrio/client"; +import { useKarrioCtx } from "~/lib/karrio/session"; +import { graphqlEdges, keyExtra } from "~/lib/karrio/hooks/_shared"; +import type { + BatchOperation, + CarrierConnection, + DocumentTemplate, + Manifest, + Paginated, + Pickup, + Shipment, + Tracker, +} from "~/lib/karrio/types"; + +const page = (results: T[]): Paginated => ({ count: results.length, next: null, previous: null, results }); + +// --- Shipments -------------------------------------------------------------- +const SHIPMENTS_QUERY = `query { shipments { edges { node { + id status tracking_number reference created_at carrier_name carrier_id service + selected_rate { carrier_name service total_charge currency transit_days + extra_charges { name amount currency } } + recipient { person_name company_name city state_code country_code postal_code address_line1 email phone_number residential } + shipper { person_name company_name city state_code country_code postal_code address_line1 } + parcels { weight weight_unit width height length dimension_unit packaging_type reference_number } +} } } }`; + +export function useShipments(params?: Record) { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["shipments", params, keyExtra(ctx)], + queryFn: async () => page(await graphqlEdges(ctx, SHIPMENTS_QUERY, "shipments")), + enabled: Boolean(ctx.token), + }); +} + +const SHIPMENT_QUERY = `query($id: String!) { shipment(id: $id) { + id status tracking_number reference created_at carrier_name carrier_id service + selected_rate { carrier_name service total_charge currency transit_days + extra_charges { name amount currency } } + recipient { person_name company_name city state_code country_code postal_code address_line1 email phone_number residential } + shipper { person_name company_name city state_code country_code postal_code address_line1 } + parcels { weight weight_unit width height length dimension_unit packaging_type reference_number } +} }`; + +export function useShipment(id: string) { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["shipment", id, keyExtra(ctx)], + queryFn: async () => { + const data = await graphql<{ shipment: Shipment }>(ctx, SHIPMENT_QUERY, { id }); + return data.shipment; + }, + enabled: Boolean(ctx.token && id), + }); +} + +// --- Trackers (carrier surfaced from meta when null, as before) ------------- +const TRACKERS_QUERY = `query { trackers { edges { node { + id tracking_number carrier_name status estimated_delivery meta + events { description location code date time } +} } } }`; + +export function useTrackers(params?: Record) { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["trackers", params, keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, TRACKERS_QUERY, "trackers"); + return page( + raws.map((t) => ({ + ...t, + carrier_name: t.carrier_name || (t.meta as { carrier_name?: string } | undefined)?.carrier_name, + })), + ); + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Carrier connections (GraphQL `user_connections`) ----------------------- +const USER_CONNECTIONS_QUERY = `query { user_connections { edges { node { + id carrier_name carrier_id display_name active test_mode capabilities +} } } }`; + +export function useCarrierConnections() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["carrier-connections", keyExtra(ctx)], + queryFn: async () => page(await graphqlEdges(ctx, USER_CONNECTIONS_QUERY, "user_connections")), + enabled: Boolean(ctx.token), + }); +} + +// --- Pickups ---------------------------------------------------------------- +const PICKUPS_QUERY = `query { pickups { edges { node { + id confirmation_number carrier_name pickup_date ready_time closing_time status + address { person_name city country_code } +} } } }`; + +export function usePickups() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["pickups", keyExtra(ctx)], + queryFn: async () => page(await graphqlEdges(ctx, PICKUPS_QUERY, "pickups")), + enabled: Boolean(ctx.token), + }); +} + +// --- Document templates ----------------------------------------------------- +const DOCUMENT_TEMPLATES_QUERY = `query { document_templates { edges { node { + id name slug description related_object active template preview_url +} } } }`; + +export function useDocumentTemplates() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["document-templates", keyExtra(ctx)], + queryFn: async () => page(await graphqlEdges(ctx, DOCUMENT_TEMPLATES_QUERY, "document_templates")), + enabled: Boolean(ctx.token), + }); +} + +// --- Manifests (shipment_count derived from shipment_identifiers) ----------- +const MANIFESTS_QUERY = `query { manifests { edges { node { + id carrier_name reference created_at manifest_url shipment_identifiers +} } } }`; + +type RawManifest = Omit & { shipment_identifiers?: string[] }; + +export function useManifests() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["manifests", keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, MANIFESTS_QUERY, "manifests"); + return page(raws.map((m) => ({ ...m, shipment_count: m.shipment_identifiers?.length ?? 0 }))); + }, + enabled: Boolean(ctx.token), + }); +} + +// --- Batch operations (total derived from resources) ------------------------ +const BATCHES_QUERY = `query { batch_operations { edges { node { + id status resource_type created_at resources { id } +} } } }`; + +type RawBatch = Omit & { resources?: Array<{ id: string }> }; + +export function useBatches() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["batches", keyExtra(ctx)], + queryFn: async () => { + const raws = await graphqlEdges(ctx, BATCHES_QUERY, "batch_operations"); + return page(raws.map((b) => ({ ...b, total: b.resources?.length ?? 0 }))); + }, + enabled: Boolean(ctx.token), + }); +} diff --git a/apps/studio/src/lib/karrio/metastore.ts b/apps/studio/src/lib/karrio/metastore.ts new file mode 100644 index 000000000..4476dbfa9 --- /dev/null +++ b/apps/studio/src/lib/karrio/metastore.ts @@ -0,0 +1,65 @@ +// metastore.ts — round-trippable per-user backend KV via Karrio metafields. +// Studio-native app state (UI preferences, agent + MCP configs) is stored as +// JSON metafields under a `studio.*` key namespace, scoped to the authenticated +// user by Karrio's ownership model. This is the real backend behind the +// localStorage caches in preferences.ts / agents.ts (unblocks EBE-99). +// +// Verified live: a free-standing `create_metafield(key, type:json, value)` plus +// `metafields(filter:{ key })` gives a clean per-user create/read/update/delete +// round trip — no backend change required. +import { graphql, type KarrioCtx } from "~/lib/karrio/client"; + +// The SessionProvider registers the current authenticated ctx here (client-side) +// so ctx-less stores (agents.ts) can reach the backend without threading ctx +// through every call. Null on the server / when unauthenticated. +let studioCtx: KarrioCtx | null = null; +export function setStudioCtx(ctx: KarrioCtx | null): void { + studioCtx = ctx; +} +export function getStudioCtx(): KarrioCtx | null { + return studioCtx; +} + +const READ = `query($key: String!) { + metafields(filter: { key: $key }) { edges { node { id value } } } +}`; +const CREATE = `mutation($input: CreateMetafieldInput!) { + create_metafield(input: $input) { metafield { id } errors { field messages } } +}`; +const UPDATE = `mutation($input: UpdateMetafieldInput!) { + update_metafield(input: $input) { metafield { id } errors { field messages } } +}`; +const DELETE = `mutation($input: DeleteMutationInput!) { + delete_metafield(input: $input) { id } +}`; + +type ReadResult = { metafields: { edges: Array<{ node: { id: string; value: T } }> } }; + +async function find(ctx: KarrioCtx, key: string): Promise<{ id: string; value: T } | null> { + const data = await graphql>>(ctx, READ, { key }); + return data?.metafields?.edges?.[0]?.node ?? null; +} + +/** Read a JSON metafield value by key, or null when absent/unauthenticated. */ +export async function readMeta(ctx: KarrioCtx, key: string): Promise { + if (!ctx.token) return null; + return (await find(ctx, key))?.value ?? null; +} + +/** Upsert a JSON metafield by key (create or update). No-op when unauthenticated. */ +export async function writeMeta(ctx: KarrioCtx, key: string, value: unknown): Promise { + if (!ctx.token) return; + const existing = await find(ctx, key); + if (existing) { + await graphql(ctx, UPDATE, { input: { id: existing.id, value } }); + } else { + await graphql(ctx, CREATE, { input: { key, type: "json", value } }); + } +} + +/** Delete the metafield for a key, if present. */ +export async function deleteMeta(ctx: KarrioCtx, key: string): Promise { + if (!ctx.token) return; + const existing = await find(ctx, key); + if (existing) await graphql(ctx, DELETE, { input: { id: existing.id } }); +} diff --git a/apps/studio/src/lib/karrio/preferences.ts b/apps/studio/src/lib/karrio/preferences.ts new file mode 100644 index 000000000..99bef2af8 --- /dev/null +++ b/apps/studio/src/lib/karrio/preferences.ts @@ -0,0 +1,220 @@ +// preferences.ts — single source of truth for Studio UI preferences. +// +// Architecture: +// Layer 1 (synchronous): localStorage — offline cache, survives reload, +// identical to prior behaviour. All reads/writes go through here first. +// Layer 2 (async): Karrio backend — persisted in User.metadata under the +// "studio.customization" namespace via the update_user GraphQL mutation. +// +// Backend store: User.metadata JSONField on the Karrio User model. +// Write: update_user(input: { metadata: { "studio.customization": } }) +// Read: The OSS UserType query does not expose metadata in its GraphQL +// selection set (only email/full_name/is_staff/permissions are returned). +// TODO(backend): Add `metadata: JSON` field to UserType so that +// loadFromBackend() can hydrate prefs on fresh login without localStorage. +// Until then, loadFromBackend() is a documented no-op on reads; the +// localStorage cache is the authoritative read source. +// +// Usage from components: +// import { loadPrefs, savePrefs, syncToBackend } from "~/lib/karrio/preferences"; +// const prefs = loadPrefs(); // synchronous — always safe +// savePrefs({ accent: "#10B981" }); // synchronous + kicks off async sync +// await syncToBackend(ctx, prefs); // explicit flush (e.g. on unload) + +import type { KarrioCtx } from "~/lib/karrio/client"; +import { readMeta, writeMeta } from "~/lib/karrio/metastore"; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export type Theme = "dark" | "light"; +export type Density = "compact" | "regular" | "comfy"; +export type FontStack = "Inter" | "IBM Plex" | "System"; + +/** All Studio appearance / layout preferences. */ +export type Preferences = { + theme: Theme; + accent: string; + density: Density; + font_stack: FontStack; + /** Reserved for future panel/layout configuration (sidebar width, pane splits, etc.) */ + layout?: Record; +}; + +// --------------------------------------------------------------------------- +// Defaults +// --------------------------------------------------------------------------- + +export const DEFAULT_PREFERENCES: Readonly = { + theme: "dark", + accent: "#8B5CF6", + density: "regular", + font_stack: "Inter", +}; + +// --------------------------------------------------------------------------- +// localStorage keys (kept stable for backwards compat with prior theme.ts) +// --------------------------------------------------------------------------- + +const LS_KEYS = { + theme: "karrio-theme", + accent: "karrio-accent", + density: "karrio-density", + fontStack: "karrio-font", +} as const; + +// Namespace used when writing into User.metadata on the Karrio backend. +export const BACKEND_META_KEY = "studio.customization"; + +// --------------------------------------------------------------------------- +// localStorage helpers +// --------------------------------------------------------------------------- + +function lsGet(key: string): string | null { + try { + return typeof localStorage !== "undefined" ? localStorage.getItem(key) : null; + } catch { + return null; + } +} + +function lsSet(key: string, value: string): void { + try { + if (typeof localStorage !== "undefined") localStorage.setItem(key, value); + } catch { + // Silently ignore (e.g. private browsing with storage disabled). + } +} + +// --------------------------------------------------------------------------- +// Public API — synchronous layer (localStorage) +// --------------------------------------------------------------------------- + +/** Load preferences from localStorage, falling back to defaults. */ +export function loadPrefs(): Preferences { + return { + theme: (lsGet(LS_KEYS.theme) as Theme) || DEFAULT_PREFERENCES.theme, + accent: lsGet(LS_KEYS.accent) || DEFAULT_PREFERENCES.accent, + density: (lsGet(LS_KEYS.density) as Density) || DEFAULT_PREFERENCES.density, + font_stack: (lsGet(LS_KEYS.fontStack) as FontStack) || DEFAULT_PREFERENCES.font_stack, + }; +} + +/** Persist a partial preferences update to localStorage. Returns the new full prefs. */ +export function savePrefs(patch: Partial): Preferences { + const current = loadPrefs(); + const next: Preferences = { ...current, ...patch }; + + if (patch.theme !== undefined) lsSet(LS_KEYS.theme, next.theme); + if (patch.accent !== undefined) lsSet(LS_KEYS.accent, next.accent); + if (patch.density !== undefined) lsSet(LS_KEYS.density, next.density); + if (patch.font_stack !== undefined) lsSet(LS_KEYS.fontStack, next.font_stack); + + return next; +} + +// --------------------------------------------------------------------------- +// DOM appliers — apply prefs live via CSS variables / data attributes +// --------------------------------------------------------------------------- + +const FONT_STACKS: Record = { + Inter: `"Inter", -apple-system, sans-serif`, + "IBM Plex": `"IBM Plex Sans", "Inter", sans-serif`, + System: `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`, +}; + +export function applyPrefsToDOM(prefs: Preferences): void { + if (typeof document === "undefined") return; + const root = document.documentElement; + root.setAttribute("data-theme", prefs.theme); + root.style.setProperty("--accent", prefs.accent); + root.dataset.density = prefs.density; + root.style.setProperty("--font-sans", FONT_STACKS[prefs.font_stack]); +} + +/** Apply a single field immediately (no save). */ +export function applyTheme(theme: Theme): void { + if (typeof document !== "undefined") + document.documentElement.setAttribute("data-theme", theme); +} + +export function applyAccent(accent: string): void { + if (typeof document !== "undefined") + document.documentElement.style.setProperty("--accent", accent); +} + +export function applyDensity(density: Density): void { + if (typeof document !== "undefined") + document.documentElement.dataset.density = density; +} + +export function applyFont(font: FontStack): void { + if (typeof document !== "undefined") + document.documentElement.style.setProperty("--font-sans", FONT_STACKS[font]); +} + +// --------------------------------------------------------------------------- +// Backend adapter — async sync layer (Karrio metafields, key-namespaced) +// --------------------------------------------------------------------------- +// Studio prefs persist as a per-user JSON metafield under BACKEND_META_KEY +// ("studio.customization"). Unlike User.metadata (write-only on the OSS +// GraphQL), metafields round-trip cleanly — so loadFromBackend() actually +// hydrates. See metastore.ts. + +/** + * Flush the given preferences to the Karrio backend (metafield + * `studio.customization`). No-op + silent when unauthenticated or on error — + * localStorage remains the applied source of truth in that case. + */ +export async function syncToBackend( + ctx: KarrioCtx, + prefs: Preferences, +): Promise { + try { + await writeMeta(ctx, BACKEND_META_KEY, prefs); + } catch { + // Sync failure is non-fatal; local settings stay applied and re-sync later. + } +} + +/** + * Load preferences from the Karrio backend (metafield `studio.customization`) + * into localStorage, and return the merged preferences. Falls back to the + * localStorage cache when unauthenticated or on any error. + */ +export async function loadFromBackend( + ctx: KarrioCtx, +): Promise { + try { + const remote = await readMeta>(ctx, BACKEND_META_KEY); + if (remote) { + const merged = { ...loadPrefs(), ...remote }; + savePrefs(merged); + return merged; + } + } catch { + // Ignore — fall through to the localStorage cache. + } + return loadPrefs(); +} + +// --------------------------------------------------------------------------- +// Convenience — save + apply + async sync in one call +// --------------------------------------------------------------------------- + +/** + * Apply a preference patch: persists to localStorage, applies to DOM immediately, + * and fires-and-forgets an async sync to the backend. + * + * Pass `ctx` from `useKarrioCtx()` when called inside a React component. + */ +export function applyAndSavePrefs( + patch: Partial, + ctx?: KarrioCtx, +): Preferences { + const next = savePrefs(patch); + applyPrefsToDOM(next); + if (ctx) void syncToBackend(ctx, next); + return next; +} diff --git a/apps/studio/src/lib/karrio/references.ts b/apps/studio/src/lib/karrio/references.ts new file mode 100644 index 000000000..ae2c0c724 --- /dev/null +++ b/apps/studio/src/lib/karrio/references.ts @@ -0,0 +1,77 @@ +// references.ts — Karrio API metadata (`/v1/references`): the carrier registry, +// per-carrier connection field schemas (for dynamic credential forms), and the +// deployment feature flags. This is the same metadata the dashboard's +// useAPIMetadata consumes; it drives dynamic connection forms (EBE-110) and +// feature-flag gating (EBE-109). +import { useQuery } from "@tanstack/react-query"; +import { restGet } from "~/lib/karrio/client"; +import { useKarrioCtx } from "~/lib/karrio/session"; +import { keyExtra } from "~/lib/karrio/hooks/_shared"; + +export type ConnectionFieldType = "string" | "number" | "boolean" | "object" | string; + +export type ConnectionField = { + name: string; + label?: string; + type: ConnectionFieldType; + required?: boolean; + sensitive?: boolean; + default?: unknown; + enum?: string[]; +}; + +export type References = { + VERSION?: string; + APP_NAME?: string; + /** carrier_name → display name */ + carriers?: Record; + /** carrier_name → { field_name → field schema } */ + connection_fields?: Record>; + // Feature flags (gate EE / optional modules) + MULTI_ORGANIZATIONS?: boolean; + ORDERS_MANAGEMENT?: boolean; + APPS_MANAGEMENT?: boolean; + DOCUMENTS_MANAGEMENT?: boolean; + DATA_IMPORT_EXPORT?: boolean; + WORKFLOW_MANAGEMENT?: boolean; + SHIPPING_RULES?: boolean; + ADMIN_DASHBOARD?: boolean; + ALLOW_SIGNUP?: boolean; + AUDIT_LOGGING?: boolean; + [key: string]: unknown; +}; + +export function useReferences() { + const ctx = useKarrioCtx(); + return useQuery({ + queryKey: ["references", keyExtra(ctx)], + queryFn: () => restGet(ctx, "/v1/references"), + enabled: Boolean(ctx.token), + staleTime: 60 * 60_000, // metadata is stable for the session + }); +} + +// --- Feature flags (EBE-109) ------------------------------------------------ +export type FeatureFlag = + | "MULTI_ORGANIZATIONS" + | "ORDERS_MANAGEMENT" + | "APPS_MANAGEMENT" + | "DOCUMENTS_MANAGEMENT" + | "DATA_IMPORT_EXPORT" + | "WORKFLOW_MANAGEMENT" + | "SHIPPING_RULES" + | "ADMIN_DASHBOARD" + | "AUDIT_LOGGING"; + +/** + * Read deployment feature flags from the references payload. Defaults to `true` + * for unknown flags so a screen never hides itself before metadata loads; gate + * EE/optional features with an explicit `false`. + */ +export function useFeatureFlags() { + const { data } = useReferences(); + return { + flags: data, + isEnabled: (flag: FeatureFlag): boolean => (data ? data[flag] !== false : true), + }; +} diff --git a/apps/studio/src/lib/karrio/session.tsx b/apps/studio/src/lib/karrio/session.tsx new file mode 100644 index 000000000..fe3771a90 --- /dev/null +++ b/apps/studio/src/lib/karrio/session.tsx @@ -0,0 +1,126 @@ +// session.tsx — framework-agnostic session context (no NextAuth). +// The token comes from the Studio server session cookie via the getSession +// server function; org + test-mode are client-controlled. Everything data-layer +// reads its KarrioCtx from here. +import { + createContext, + useContext, + useEffect, + useMemo, + useState, + type ReactNode, +} from "react"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { getSession, refreshSession } from "~/server/auth"; +import { karrioBaseUrl } from "~/lib/karrio/env"; +import { setRefreshHandler, type KarrioCtx } from "~/lib/karrio/client"; +import { applyPrefsToDOM, loadFromBackend } from "~/lib/karrio/preferences"; +import { setStudioCtx } from "~/lib/karrio/metastore"; + +type SessionContextValue = { + ctx: KarrioCtx; + isAuthenticated: boolean; + email?: string; + testMode: boolean; + setTestMode: (on: boolean) => void; + orgId?: string; + setOrgId: (id?: string) => void; +}; + +const SessionContext = createContext(null); + +const TEST_MODE_KEY = "karrio-studio:test-mode"; +const ORG_KEY = "karrio-studio:org-id"; +const lsGet = (k: string): string | null => { + try { + return typeof localStorage !== "undefined" ? localStorage.getItem(k) : null; + } catch { + return null; + } +}; +const lsSet = (k: string, v: string | null): void => { + try { + if (typeof localStorage === "undefined") return; + if (v === null) localStorage.removeItem(k); + else localStorage.setItem(k, v); + } catch { + /* storage may be unavailable */ + } +}; + +export function SessionProvider({ children }: { children: ReactNode }) { + const baseUrl = karrioBaseUrl(); + const queryClient = useQueryClient(); + const [testMode, setTestModeState] = useState(false); + const [orgId, setOrgIdState] = useState(undefined); + + // Hydrate test-mode + org from localStorage on the client (avoids SSR mismatch), + // so the selected mode persists across reloads and navigation. + useEffect(() => { + setTestModeState(lsGet(TEST_MODE_KEY) === "1"); + setOrgIdState(lsGet(ORG_KEY) ?? undefined); + }, []); + + const setTestMode = (on: boolean) => { + lsSet(TEST_MODE_KEY, on ? "1" : "0"); + setTestModeState(on); + }; + const setOrgId = (id?: string) => { + lsSet(ORG_KEY, id ?? null); + setOrgIdState(id); + }; + + const sessionQuery = useQuery({ + queryKey: ["studio-session"], + queryFn: () => getSession(), + staleTime: 5 * 60_000, + }); + + // Register the 401 → refresh handler so expired access tokens are rotated + // transparently. Updates the cached session so ctx.token reflects the new + // access token for subsequent requests. + useEffect(() => { + setRefreshHandler(async () => { + const next = await refreshSession(); + queryClient.setQueryData(["studio-session"], next ?? null); + return next?.access ?? null; + }); + return () => setRefreshHandler(null); + }, [queryClient]); + + // Hydrate Studio UI preferences from the backend (metafield `studio.customization`) + // once authenticated, so settings follow the user across devices. + const token = sessionQuery.data?.access; + useEffect(() => { + // Register the current ctx so ctx-less stores (agents/MCP configs) can reach + // the backend, then hydrate UI preferences from the backend metafield. + setStudioCtx(token ? { baseUrl, token, orgId, testMode } : null); + if (!token) return; + void loadFromBackend({ baseUrl, token, orgId, testMode }).then(applyPrefsToDOM); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [token, baseUrl, orgId, testMode]); + + const value = useMemo(() => { + return { + ctx: { baseUrl, token, orgId, testMode }, + isAuthenticated: Boolean(token), + email: sessionQuery.data?.email, + testMode, + setTestMode, + orgId, + setOrgId, + }; + }, [baseUrl, orgId, testMode, sessionQuery.data]); + + return {children}; +} + +export function useSession(): SessionContextValue { + const ctx = useContext(SessionContext); + if (!ctx) throw new Error("useSession must be used within "); + return ctx; +} + +export function useKarrioCtx(): KarrioCtx { + return useSession().ctx; +} diff --git a/apps/studio/src/lib/karrio/types.ts b/apps/studio/src/lib/karrio/types.ts new file mode 100644 index 000000000..8311bce2f --- /dev/null +++ b/apps/studio/src/lib/karrio/types.ts @@ -0,0 +1,354 @@ +// types.ts — local Karrio API types (decoupled; no @karrio/types dependency). +// Only the fields Studio screens consume are modeled; extend as screens grow. + +export type Paginated = { + count: number; + next: string | null; + previous: string | null; + results: T[]; +}; + +export type Address = { + city?: string; + state_code?: string; + country_code?: string; + person_name?: string; + company_name?: string; + address_line1?: string; + address_line2?: string; + postal_code?: string; + email?: string; + phone_number?: string; + residential?: boolean; +}; + +export type Charge = { name?: string; amount?: number; currency?: string }; + +export type Parcel = { + id?: string; + weight?: number; + weight_unit?: string; + width?: number; + height?: number; + length?: number; + dimension_unit?: string; + packaging_type?: string; + reference_number?: string; +}; + +export type Rate = { + carrier_name?: string; + carrier_id?: string; + service?: string; + total_charge?: number; + currency?: string; + transit_days?: number; + extra_charges?: Charge[]; +}; + +export type Shipment = { + id: string; + status: string; + carrier_name?: string; + carrier_id?: string; + tracking_number?: string; + service?: string; + selected_rate?: Rate | null; + parcels?: Parcel[]; + recipient?: Address; + shipper?: Address; + reference?: string; + created_at?: string; + meta?: Record; +}; + +export type Tracker = { + id: string; + tracking_number: string; + carrier_name?: string; + status: string; + estimated_delivery?: string; + events?: Array<{ description?: string; location?: string; date?: string; time?: string }>; + meta?: Record; +}; + +export type Order = { + id: string; + order_id?: string; + status: string; + source?: string; + created_at?: string; + line_items?: Array<{ id: string; title?: string; quantity?: number }>; + shipping_to?: Address; +}; + +export type CarrierConnection = { + id: string; + carrier_name: string; + carrier_id: string; + test_mode?: boolean; + active?: boolean; + capabilities?: string[]; +}; + +export type Pickup = { + id: string; + confirmation_number?: string; + carrier_name?: string; + pickup_date?: string; + ready_time?: string; + closing_time?: string; + status?: string; + address?: Address; +}; + +// AddressTemplate: live schema exposes the Address model directly via the +// `addresses` Connection. Fields are flat on the node; label/is_default live +// in the `meta` JSON field. The hook normalises meta into top-level `label`, +// `is_default`, and an `address` sub-object for screen backward-compat. +export type AddressTemplate = { + id: string; + /** Populated by hook from meta.label */ + label?: string; + /** Populated by hook from meta.is_default */ + is_default?: boolean; + /** Raw meta from the API (contains label, is_default, …) */ + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + /** Nested address fields (normalised by hook from flat wire fields) */ + address?: Address; + // Also exposed flat for direct access: + person_name?: string; + company_name?: string; + address_line1?: string; + address_line2?: string; + city?: string; + state_code?: string; + postal_code?: string; + country_code?: string; + email?: string; + phone_number?: string; + residential?: boolean; +}; + +// ParcelTemplate: live schema exposes the Parcel model directly via the +// `parcels` Connection. Fields are flat; label/is_default live in `meta`. +// The hook normalises meta into top-level `label` and `is_default`. +export type ParcelTemplate = { + id: string; + /** Populated by hook from meta.label */ + label?: string; + /** Populated by hook from meta.is_default */ + is_default?: boolean; + /** Raw meta from the API */ + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + packaging_type?: string; + width?: number; + height?: number; + length?: number; + dimension_unit?: string; + weight?: number; + weight_unit?: string; +}; + +// ProductTemplate: live schema exposes the Commodity model via the `products` +// Connection. label/is_default live in `meta`. The hook normalises them. +export type ProductTemplate = { + id: string; + /** Populated by hook from meta.label */ + label?: string; + /** Populated by hook from meta.is_default */ + is_default?: boolean; + /** Raw meta from the API */ + meta?: { label?: string; is_default?: boolean; [key: string]: unknown }; + title?: string; + sku?: string; + hs_code?: string; + weight?: number; + weight_unit?: string; + value_amount?: number; + value_currency?: string; + origin_country?: string; +}; + +export type ShippingRule = { + id: string; + name: string; + priority?: number; + is_active?: boolean; + description?: string; + action_type?: string; +}; + +export type DocumentTemplate = { + id: string; + name: string; + slug?: string; + related_object?: string; + description?: string; + active?: boolean; + /** Liquid/HTML template body */ + template?: string; + /** Server render URL (relative to the API base): renders with sample data */ + preview_url?: string; +}; + +export type App = { + id: string; + name: string; + vendor?: string; + description?: string; + installed?: boolean; + status?: string; + category?: string; + badge?: string; +}; + +export type Plugin = { + id: string; + name: string; + vendor?: string; + description?: string; + installed?: boolean; + status?: string; + version?: string; + tags?: string[]; + badge?: string; +}; + +export type Webhook = { + id: string; + url: string; + description?: string; + enabled?: boolean; + events?: string[]; +}; + +export type ApiKey = { + id: string; + label?: string; + key: string; + test_mode?: boolean; + created?: string; +}; + +export type McpTool = { name: string; description?: string; requests?: number; p99?: string }; +export type McpClient = { id: string; name: string; connected?: boolean; last_seen?: string; calls?: number }; +export type McpInvocation = { id: string; tool: string; client?: string; duration_ms?: number; at?: string }; +export type McpInfo = { + status?: string; + url?: string; + version?: string; + stats?: { tools?: number; clients?: number; calls_24h?: string; p99?: string }; + tools?: McpTool[]; + clients?: McpClient[]; + invocations?: McpInvocation[]; +}; + +export type Tenant = { + id: string; + name: string; + slug?: string; + status?: string; + members?: number; + created?: string; +}; + +export type TeamMember = { + id: string; + name?: string; + email: string; + role?: string; + status?: string; +}; + +export type AuditEvent = { + id: string; + type?: string; + actor?: string; + description?: string; + at?: string; +}; + +export type AdminInfo = { + version?: string; + tenants?: number; + license?: string; + users?: number; + system_connections?: number; + worker_available?: boolean; + resources?: { label: string; used: number; total: number }[]; + runtimes?: { name: string; memory?: string; calls?: number; p99?: string }[]; +}; + +// --- Parity resources (manifests, batches, workflows, rate sheets, usage) ---- +export type Manifest = { + id: string; + carrier_name?: string; + reference?: string; + shipment_count?: number; + created_at?: string; + manifest_url?: string; +}; + +export type BatchOperation = { + id: string; + status?: string; + resource_type?: string; + total?: number; + created_at?: string; +}; + +export type Workflow = { + id: string; + name: string; + description?: string; + is_active?: boolean; + trigger?: string; + action_count?: number; +}; + +// RateSheet: live OSS schema fields are id, name, slug, carrier_name. +// `services_count` and `is_system` do not exist on the OSS RateSheetType and +// will always be undefined when fetched from OSS; screens degrade gracefully. +export type RateSheetService = { + id?: string; + service_code: string; + service_name: string; + currency?: string; + cost?: number; + min_weight?: number; + max_weight?: number; + weight_unit?: string; + transit_days?: number; +}; +export type RateSheet = { + id: string; + name: string; + slug?: string; + carrier_name?: string; + services?: RateSheetService[]; + /** OSS: always undefined — not exposed by the OSS GraphQL schema */ + services_count?: number; + /** OSS: always undefined — not exposed by the OSS GraphQL schema */ + is_system?: boolean; +}; + +export type UsageRecord = { + label: string; + value: string; + delta?: string; +}; + +export type UsagePoint = { date: string; count: number }; +export type UsageSeries = { + key: string; + label: string; + format?: "number" | "currency"; + points: UsagePoint[]; +}; +export type UsageSummary = { + plan?: string; + period?: string; + metrics?: UsageRecord[]; + series?: UsageSeries[]; +}; diff --git a/apps/studio/src/lib/modes.ts b/apps/studio/src/lib/modes.ts new file mode 100644 index 000000000..6dc499510 --- /dev/null +++ b/apps/studio/src/lib/modes.ts @@ -0,0 +1,136 @@ +// modes.ts — Ship / Build / Govern information architecture. +// Mode is the core IA primitive: switching swaps the nav and jumps to the +// mode default route; mode is also auto-derived from the active route so +// deep links land in the right mode. +import { Icon, type IconName } from "~/components/ui/icons"; + +export type Mode = "ship" | "build" | "govern"; + +export type NavItem = { + icon: IconName; + label: string; + route: string; + badge?: string; + /** Optional deployment feature flag (from /v1/references) that gates this item. */ + flag?: string; +}; + +export type NavGroup = { + label?: string; + items: NavItem[]; +}; + +export const MODE_LABELS: Record = { + ship: { label: "Ship", icon: "Truck" }, + build: { label: "Build", icon: "Code" }, + govern: { label: "Govern", icon: "Shield" }, +}; + +export const MODE_DEFAULTS: Record = { + ship: "home", + build: "apps", + govern: "admin", +}; + +export const NAV: Record = { + ship: [ + { + items: [ + { icon: "Home", label: "Home", route: "home" }, + { icon: "Truck", label: "Shipments", route: "shipments" }, + { icon: "Pin", label: "Trackers", route: "trackers" }, + { icon: "Inbox", label: "Orders", route: "orders", flag: "ORDERS_MANAGEMENT" }, + { icon: "Box", label: "Pickups", route: "pickups" }, + ], + }, + { + label: "Setup", + items: [ + { icon: "Plug", label: "Connections", route: "connections" }, + { icon: "Activity", label: "Shipping rules", route: "rules", flag: "SHIPPING_RULES" }, + { icon: "Pin", label: "Addresses", route: "addresses" }, + { icon: "Pkg", label: "Parcels", route: "parcels" }, + { icon: "Tag", label: "Products", route: "products" }, + { icon: "Doc", label: "Documents", route: "documents", flag: "DOCUMENTS_MANAGEMENT" }, + ], + }, + { + label: "Operations", + items: [ + { icon: "Doc", label: "Manifests", route: "manifests" }, + { icon: "Grid", label: "Batches", route: "batches" }, + ], + }, + ], + build: [ + { + items: [ + { icon: "Grid", label: "Apps", route: "apps", flag: "APPS_MANAGEMENT" }, + { icon: "Plug", label: "Plugins", route: "plugins" }, + { icon: "Terminal", label: "MCP", route: "mcp" }, + { icon: "Code", label: "Editor", route: "editor" }, + ], + }, + { + label: "Developer", + items: [ + { icon: "Webhook", label: "Webhooks", route: "webhooks" }, + { icon: "Key", label: "API keys", route: "apikeys" }, + { icon: "Activity", label: "Workflows", route: "workflows", flag: "WORKFLOW_MANAGEMENT" }, + ], + }, + ], + govern: [ + { + items: [ + { icon: "Home", label: "Overview", route: "admin" }, + { icon: "Workspace", label: "Tenants", route: "tenants", flag: "MULTI_ORGANIZATIONS" }, + { icon: "User", label: "Team & roles", route: "team" }, + { icon: "Lock", label: "Security", route: "security" }, + { icon: "Doc", label: "Audit log", route: "audit" }, + { icon: "Settings", label: "Settings", route: "settings" }, + ], + }, + { + label: "Billing & rates", + items: [ + { icon: "Tag", label: "Rate sheets", route: "ratesheets" }, + { icon: "Activity", label: "Usage & billing", route: "usage" }, + ], + }, + ], +}; + +const BUILD_ROUTES = new Set([ + "apps", + "plugins", + "mcp", + "editor", + "webhooks", + "apikeys", + "workflows", +]); +const GOVERN_ROUTES = new Set([ + "admin", + "tenants", + "team", + "security", + "audit", + "settings", + "ratesheets", + "usage", +]); + +export function routeMode(route: string): Mode { + if (BUILD_ROUTES.has(route)) return "build"; + if (GOVERN_ROUTES.has(route)) return "govern"; + return "ship"; +} + +// Flat list of every screen route — used by the splat route + tests. +export const ALL_ROUTES: string[] = Object.values(NAV) + .flat() + .flatMap((group) => group.items.map((item) => item.route)); + +// Re-export Icon so consumers importing from modes get a single source. +export { Icon }; diff --git a/apps/studio/src/lib/monitoring.ts b/apps/studio/src/lib/monitoring.ts new file mode 100644 index 000000000..dc6ade9e4 --- /dev/null +++ b/apps/studio/src/lib/monitoring.ts @@ -0,0 +1,30 @@ +// monitoring.ts — thin, dependency-free observability seam (H4). +// Real SDKs (Sentry, PostHog) are wired at deploy time behind these functions +// so app code never imports them directly. No-ops (console in dev) until a +// provider is registered via registerMonitor(), keeping the bundle lean. +type Monitor = { + captureError: (error: unknown, context?: Record) => void; + trackEvent: (name: string, props?: Record) => void; +}; + +let monitor: Monitor | null = null; + +export function registerMonitor(m: Monitor) { + monitor = m; +} + +export function captureError(error: unknown, context?: Record) { + if (monitor) return monitor.captureError(error, context); + if (import.meta.env?.DEV) console.error("[monitor] error", error, context ?? ""); +} + +export function trackEvent(name: string, props?: Record) { + if (monitor) return monitor.trackEvent(name, props); + if (import.meta.env?.DEV) console.debug("[monitor] event", name, props ?? ""); +} + +// Wire SDKs here when DSN/keys are configured (deploy-time). Example: +// if (import.meta.env.VITE_SENTRY_DSN) { const s = await import("@sentry/browser"); s.init(...); registerMonitor({...}); } +export function initMonitoring() { + // Intentionally empty by default — providers register lazily at deploy time. +} diff --git a/apps/studio/src/lib/studio-state.ts b/apps/studio/src/lib/studio-state.ts new file mode 100644 index 000000000..efcc971f8 --- /dev/null +++ b/apps/studio/src/lib/studio-state.ts @@ -0,0 +1,50 @@ +// studio-state.ts — Studio-native state contract. +// +// DECISION (passthrough): Studio does NOT own a database. Every piece of state +// — shipping data AND Studio-native state (customization, agents, MCP config) — +// is persisted on the Karrio backend and read/written through the Karrio +// GraphQL/REST API (via @karrio/hooks). This keeps a single source of truth and +// lets Studio stay a thin client. +// +// Mapping of Studio-native state onto existing Karrio surfaces: +// +// Customization (theme/accent/density/font/layout) +// → User.metadata["studio.customization"] on the Karrio backend. +// localStorage is the synchronous cache; async sync via update_user +// GraphQL mutation. See ~/lib/karrio/preferences.ts for full details. +// +// Agent sessions / runs / messages +// → Karrio metafields namespaced under `studio.agents.*` +// (graduates to dedicated Karrio endpoints if/when added backend-side) +// +// MCP servers / clients / invocations +// → Karrio MCP server (packages/mcp + Django) + metafields +// `studio.mcp.*`; live status/invocations come from the MCP server. +// +// These namespaces keep Studio state isolated within Karrio metadata so no +// backend migrations are required to ship Studio. + +export const STUDIO_NS = { + customization: "studio.customization", + agents: "studio.agents", + mcp: "studio.mcp", +} as const; + +// Customization types and defaults are now owned by ~/lib/karrio/preferences.ts +// (single source of truth). Re-exported here for backwards compatibility with +// any imports that reference studio-state.ts directly. +export type { + Preferences as Customization, + Theme, + Density, + FontStack, +} from "~/lib/karrio/preferences"; + +export { + DEFAULT_PREFERENCES as DEFAULT_CUSTOMIZATION, + loadPrefs, + savePrefs, + applyAndSavePrefs, + syncToBackend, + loadFromBackend, +} from "~/lib/karrio/preferences"; diff --git a/apps/studio/src/lib/theme.ts b/apps/studio/src/lib/theme.ts new file mode 100644 index 000000000..55af22cad --- /dev/null +++ b/apps/studio/src/lib/theme.ts @@ -0,0 +1,92 @@ +// theme.ts — theme + density preferences (persisted to localStorage; mirrored +// to the Studio DB app_config in a later phase). Dark is the default. +export type Theme = "dark" | "light"; +export type Density = "compact" | "regular" | "comfy"; + +const THEME_KEY = "karrio-theme"; +const SIDEBAR_KEY = "karrio-sidebar-collapsed"; + +export function getStoredTheme(): Theme { + if (typeof localStorage === "undefined") return "dark"; + return (localStorage.getItem(THEME_KEY) as Theme) || "dark"; +} + +export function setStoredTheme(theme: Theme): void { + if (typeof document !== "undefined") { + document.documentElement.setAttribute("data-theme", theme); + } + if (typeof localStorage !== "undefined") { + localStorage.setItem(THEME_KEY, theme); + } +} + +export function getSidebarCollapsed(): boolean { + if (typeof localStorage === "undefined") return false; + return localStorage.getItem(SIDEBAR_KEY) === "1"; +} + +export function setSidebarCollapsed(collapsed: boolean): void { + if (typeof localStorage !== "undefined") { + localStorage.setItem(SIDEBAR_KEY, collapsed ? "1" : "0"); + } +} + +// --- Tweaks (accent / density / font) — G1–G3 ------------------------------- +export type FontStack = "Inter" | "IBM Plex" | "System"; + +const ACCENT_KEY = "karrio-accent"; +const DENSITY_KEY = "karrio-density"; +const FONT_KEY = "karrio-font"; + +export const ACCENTS = ["#8B5CF6", "#3B82F6", "#10B981", "#F97316", "#E11D48"] as const; + +const FONT_STACKS: Record = { + Inter: `"Inter", -apple-system, sans-serif`, + "IBM Plex": `"IBM Plex Sans", "Inter", sans-serif`, + System: `-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`, +}; + +function ls(key: string): string | null { + try { + return typeof localStorage !== "undefined" ? localStorage.getItem(key) : null; + } catch { + return null; + } +} + +export const getAccent = () => ls(ACCENT_KEY) || ACCENTS[0]; +export const getDensity = () => (ls(DENSITY_KEY) as Density) || "regular"; +export const getFont = () => (ls(FONT_KEY) as FontStack) || "Inter"; + +export function applyAccent(accent: string) { + if (typeof document !== "undefined") document.documentElement.style.setProperty("--accent", accent); + try { + localStorage.setItem(ACCENT_KEY, accent); + } catch {} +} + +export function applyDensity(density: Density) { + if (typeof document !== "undefined") document.documentElement.dataset.density = density; + try { + localStorage.setItem(DENSITY_KEY, density); + } catch {} +} + +export function applyFont(font: FontStack) { + if (typeof document !== "undefined") { + document.documentElement.style.setProperty("--font-sans", FONT_STACKS[font]); + } + try { + localStorage.setItem(FONT_KEY, font); + } catch {} +} + +// Apply all persisted tweaks (call once on mount). +export function applyStoredTweaks() { + applyAccent(getAccent()); + applyDensity(getDensity()); + applyFont(getFont()); +} + +// Inline script injected pre-render in __root to avoid a theme/tweak flash. +export const THEME_INIT_SCRIPT = `(function(){try{var d=document.documentElement;d.setAttribute('data-theme',localStorage.getItem('${THEME_KEY}')||'dark');var a=localStorage.getItem('${ACCENT_KEY}');if(a)d.style.setProperty('--accent',a);var de=localStorage.getItem('${DENSITY_KEY}');if(de)d.dataset.density=de;}catch(e){}})();`; diff --git a/apps/studio/src/lib/utils.ts b/apps/studio/src/lib/utils.ts new file mode 100644 index 000000000..441d8539a --- /dev/null +++ b/apps/studio/src/lib/utils.ts @@ -0,0 +1,7 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +// shadcn `cn` helper. +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/apps/studio/src/router.tsx b/apps/studio/src/router.tsx new file mode 100644 index 000000000..96f2f9d7b --- /dev/null +++ b/apps/studio/src/router.tsx @@ -0,0 +1,17 @@ +import { createRouter as createTanStackRouter } from "@tanstack/react-router"; +import { routeTree } from "./routeTree.gen"; + +// TanStack Start resolves the router entry from this file and expects `getRouter`. +export function getRouter() { + return createTanStackRouter({ + routeTree, + defaultPreload: "intent", + scrollRestoration: true, + }); +} + +declare module "@tanstack/react-router" { + interface Register { + router: ReturnType; + } +} diff --git a/apps/studio/src/routes/__root.tsx b/apps/studio/src/routes/__root.tsx new file mode 100644 index 000000000..7518605d1 --- /dev/null +++ b/apps/studio/src/routes/__root.tsx @@ -0,0 +1,76 @@ +import { + HeadContent, + Outlet, + Scripts, + createRootRoute, +} from "@tanstack/react-router"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useEffect, useState } from "react"; +import { SessionProvider } from "~/lib/karrio/session"; +import { ErrorBoundary } from "~/components/ErrorBoundary"; +import { initMonitoring } from "~/lib/monitoring"; +import { THEME_INIT_SCRIPT } from "~/lib/theme"; +// Tailwind/shadcn theme first, then the bespoke enterprise shell CSS (overrides). +import "~/styles/globals.css"; +import "~/styles/tokens.css"; + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { charSet: "utf-8" }, + { name: "viewport", content: "width=device-width, initial-scale=1" }, + { title: "Karrio Studio" }, + { + name: "description", + content: + "Karrio Studio — ship, build, and govern multi-carrier logistics from one agent-friendly workspace.", + }, + { name: "theme-color", content: "#0b0b0e" }, + ], + links: [ + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Inter:wght@400;450;500;600;700&family=JetBrains+Mono:wght@400;500&family=IBM+Plex+Sans:wght@400;500;600&display=swap", + }, + ], + }), + component: RootComponent, +}); + +function RootComponent() { + // One QueryClient per app instance; shipping data hooks (@karrio/hooks) use it. + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { queries: { staleTime: 30_000, retry: 1 } }, + }), + ); + useEffect(() => initMonitoring(), []); + return ( + + + + + + + + + + ); +} + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + {/* Apply theme before paint to avoid a flash of the wrong theme. */} +