diff --git a/.github/workflows/build-macos-app.yml b/.github/workflows/build-macos-app.yml
new file mode 100644
index 0000000..72b2a3a
--- /dev/null
+++ b/.github/workflows/build-macos-app.yml
@@ -0,0 +1,70 @@
+name: Build macOS App
+
+on:
+ push:
+ tags: ["v*.*.*"]
+ workflow_dispatch:
+
+permissions:
+ contents: write
+
+jobs:
+ build:
+ runs-on: macos-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 10
+
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: pnpm
+ cache-dependency-path: pnpm-lock.yaml
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - uses: dtolnay/rust-toolchain@stable
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ # Runs beforeBuildCommand internally (builds the Vite client, then
+ # scripts/prepare-server-bundle.sh stages the backend) before
+ # compiling and bundling the Rust shell -- one command covers the
+ # whole pipeline, see client/src-tauri/tauri.conf.json.
+ - name: Build the Tauri app
+ working-directory: client
+ run: pnpm exec tauri build
+
+ - name: Zip the .app bundle
+ working-directory: client/src-tauri/target/release/bundle/macos
+ run: ditto -c -k --sequesterRsrc --keepParent "AI Video Editor.app" "AI-Video-Editor-macos-arm64.app.zip"
+
+ # Uploaded on every run (including workflow_dispatch) so the build
+ # can be verified end-to-end -- downloaded from the run's own
+ # artifacts -- before this workflow is ever allowed to touch a real
+ # public release.
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: macos-app
+ path: |
+ client/src-tauri/target/release/bundle/macos/AI-Video-Editor-macos-arm64.app.zip
+ client/src-tauri/target/release/bundle/dmg/*.dmg
+ if-no-files-found: error
+
+ # Only attaches to a public GitHub Release when this run was
+ # actually triggered by a version tag push -- a manual
+ # workflow_dispatch run never touches the public releases page.
+ - name: Attach to release
+ if: startsWith(github.ref, 'refs/tags/')
+ uses: softprops/action-gh-release@v2
+ with:
+ files: |
+ client/src-tauri/target/release/bundle/macos/AI-Video-Editor-macos-arm64.app.zip
+ client/src-tauri/target/release/bundle/dmg/*.dmg
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index f825d06..c4792c5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,9 +9,6 @@ on:
jobs:
lint-and-build:
runs-on: ubuntu-latest
- defaults:
- run:
- working-directory: client
steps:
- uses: actions/checkout@v4
@@ -23,13 +20,18 @@ jobs:
with:
node-version: 20
cache: pnpm
- cache-dependency-path: client/pnpm-lock.yaml
+ cache-dependency-path: pnpm-lock.yaml
+
+ - uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
- name: Install dependencies
run: pnpm install --frozen-lockfile
- - name: Generate Prisma client
- run: pnpm exec prisma generate
+ - name: Generate Prisma client (server)
+ working-directory: server
+ run: bunx prisma generate
- name: Lint
run: pnpm run lint
@@ -40,9 +42,9 @@ jobs:
- name: Build
run: pnpm run build
env:
- # Not required anymore (lib/ai/{transcribe,filler-words}.ts now
- # construct their OpenAI client lazily on first call, not at
- # module load, so `next build` succeeds even with this unset) --
- # kept as defense-in-depth against that regressing back to eager
- # construction. Not a real secret, no API calls happen during build.
+ # Not required for the build itself (lib/ai/{transcribe,filler-words}.ts
+ # construct their OpenAI client lazily, only on first real call --
+ # nothing at build time imports/executes that code) -- kept as
+ # defense-in-depth in case that ever regresses. Not a real secret,
+ # no API calls happen during build.
OPENAI_API_KEY: dummy-key-for-ci-build-only
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6a08ea7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# dependencies
+node_modules
+
+# misc
+.DS_Store
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files (can opt-in for committing if needed)
+.env*
+!.env.example
+
+# typescript
+*.tsbuildinfo
+
+# staged Tauri backend bundle (built by scripts/prepare-server-bundle.sh)
+/server-bundle
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ccfa020..06a390b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,30 +8,32 @@ issues for bugs and ideas.
## Running it locally
On macOS, `./scripts/setup-mac.sh` handles all of this for you (Node/pnpm/
-FFmpeg via Homebrew, `pnpm install`, `prisma generate`, and `.env`) --
+Bun/FFmpeg via Homebrew, `pnpm install`, `prisma generate`, and `.env`) --
see the README's "macOS quick start". Otherwise, manually:
```bash
-cd client
-pnpm install
-cp .env.example .env # then fill in OPENAI_API_KEY
-pnpm exec prisma generate
-pnpm run dev
+pnpm install # installs deps for client + server
+cd server
+cp .env.example .env # then fill in OPENAI_API_KEY
+bunx prisma generate
+cd ..
+pnpm run dev # runs the Vite client and Bun backend together
```
-Open [http://localhost:3000](http://localhost:3000). See
-[`.env.example`](./client/.env.example) for what each variable does.
+Open [http://localhost:5173](http://localhost:5173). See
+[`server/.env.example`](./server/.env.example) for what each variable
+does.
-You'll also need [FFmpeg](https://ffmpeg.org/download.html) on your
-machine for transcript editing and export to work. If burning in captions
-fails with `No such filter: 'subtitles'`, your FFmpeg build doesn't
-include libass — see the `FFMPEG_PATH` note in `.env.example` (or just use
-`scripts/setup-mac.sh`, which installs and points at a build that has it).
+You'll also need [FFmpeg](https://ffmpeg.org/download.html) and
+[Bun](https://bun.sh) (the backend's runtime) on your machine. If burning
+in captions fails with `No such filter: 'subtitles'`, your FFmpeg build
+doesn't include libass — see the `FFMPEG_PATH` note in `.env.example` (or
+just use `scripts/setup-mac.sh`, which installs and points at a build
+that has it).
## Before submitting a PR
```bash
-cd client
pnpm run lint
pnpm run test
pnpm run build
@@ -40,8 +42,10 @@ pnpm run build
All three must pass — CI runs the same three commands on every PR. If
you're changing pure logic (timeline math, transcript mapping, ffmpeg
argument/filter-string building, validation, path handling), add or update
-a test in the matching `*.test.ts` file rather than only checking it by
-hand — `pnpm run test:watch` re-runs on save.
+a test in the matching `*.test.ts` file in whichever package (`client/` or
+`server/`) owns that code, rather than only checking it by hand --
+`pnpm --filter client test:watch` or `pnpm --filter server test:watch`
+re-runs on save.
## Project structure and conventions
diff --git a/README.md b/README.md
index a9227a0..c50317c 100644
--- a/README.md
+++ b/README.md
@@ -44,10 +44,12 @@ the actual export drifting apart, which happened in practice for several
of these (see the issue tracker's closed bugs).
Local persistence (SQLite via Prisma) and project CRUD are built and in
-use. The separate `server/` directory isn't — see the
-[issue tracker](https://github.com/amide-init/transcriptcut/issues) for the
-full breakdown of what's done vs. planned. Everything currently runs
-inside the `client/` Next.js app, including the API routes.
+use. The app is a Vite React SPA (`client/`) talking to a Bun/Hono
+backend (`server/`) — the two run as separate processes, kept in one
+piece by `pnpm run dev` from the repo root. This split (no server-side
+rendering, one deployable backend unit) is prep for an eventual native
+Mac app via Tauri; see the [issue tracker](https://github.com/amide-init/transcriptcut/issues)
+for the full breakdown of what's done vs. planned.
There is no login — v1 is a single local user by design, not a gap to
fill in. **Don't expose this to a public or shared network** without
@@ -57,9 +59,9 @@ adding real authentication first; see `claude.md` section 18.
```text
claude.md — product spec
-client/ — Next.js app (editor UI + API routes)
+client/ — Vite + React editor UI (SPA, no server rendering)
+server/ — Bun + Hono backend (API routes, FFmpeg, Prisma/SQLite, OpenAI)
docs/ — documentation site (VitePress, deployed to GitHub Pages)
-server/ — reserved for future use, not in use yet
```
## Documentation
@@ -77,17 +79,18 @@ pnpm install
pnpm run dev
```
-## Running the client
+## Running the app
-Requires [FFmpeg](https://ffmpeg.org/download.html) on your machine
-(used for cuts and export) and an OpenAI API key (used server-side only,
-for transcription).
+Requires [FFmpeg](https://ffmpeg.org/download.html) and
+[Bun](https://bun.sh) (the backend's runtime) on your machine, plus an
+OpenAI API key (used server-side only, for transcription).
### macOS quick start
-[`scripts/setup-mac.sh`](./scripts/setup-mac.sh) installs Node/pnpm/FFmpeg
-(with subtitle burn-in support) via Homebrew if you don't already have
-them, installs dependencies, and walks you through `.env`:
+[`scripts/setup-mac.sh`](./scripts/setup-mac.sh) installs Node/pnpm/Bun/
+FFmpeg (with subtitle burn-in support) via Homebrew if you don't already
+have them, installs dependencies for the whole workspace, and walks you
+through `.env`:
```bash
git clone https://github.com/amide-init/transcriptcut.git
@@ -95,22 +98,44 @@ cd transcriptcut
./scripts/setup-mac.sh
```
-Then `cd client && pnpm run dev`. Safe to re-run -- it only installs
-what's missing and never overwrites an existing `.env`.
+Then `pnpm run dev` from the repo root. Safe to re-run -- it only
+installs what's missing and never overwrites an existing `.env`.
### Manual setup (any platform)
+```bash
+pnpm install # installs deps for client + server
+cd server
+cp .env.example .env # then fill in OPENAI_API_KEY
+bunx prisma generate
+cd ..
+pnpm run dev # runs the Vite client and Bun backend together
+```
+
+Open [http://localhost:5173](http://localhost:5173). See
+[`server/.env.example`](./server/.env.example) for what each variable
+does, and [`CONTRIBUTING.md`](./CONTRIBUTING.md) for more.
+
+### Download
+
+A native macOS app is also available (Apple Silicon only) — no need to
+clone the repo or run anything. Grab it from the
+**[docs download page](https://amide-init.github.io/transcriptcut/download)**,
+which also covers the one-time Gatekeeper step (this build is ad-hoc
+signed, not signed with a paid Apple Developer account) and the
+first-launch API key setup.
+
+To build it yourself instead:
+
```bash
cd client
-pnpm install
-cp .env.example .env # then fill in OPENAI_API_KEY
-pnpm exec prisma generate
-pnpm run dev
+pnpm exec tauri build
```
-Open [http://localhost:3000](http://localhost:3000). See
-[`.env.example`](./client/.env.example) for what each variable does, and
-[`CONTRIBUTING.md`](./CONTRIBUTING.md) for more.
+Produces `client/src-tauri/target/release/bundle/{macos,dmg}/`. See
+`claude.md` section 35 for how it's wired together, and
+[`.github/workflows/build-macos-app.yml`](./.github/workflows/build-macos-app.yml)
+for how CI builds and releases it on a version tag push.
## Testing
@@ -120,8 +145,7 @@ builder, path-traversal safety, and the CSS-preview/ffmpeg-export formula
parity work described in the issue tracker:
```bash
-cd client
-pnpm run test
+pnpm run test # runs both client and server test suites
```
CI runs the same command on every PR. There's no end-to-end/UI test suite
diff --git a/claude.md b/claude.md
index 7dd8e9b..31c95eb 100644
--- a/claude.md
+++ b/claude.md
@@ -12,7 +12,7 @@ The MVP should prioritize a reliable transcript ↔ video editing workflow over
This project is **local-first and open source**. It runs entirely on your
own machine — no cloud account, no vendor sign-up, no bill — with a single
-`npm install && npm run dev`. The only external dependency is an LLM
+`pnpm install && pnpm run dev`. The only external dependency is an LLM
provider API key (for transcription and AI editing), used purely for
inference calls, never for storage. See section 13 for the full
architecture rationale.
@@ -579,11 +579,24 @@ child process on the same machine as everything else (see section 18).
No cloud account is required to run or develop this project.
-* **Database:** SQLite via Prisma. A single file, no server process, no
- connection string to configure.
+The app is two local processes, not one: a Vite-built React SPA
+(`client/`, no server-side rendering) and a Bun/Hono backend (`server/`)
+that owns the database, filesystem, FFmpeg, and AI calls. `pnpm run dev`
+from the repo root runs both together; in production the built client is
+served as static files, optionally by the same Bun process. This split —
+no server rendering, one deployable backend unit with a clear entrypoint
+— exists so the backend can later run as a Tauri sidecar for a native Mac
+app release; nothing about "no cloud account" or "local-first" changes,
+it's still one machine, one data directory, no accounts.
+
+* **Database:** SQLite via Prisma, using the `@prisma/adapter-libsql`
+ driver adapter (chosen over `better-sqlite3` specifically to avoid
+ native-binding packaging pain under Bun/Tauri). Still a single file, no
+ server process, no connection string to configure.
* **Storage:** the local filesystem, under a configurable data directory
(see section 15).
-* **Rendering:** FFmpeg, invoked as a local child process (see section 14).
+* **Rendering:** FFmpeg, invoked as a local child process from the Bun
+ backend (see section 14).
* **AI:** an LLM provider API key (OpenAI by default) for transcription and
editing calls only — never for storage. Keep the provider behind a
service interface (section 29) so it can be swapped.
@@ -606,7 +619,7 @@ Use:
```text
Frontend
↓
-Next.js API route
+Hono API route (Bun)
↓
Create RenderJob row (SQLite, status: "queued")
↓
@@ -801,7 +814,6 @@ POST /api/projects/:id/transcribe
GET /api/projects/:id/transcript
-POST /api/projects/:id/ai/edit
POST /api/projects/:id/operations
DELETE /api/projects/:id/operations/:operationId
@@ -809,6 +821,14 @@ POST /api/projects/:id/render
GET /api/projects/:id/render/:jobId
```
+(No `POST /api/projects/:id/ai/edit` — the free-text AI command bar this
+would have backed was removed; see section 3's status note.)
+
+`POST /api/projects/:id/upload` takes the raw video body (`Content-Type:
+video/*`, filename via a `?filename=` query param), not
+`multipart/form-data` — this lets the server stream the body straight to
+disk (`Bun.write`) without buffering the whole upload in memory.
+
Use typed request/response schemas.
Prefer Zod.
@@ -1043,7 +1063,6 @@ Do NOT initially build:
* complex animation system
* multi-user real-time editing
* mobile application
-* desktop application
* GPU rendering
* full Descript feature parity
@@ -1143,51 +1162,52 @@ A working transcript-based editor is more valuable than an unfinished full-featu
---
-# 31. Suggested Repository Structure
+# 31. Repository Structure
+
+Two packages in one pnpm workspace — `client/` (Vite + React, no server
+rendering) and `server/` (Bun + Hono, owns the database/filesystem/
+FFmpeg/AI calls):
```text
-prisma/
-└── schema.prisma
-
-src/
-├── app/
-│ ├── dashboard/
-│ ├── editor/
-│ └── api/
-│
-├── components/
-│ ├── editor/
-│ ├── timeline/
-│ ├── transcript/
-│ ├── video-player/
-│ └── ai/
-│
-├── lib/
-│ ├── db/ (Prisma client)
-│ ├── ai/
-│ ├── langgraph/
-│ ├── ffmpeg/
-│ ├── storage/ (local filesystem helpers)
-│ └── validation/
-│
-├── agents/
-│ ├── director/
-│ ├── editor/
-│ └── validator/
-│
-├── types/
-│ ├── project.ts
-│ ├── transcript.ts
-│ ├── timeline.ts
-│ └── edit-operation.ts
-│
-└── stores/
- ├── project-store.ts
- ├── timeline-store.ts
- ├── transcript-store.ts
- └── editor-store.ts
+client/
+├── index.html
+├── vite.config.mts
+└── src/
+ ├── main.tsx, App.tsx (entry, React Router root)
+ ├── routes/ (DashboardRoute, EditorRoute, NewProjectRoute, ...)
+ ├── components/
+ │ ├── editor/
+ │ ├── timeline/
+ │ ├── transcript/
+ │ └── video-player/
+ ├── lib/ (dual-use pure logic also needed server-side:
+ │ video/, timeline/cuts.ts + sentences.ts,
+ │ captions/style.ts + generate.ts — duplicated,
+ │ not imported, across the client/server boundary)
+ ├── types/
+ └── stores/ (Zustand: project, timeline, transcript, player, caption-style)
+
+server/
+├── prisma/
+│ └── schema.prisma
+└── src/
+ ├── index.ts (Hono app entrypoint)
+ ├── routes/ (one file per resource, mounted under /api/projects)
+ └── lib/
+ ├── db/ (Prisma client, @prisma/adapter-libsql)
+ ├── ai/ (Whisper transcription, filler-word detection)
+ ├── ffmpeg/ (render-job orchestrator, argv builder, execFile wrapper)
+ ├── storage/ (local filesystem helpers)
+ ├── validation/ (Zod request schemas)
+ └── captions/, video/, timeline/ (server's copy of the dual-use logic above)
```
+No `agents/`/`lib/langgraph/` directory exists in either package — the
+LangGraph Director/Editor/Validator pipeline (sections 5–8) was built,
+verified, then deliberately removed per section 3's status note. It's not
+part of the current app; treat sections 5–8 as a historical design record,
+not a structure to scaffold.
+
---
# 32. Definition of Done
@@ -1277,3 +1297,116 @@ stranger will read, because eventually one will.
None of this is optional polish. For an OSS project, the commit history
is part of what contributors read to understand the codebase — treat it
with the same care as the code itself.
+
+---
+
+# 35. Mac App Packaging (Tauri)
+
+A native macOS `.app` build exists at `client/src-tauri/`, wrapping the
+Vite client + Bun backend with Tauri v2, built and released via CI
+(`.github/workflows/build-macos-app.yml`) and downloadable from the
+docs site. **Scope: ad-hoc signed (no paid Apple Developer account),
+Apple Silicon (arm64) only, no notarization** — a deliberate choice,
+not an oversight: downloaders hit Gatekeeper's "unidentified
+developer" warning and need to right-click → Open once (documented on
+the download page). Real Developer ID signing + notarization + a
+universal (Intel) build are legitimate future steps, not something to
+add speculatively.
+
+## Architecture
+
+Rather than a compiled standalone sidecar binary (Tauri's usual
+pattern), the app spawns the **system-installed `bun`** to run a staged
+copy of the backend, for one specific reason: `bun build --compile`
+(the mechanism for a self-contained sidecar binary) has a known,
+unresolved upstream bug with `@libsql`, this project's SQLite driver —
+confirmed via `oven-sh/bun` issue #18909. Compiling would need a much
+bigger rewrite (dropping `@prisma/adapter-libsql` entirely); spawning
+the already-installed runtime instead sidesteps the bug completely,
+at the cost of requiring Bun to already be on the machine running the
+app — a non-issue for this personal-use-only scope.
+
+* `scripts/prepare-server-bundle.sh` stages a self-contained copy of
+ the backend at repo-root `server-bundle/` (gitignored): server
+ source (unbundled — Bun runs `.ts` directly), a **hoisted** (flat,
+ npm-style, zero symlinks) `node_modules`, the generated Prisma
+ client, the built Vite client, and a pre-migrated empty
+ `app.db.template`. The node-linker choice matters: pnpm's default
+ nested/symlinked `node_modules` (even via `pnpm deploy`, which does
+ correctly resolve the full dependency graph) breaks once Tauri's own
+ resource-bundler copies it into the `.app` — confirmed empirically
+ that Tauri's copy step silently drops directories only reachable via
+ a symlink. `pnpm install --config.node-linker=hoisted` avoids the
+ problem at the source instead of working around Tauri's copier.
+ **`server/.env` is deliberately never copied into the bundle** — see
+ the OpenAI API key note below.
+* `client/src-tauri/tauri.conf.json`'s `build.frontendDist` is a URL
+ (`http://localhost:3001`), not a static path — the packaged app's
+ window loads directly from the spawned Bun server, which serves both
+ the built static client and the `/api/*` routes on one origin,
+ exactly like the dev proxy does. `beforeBuildCommand` builds the
+ client and runs the staging script (`cd .. && pnpm --filter client
+ build && bash scripts/prepare-server-bundle.sh`) — Tauri hook
+ commands run from the frontend package directory (`client/`), so
+ `cd ..` alone reaches the repo root; an earlier version of this
+ config had an absolute machine-specific path here instead, which
+ worked locally but would have failed on any CI runner.
+* `client/src-tauri/src/lib.rs`'s `setup()` hook resolves
+ `resource_dir()`/`app_data_dir()`, seeds the database from
+ `app.db.template` on first launch only (never overwrites existing
+ data), spawns `bun run src/index.ts` from the staged bundle via
+ `tauri-plugin-shell` with `DATABASE_URL`/`DATA_DIR`/`PORT`/
+ `FFMPEG_PATH` pointed at the right places, creates the main window
+ hidden and reveals it once the server's own "server listening" log
+ line appears (avoids a startup race against the window trying to
+ load a server that isn't up yet), and kills the spawned process on
+ `RunEvent::Exit` (not `ExitRequested` — a normal macOS quit skips
+ straight to `Exit`).
+
+## Building it
+
+```bash
+cd client
+pnpm exec tauri build
+```
+
+Produces both `client/src-tauri/target/release/bundle/macos/AI Video
+Editor.app` and a `.dmg` alongside it (`bundle.targets` includes both).
+Since it's ad-hoc signed rather than signed with a real Developer ID,
+launch it with right-click → Open the first time (a plain double-click
+triggers Gatekeeper's "unidentified developer" block) — this applies
+whether you built it yourself or downloaded a release.
+
+CI (`.github/workflows/build-macos-app.yml`) runs this same command on
+`macos-latest` for every `v*.*.*` tag push, uploads the result as a
+workflow artifact on every run (including manual `workflow_dispatch`,
+for testing without touching anything public), and additionally
+attaches it to the matching GitHub Release when the run was a real tag
+push. No Apple secrets needed — ad-hoc signing requires no certificate
+or notarization credentials.
+
+## The OpenAI API key
+
+Not baked into the build. Each person who runs the app (whether built
+locally or downloaded) is asked for their own key on first launch —
+see `server/src/lib/settings.ts` (`DATA_DIR/settings.json`, checked
+before falling back to `server/.env`'s `OPENAI_API_KEY` for local dev)
+and `client/src/routes/SetupRoute.tsx`. An earlier version of this
+build did copy `server/.env` — including a real key — into every
+bundle; that was fine while builds never left the developer's own
+machine, but had to change once the app became publicly downloadable,
+since anyone could otherwise have extracted the key from the bundle.
+
+## Known constraints
+
+* App data lives at `~/Library/Application Support/com.local.aivideoeditor/`,
+ separate from whatever's in `server/data/` during local dev — the
+ packaged app starts with an empty project list on first launch, not
+ a copy of your dev data.
+* `FFMPEG_PATH` and the `bun` executable path are hardcoded in
+ `lib.rs` to the standard Homebrew/Bun install locations on any
+ Apple Silicon Mac (`/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg`,
+ `~/.bun/bin/bun`) — not this-machine-specific, but they are real
+ prerequisites: anyone running the app (built locally or downloaded)
+ needs Bun and Homebrew's `ffmpeg-full` already installed, documented
+ on the docs site's download page.
diff --git a/client/.gitignore b/client/.gitignore
index 17ec04a..db25032 100644
--- a/client/.gitignore
+++ b/client/.gitignore
@@ -2,23 +2,12 @@
# dependencies
/node_modules
-/.pnp
-.pnp.*
-.yarn/*
-!.yarn/patches
-!.yarn/plugins
-!.yarn/releases
-!.yarn/versions
# testing
/coverage
-# next.js
-/.next/
-/out/
-
-# production
-/build
+# vite build output
+/dist
# misc
.DS_Store
@@ -34,14 +23,8 @@ yarn-error.log*
.env*
!.env.example
-# vercel
-.vercel
-
# typescript
*.tsbuildinfo
-next-env.d.ts
-
-/src/generated/prisma
# local project data (uploads, sqlite db, renders) — never committed
/data
diff --git a/client/AGENTS.md b/client/AGENTS.md
deleted file mode 100644
index 643577d..0000000
--- a/client/AGENTS.md
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-# This is NOT the Next.js you know
-
-This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
-
-This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
-
-
diff --git a/client/CLAUDE.md b/client/CLAUDE.md
deleted file mode 100644
index 43c994c..0000000
--- a/client/CLAUDE.md
+++ /dev/null
@@ -1 +0,0 @@
-@AGENTS.md
diff --git a/client/eslint.config.mjs b/client/eslint.config.mjs
index 05e726d..ff346fa 100644
--- a/client/eslint.config.mjs
+++ b/client/eslint.config.mjs
@@ -1,18 +1,35 @@
import { defineConfig, globalIgnores } from "eslint/config";
-import nextVitals from "eslint-config-next/core-web-vitals";
-import nextTs from "eslint-config-next/typescript";
+import js from "@eslint/js";
+import tseslint from "typescript-eslint";
+import reactHooks from "eslint-plugin-react-hooks";
+import reactRefresh from "eslint-plugin-react-refresh";
+import globals from "globals";
-const eslintConfig = defineConfig([
- ...nextVitals,
- ...nextTs,
- // Override default ignores of eslint-config-next.
- globalIgnores([
- // Default ignores of eslint-config-next:
- ".next/**",
- "out/**",
- "build/**",
- "next-env.d.ts",
- ]),
+export default defineConfig([
+ globalIgnores([".next/**", "dist/**", "src/generated/**", "src-tauri/**"]),
+ js.configs.recommended,
+ ...tseslint.configs.recommended,
+ reactRefresh.configs.vite,
+ {
+ plugins: { "react-hooks": reactHooks },
+ // Match the previous eslint-config-next setup's react-hooks coverage
+ // (rules-of-hooks + exhaustive-deps) rather than pulling in
+ // eslint-plugin-react-hooks v7's much larger React Compiler-oriented
+ // rule set -- this migration ports the framework, not a full relint.
+ rules: {
+ "react-hooks/rules-of-hooks": "error",
+ "react-hooks/exhaustive-deps": "warn",
+ },
+ languageOptions: {
+ globals: globals.browser,
+ },
+ },
+ {
+ // shadcn's generated primitives legitimately co-export a component and
+ // its variant helper (e.g. buttonVariants) from the same file.
+ files: ["src/components/ui/**"],
+ rules: {
+ "react-refresh/only-export-components": "off",
+ },
+ },
]);
-
-export default eslintConfig;
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..e88da12
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ AI Video Editor
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/next.config.ts b/client/next.config.ts
deleted file mode 100644
index e9ffa30..0000000
--- a/client/next.config.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import type { NextConfig } from "next";
-
-const nextConfig: NextConfig = {
- /* config options here */
-};
-
-export default nextConfig;
diff --git a/client/package.json b/client/package.json
index ed84924..842aebc 100644
--- a/client/package.json
+++ b/client/package.json
@@ -4,52 +4,42 @@
"private": true,
"license": "MIT",
"scripts": {
- "dev": "next dev",
- "build": "next build",
- "start": "next start",
+ "dev": "vite",
+ "build": "tsc --noEmit && vite build",
+ "preview": "vite preview",
"lint": "eslint",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
- "@prisma/adapter-better-sqlite3": "^7.10.0",
- "@prisma/client": "^7.10.0",
- "better-sqlite3": "^13.0.3",
"class-variance-authority": "^0.7.1",
"cn": "^0.3.0",
"lucide-react": "^1.46.0",
- "next": "16.3.5",
- "openai": "^6.49.0",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
+ "react-router-dom": "^7.18.4",
"shadcn": "^4.21.0",
"tw-animate-css": "^1.4.0",
"vaul": "^1.1.2",
- "zod": "^4.6.5",
"zustand": "^5.0.15"
},
"devDependencies": {
+ "@eslint/js": "^9.39.5",
"@tailwindcss/postcss": "^4",
- "@types/better-sqlite3": "^9.6.0",
- "@types/node": "^20",
+ "@tauri-apps/cli": "^2.11.5",
+ "@types/node": "^26.6.2",
"@types/react": "^19",
"@types/react-dom": "^19",
- "dotenv": "^17.4.2",
+ "@vitejs/plugin-react": "^6.1.1",
"eslint": "^9",
- "eslint-config-next": "16.3.5",
- "prisma": "^7.10.0",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "eslint-plugin-react-refresh": "^0.5.7",
+ "globals": "^17.12.0",
"tailwindcss": "^4",
"typescript": "^5",
+ "typescript-eslint": "^8.70.1",
+ "vite": "^8.3.0",
"vitest": "^5.0.1"
- },
- "pnpm": {
- "onlyBuiltDependencies": [
- "@prisma/engines",
- "better-sqlite3",
- "prisma",
- "protobufjs",
- "unrs-resolver"
- ]
}
}
diff --git a/client/src/app/favicon.ico b/client/public/favicon.ico
similarity index 100%
rename from client/src/app/favicon.ico
rename to client/public/favicon.ico
diff --git a/client/public/file.svg b/client/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/client/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/public/globe.svg b/client/public/globe.svg
deleted file mode 100644
index 567f17b..0000000
--- a/client/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/public/next.svg b/client/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/client/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/public/vercel.svg b/client/public/vercel.svg
deleted file mode 100644
index 7705396..0000000
--- a/client/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/public/window.svg b/client/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/client/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/client/src-tauri/.gitignore b/client/src-tauri/.gitignore
new file mode 100644
index 0000000..502406b
--- /dev/null
+++ b/client/src-tauri/.gitignore
@@ -0,0 +1,4 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+/gen/schemas
diff --git a/client/src-tauri/Cargo.lock b/client/src-tauri/Cargo.lock
new file mode 100644
index 0000000..cf86f51
--- /dev/null
+++ b/client/src-tauri/Cargo.lock
@@ -0,0 +1,4752 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "android_log-sys"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d"
+
+[[package]]
+name = "android_logger"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3"
+dependencies = [
+ "android_log-sys",
+ "env_filter",
+ "log",
+]
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "app"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "serde",
+ "serde_json",
+ "tauri",
+ "tauri-build",
+ "tauri-plugin-log",
+ "tauri-plugin-shell",
+]
+
+[[package]]
+name = "atk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b"
+dependencies = [
+ "atk-sys",
+ "glib",
+ "libc",
+]
+
+[[package]]
+name = "atk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "atomic-waker"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "base64"
+version = "0.21.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2",
+]
+
+[[package]]
+name = "brotli"
+version = "8.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bs58"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4"
+dependencies = [
+ "tinyvec",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cairo-rs"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2"
+dependencies = [
+ "bitflags 2.13.2",
+ "cairo-sys-rs",
+ "glib",
+ "libc",
+ "once_cell",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "cairo-sys-rs"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "camino"
+version = "1.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "cargo-platform"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "cargo_metadata"
+version = "0.19.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba"
+dependencies = [
+ "camino",
+ "cargo-platform",
+ "semver",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.20",
+]
+
+[[package]]
+name = "cargo_toml"
+version = "0.22.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77"
+dependencies = [
+ "serde",
+ "toml 0.9.12+spec-1.1.0",
+]
+
+[[package]]
+name = "cc"
+version = "1.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cesu8"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
+
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
+[[package]]
+name = "cfg-expr"
+version = "0.15.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
+dependencies = [
+ "smallvec",
+ "target-lexicon",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "num-traits",
+ "serde",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "combine"
+version = "4.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e"
+dependencies = [
+ "bytes",
+ "memchr",
+]
+
+[[package]]
+name = "cookie"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87"
+dependencies = [
+ "time",
+ "version_check",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "core-graphics"
+version = "0.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97"
+dependencies = [
+ "bitflags 2.13.2",
+ "core-foundation",
+ "core-graphics-types",
+ "foreign-types",
+ "libc",
+]
+
+[[package]]
+name = "core-graphics-types"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb"
+dependencies = [
+ "bitflags 2.13.2",
+ "core-foundation",
+ "libc",
+]
+
+[[package]]
+name = "core_detect"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
+[[package]]
+name = "crypto-common"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "cssparser"
+version = "0.36.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2"
+dependencies = [
+ "cssparser-macros",
+ "dtoa-short",
+ "itoa",
+ "phf",
+ "smallvec",
+]
+
+[[package]]
+name = "cssparser-macros"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "ctor"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
+dependencies = [
+ "ctor-proc-macro",
+ "dtor",
+]
+
+[[package]]
+name = "ctor-proc-macro"
+version = "0.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1"
+
+[[package]]
+name = "darling"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
+dependencies = [
+ "darling_core",
+ "darling_macro",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.24.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
+dependencies = [
+ "darling_core",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "dbus"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e"
+dependencies = [
+ "libc",
+ "libdbus-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "defmt"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
+dependencies = [
+ "bitflags 1.3.2",
+ "defmt-macros",
+]
+
+[[package]]
+name = "defmt-macros"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
+dependencies = [
+ "defmt-parser",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "defmt-parser"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
+dependencies = [
+ "thiserror 2.0.20",
+]
+
+[[package]]
+name = "deranged"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "crypto-common",
+]
+
+[[package]]
+name = "dirs"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "libc",
+ "objc2",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "dlopen2"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4"
+dependencies = [
+ "dlopen2_derive",
+ "libc",
+ "once_cell",
+ "winapi",
+]
+
+[[package]]
+name = "dlopen2_derive"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "dom_query"
+version = "0.27.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89"
+dependencies = [
+ "bit-set",
+ "cssparser",
+ "foldhash",
+ "html5ever",
+ "precomputed-hash",
+ "selectors",
+ "tendril",
+]
+
+[[package]]
+name = "dpi"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "dtoa"
+version = "1.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
+
+[[package]]
+name = "dtoa-short"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
+dependencies = [
+ "dtoa",
+]
+
+[[package]]
+name = "dtor"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4"
+dependencies = [
+ "dtor-proc-macro",
+]
+
+[[package]]
+name = "dtor-proc-macro"
+version = "0.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5"
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "dyn-clone"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
+
+[[package]]
+name = "embed-resource"
+version = "3.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd"
+dependencies = [
+ "cc",
+ "memchr",
+ "rustc_version",
+ "toml 1.1.6+spec-1.1.0",
+ "vswhom",
+ "winreg",
+]
+
+[[package]]
+name = "embed_plist"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7"
+
+[[package]]
+name = "encoding_rs"
+version = "0.8.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5ef0006ac9ab233c38522f5ae99cae3625151de8f706cacee1cba4b8e2832a"
+dependencies = [
+ "cfg-if",
+ "core_detect",
+ "multiversion",
+ "multiversion_no_op",
+ "rustversion",
+ "scopeguard",
+ "simdutf8",
+]
+
+[[package]]
+name = "env_filter"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "fdeflate"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
+dependencies = [
+ "simd-adler32",
+]
+
+[[package]]
+name = "fern"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4316185f709b23713e41e3195f90edef7fb00c3ed4adc79769cf09cc762a3b29"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "field-offset"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f"
+dependencies = [
+ "memoffset",
+ "rustc_version",
+]
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
+
+[[package]]
+name = "flate2"
+version = "1.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide 0.9.1",
+ "zlib-rs",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d"
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "gdk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691"
+dependencies = [
+ "cairo-rs",
+ "gdk-pixbuf",
+ "gdk-sys",
+ "gio",
+ "glib",
+ "libc",
+ "pango",
+]
+
+[[package]]
+name = "gdk-pixbuf"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec"
+dependencies = [
+ "gdk-pixbuf-sys",
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+]
+
+[[package]]
+name = "gdk-pixbuf-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gdk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7"
+dependencies = [
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkwayland-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pkg-config",
+ "system-deps",
+]
+
+[[package]]
+name = "gdkx11"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe"
+dependencies = [
+ "gdk",
+ "gdkx11-sys",
+ "gio",
+ "glib",
+ "libc",
+ "x11",
+]
+
+[[package]]
+name = "gdkx11-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d"
+dependencies = [
+ "gdk-sys",
+ "glib-sys",
+ "libc",
+ "system-deps",
+ "x11",
+]
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 5.3.0",
+ "wasip2",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+]
+
+[[package]]
+name = "gio"
+version = "0.18.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-util",
+ "gio-sys",
+ "glib",
+ "libc",
+ "once_cell",
+ "pin-project-lite",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "gio-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+ "winapi",
+]
+
+[[package]]
+name = "glib"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5"
+dependencies = [
+ "bitflags 2.13.2",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-task",
+ "futures-util",
+ "gio-sys",
+ "glib-macros",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "memchr",
+ "once_cell",
+ "smallvec",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "glib-macros"
+version = "0.18.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
+dependencies = [
+ "heck 0.4.1",
+ "proc-macro-crate 2.0.2",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "glib-sys"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898"
+dependencies = [
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"
+
+[[package]]
+name = "gobject-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44"
+dependencies = [
+ "glib-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a"
+dependencies = [
+ "atk",
+ "cairo-rs",
+ "field-offset",
+ "futures-channel",
+ "gdk",
+ "gdk-pixbuf",
+ "gio",
+ "glib",
+ "gtk-sys",
+ "gtk3-macros",
+ "libc",
+ "pango",
+ "pkg-config",
+]
+
+[[package]]
+name = "gtk-sys"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414"
+dependencies = [
+ "atk-sys",
+ "cairo-sys-rs",
+ "gdk-pixbuf-sys",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "pango-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "gtk3-macros"
+version = "0.18.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d"
+dependencies = [
+ "proc-macro-crate 1.3.1",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "html5ever"
+version = "0.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2"
+dependencies = [
+ "log",
+ "markup5ever",
+]
+
+[[package]]
+name = "http"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
+dependencies = [
+ "bytes",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
+
+[[package]]
+name = "hyper"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43"
+dependencies = [
+ "atomic-waker",
+ "bytes",
+ "futures-channel",
+ "futures-core",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
+dependencies = [
+ "base64 0.22.1",
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "ipnet",
+ "libc",
+ "percent-encoding",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core 0.62.2",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "ico"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371"
+dependencies = [
+ "byteorder",
+ "png 0.17.16",
+]
+
+[[package]]
+name = "icu_collections"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513"
+dependencies = [
+ "displaydoc",
+ "potential_utf",
+ "utf8_iter",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locale_core"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f"
+dependencies = [
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0"
+
+[[package]]
+name = "icu_properties"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locale_core",
+ "icu_properties_data",
+ "icu_provider",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa"
+
+[[package]]
+name = "icu_provider"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73"
+dependencies = [
+ "displaydoc",
+ "icu_locale_core",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerotrie",
+ "zerovec",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
+[[package]]
+name = "idna"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
+dependencies = [
+ "autocfg",
+ "hashbrown 0.12.3",
+ "serde",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.17.1",
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "infer"
+version = "0.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0"
+
+[[package]]
+name = "is-docker"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "is-wsl"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5"
+dependencies = [
+ "is-docker",
+ "once_cell",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
+
+[[package]]
+name = "javascriptcore-rs"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc"
+dependencies = [
+ "bitflags 1.3.2",
+ "glib",
+ "javascriptcore-rs-sys",
+]
+
+[[package]]
+name = "javascriptcore-rs-sys"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "jiff"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb"
+dependencies = [
+ "defmt",
+ "jiff-core",
+ "jiff-static",
+ "jiff-tzdb-platform",
+ "log",
+ "portable-atomic",
+ "portable-atomic-util",
+ "serde_core",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "jiff-core"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c"
+dependencies = [
+ "defmt",
+ "log",
+]
+
+[[package]]
+name = "jiff-static"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212"
+dependencies = [
+ "jiff-core",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "jiff-tzdb"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e"
+
+[[package]]
+name = "jiff-tzdb-platform"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8"
+dependencies = [
+ "jiff-tzdb",
+]
+
+[[package]]
+name = "jni"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
+dependencies = [
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
+dependencies = [
+ "jni-sys 0.4.1",
+]
+
+[[package]]
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json-patch"
+version = "3.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08"
+dependencies = [
+ "jsonptr",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "jsonptr"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70"
+dependencies = [
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "keyboard-types"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a"
+dependencies = [
+ "bitflags 2.13.2",
+ "serde",
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "libappindicator"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a"
+dependencies = [
+ "glib",
+ "gtk",
+ "gtk-sys",
+ "libappindicator-sys",
+ "log",
+]
+
+[[package]]
+name = "libappindicator-sys"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf"
+dependencies = [
+ "gtk-sys",
+ "libloading",
+ "once_cell",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libdbus-sys"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043"
+dependencies = [
+ "pkg-config",
+]
+
+[[package]]
+name = "libloading"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
+dependencies = [
+ "cfg-if",
+ "winapi",
+]
+
+[[package]]
+name = "libredox"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "litemap"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "markup5ever"
+version = "0.38.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862"
+dependencies = [
+ "log",
+ "tendril",
+ "web_atoms",
+]
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "memoffset"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c"
+dependencies = [
+ "adler2",
+ "simd-adler32",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "muda"
+version = "0.19.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878"
+dependencies = [
+ "crossbeam-channel",
+ "dpi",
+ "gtk",
+ "keyboard-types",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "once_cell",
+ "png 0.18.1",
+ "serde",
+ "thiserror 2.0.20",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "multiversion"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4ca4bea16ffc3f443cf7d866912118196bfef4c6a1556ca00f9f9b00bb43f7c"
+dependencies = [
+ "multiversion-macros",
+]
+
+[[package]]
+name = "multiversion-macros"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d416831a7317ef4b08bee00b69cbbb9c8763da7959a7026244d6266869f9c83"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "multiversion_no_op"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d"
+
+[[package]]
+name = "ndk"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4"
+dependencies = [
+ "bitflags 2.13.2",
+ "jni-sys 0.3.1",
+ "log",
+ "ndk-sys",
+ "num_enum",
+ "raw-window-handle",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "ndk-sys"
+version = "0.6.0+11769913"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873"
+dependencies = [
+ "jni-sys 0.3.1",
+]
+
+[[package]]
+name = "new_debug_unreachable"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+
+[[package]]
+name = "num-conv"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26"
+dependencies = [
+ "num_enum_derive",
+ "rustversion",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8"
+dependencies = [
+ "proc-macro-crate 3.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "objc2"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
+dependencies = [
+ "objc2-encode",
+ "objc2-exception-helper",
+]
+
+[[package]]
+name = "objc2-app-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-cloud-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-data"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.13.2",
+ "dispatch2",
+ "objc2",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.13.2",
+ "dispatch2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-text"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-exception-helper"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.13.2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "objc2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-core-text",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-web-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "open"
+version = "5.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa576c76302b7b808eecc68061e67336c47833ef9d22caa74dda10fa9675eebc"
+dependencies = [
+ "dunce",
+ "is-wsl",
+ "libc",
+]
+
+[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
+name = "os_pipe"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "pango"
+version = "0.18.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4"
+dependencies = [
+ "gio",
+ "glib",
+ "libc",
+ "once_cell",
+ "pango-sys",
+]
+
+[[package]]
+name = "pango-sys"
+version = "0.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5"
+dependencies = [
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
+
+[[package]]
+name = "phf"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
+dependencies = [
+ "phf_macros",
+ "phf_shared",
+ "serde",
+]
+
+[[package]]
+name = "phf_codegen"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_generator"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
+dependencies = [
+ "fastrand",
+ "phf_shared",
+]
+
+[[package]]
+name = "phf_macros"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "phf_shared"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
+dependencies = [
+ "siphasher",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
+
+[[package]]
+name = "plist"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb"
+dependencies = [
+ "base64 0.23.1",
+ "indexmap 2.14.2",
+ "quick-xml",
+ "serde",
+ "time",
+]
+
+[[package]]
+name = "png"
+version = "0.17.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
+dependencies = [
+ "bitflags 1.3.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide 0.8.9",
+]
+
+[[package]]
+name = "png"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
+dependencies = [
+ "bitflags 2.13.2",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide 0.8.9",
+]
+
+[[package]]
+name = "portable-atomic"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
+
+[[package]]
+name = "portable-atomic-util"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715"
+dependencies = [
+ "portable-atomic",
+]
+
+[[package]]
+name = "potential_utf"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661"
+dependencies = [
+ "zerovec",
+]
+
+[[package]]
+name = "powerfmt"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
+
+[[package]]
+name = "precomputed-hash"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
+dependencies = [
+ "once_cell",
+ "toml_edit 0.19.15",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24"
+dependencies = [
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
+dependencies = [
+ "toml_edit 0.25.15+spec-1.1.0",
+]
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quick-xml"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "5.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "raw-window-handle"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags 2.13.2",
+]
+
+[[package]]
+name = "redox_users"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0"
+dependencies = [
+ "libredox",
+ "thiserror 2.0.20",
+]
+
+[[package]]
+name = "ref-cast"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3"
+dependencies = [
+ "ref-cast-impl",
+]
+
+[[package]]
+name = "ref-cast-impl"
+version = "1.0.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "regex"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
+
+[[package]]
+name = "reqwest"
+version = "0.13.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029"
+dependencies = [
+ "base64 0.23.1",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite",
+ "serde",
+ "serde_json",
+ "sync_wrapper",
+ "tokio",
+ "tokio-util",
+ "tower",
+ "tower-http",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-streams",
+ "web-sys",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "schemars"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
+dependencies = [
+ "dyn-clone",
+ "indexmap 1.9.3",
+ "schemars_derive",
+ "serde",
+ "serde_json",
+ "url",
+ "uuid",
+]
+
+[[package]]
+name = "schemars"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a"
+dependencies = [
+ "dyn-clone",
+ "ref-cast",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "schemars_derive"
+version = "0.8.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "serde_derive_internals",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "selectors"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c"
+dependencies = [
+ "bitflags 2.13.2",
+ "cssparser",
+ "derive_more",
+ "log",
+ "new_debug_unreachable",
+ "phf",
+ "phf_codegen",
+ "precomputed-hash",
+ "rustc-hash",
+ "servo_arc",
+ "smallvec",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+dependencies = [
+ "serde",
+ "serde_core",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-untagged"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058"
+dependencies = [
+ "erased-serde",
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "serde_derive_internals"
+version = "0.29.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.151"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
+dependencies = [
+ "itoa",
+ "memchr",
+ "serde",
+ "serde_core",
+ "zmij",
+]
+
+[[package]]
+name = "serde_repr"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "serde_with"
+version = "3.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4"
+dependencies = [
+ "base64 0.23.1",
+ "bs58",
+ "chrono",
+ "hex",
+ "indexmap 1.9.3",
+ "indexmap 2.14.2",
+ "jiff",
+ "schemars 0.9.0",
+ "schemars 1.2.2",
+ "serde_core",
+ "serde_json",
+ "serde_with_macros",
+ "time",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "3.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "serialize-to-javascript"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5"
+dependencies = [
+ "serde",
+ "serde_json",
+ "serialize-to-javascript-impl",
+]
+
+[[package]]
+name = "serialize-to-javascript-impl"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "servo_arc"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest",
+]
+
+[[package]]
+name = "shared_child"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "607549934f6cc26b89cfecfdc46fa90f1e5d1536a68349b0c3a4f9d1c0d37959"
+dependencies = [
+ "libc",
+ "sigchld",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "sigchld"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24f2b37f04360cd465089b87a9c3869c08220a2f3458463f0adf8badf5e77f2c"
+dependencies = [
+ "libc",
+ "os_pipe",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2a0c28ca5908dbdbcd52e6fdaa00358ab88637f8ab33e1f188dd510eb44b53d"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "simd-adler32"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
+
+[[package]]
+name = "simdutf8"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
+
+[[package]]
+name = "siphasher"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
+
+[[package]]
+name = "socket2"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
+dependencies = [
+ "libc",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "softbuffer"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3"
+dependencies = [
+ "bytemuck",
+ "js-sys",
+ "ndk",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "raw-window-handle",
+ "redox_syscall",
+ "tracing",
+ "wasm-bindgen",
+ "web-sys",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "soup3"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f"
+dependencies = [
+ "futures-channel",
+ "gio",
+ "glib",
+ "libc",
+ "soup3-sys",
+]
+
+[[package]]
+name = "soup3-sys"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27"
+dependencies = [
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "libc",
+ "system-deps",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "string_cache"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901"
+dependencies = [
+ "new_debug_unreachable",
+ "parking_lot",
+ "phf_shared",
+ "precomputed-hash",
+]
+
+[[package]]
+name = "string_cache_codegen"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69"
+dependencies = [
+ "phf_generator",
+ "phf_shared",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "strsim"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
+
+[[package]]
+name = "swift-rs"
+version = "1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e"
+dependencies = [
+ "base64 0.21.7",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "system-deps"
+version = "6.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
+dependencies = [
+ "cfg-expr",
+ "heck 0.5.0",
+ "pkg-config",
+ "toml 0.8.2",
+ "version-compare",
+]
+
+[[package]]
+name = "tao"
+version = "0.35.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9"
+dependencies = [
+ "bitflags 2.13.2",
+ "block2",
+ "core-foundation",
+ "core-graphics",
+ "crossbeam-channel",
+ "dbus",
+ "dispatch2",
+ "dlopen2",
+ "dpi",
+ "gdkwayland-sys",
+ "gdkx11-sys",
+ "gtk",
+ "jni",
+ "libc",
+ "log",
+ "ndk",
+ "ndk-sys",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "once_cell",
+ "parking_lot",
+ "percent-encoding",
+ "raw-window-handle",
+ "tao-macros",
+ "unicode-segmentation",
+ "url",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "tao-macros"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "target-lexicon"
+version = "0.12.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+
+[[package]]
+name = "tauri"
+version = "2.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa5bacdb9bbad5954af3d1bd6cf6ae9192cab1b2e270f4a07f904610b9e85f4"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "cookie",
+ "dirs",
+ "dunce",
+ "embed_plist",
+ "getrandom 0.3.4",
+ "glob",
+ "gtk",
+ "heck 0.5.0",
+ "http",
+ "jni",
+ "libc",
+ "log",
+ "mime",
+ "muda",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "percent-encoding",
+ "plist",
+ "raw-window-handle",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "serialize-to-javascript",
+ "swift-rs",
+ "tauri-build",
+ "tauri-macros",
+ "tauri-runtime",
+ "tauri-runtime-wry",
+ "tauri-utils",
+ "thiserror 2.0.20",
+ "tokio",
+ "tray-icon",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "window-vibrancy",
+ "windows",
+]
+
+[[package]]
+name = "tauri-build"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632"
+dependencies = [
+ "anyhow",
+ "cargo_toml",
+ "dirs",
+ "glob",
+ "heck 0.5.0",
+ "json-patch",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "tauri-winres",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-codegen"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5"
+dependencies = [
+ "base64 0.22.1",
+ "brotli",
+ "ico",
+ "json-patch",
+ "plist",
+ "png 0.17.16",
+ "proc-macro2",
+ "quote",
+ "semver",
+ "serde",
+ "serde_json",
+ "sha2",
+ "syn 2.0.119",
+ "tauri-utils",
+ "thiserror 2.0.20",
+ "time",
+ "url",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-macros"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45"
+dependencies = [
+ "heck 0.5.0",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+ "tauri-codegen",
+ "tauri-utils",
+]
+
+[[package]]
+name = "tauri-plugin"
+version = "2.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74be5dd4bed9afbd145e5716b5fa2ec28cbc29c34ffa61c258c9273d896c8020"
+dependencies = [
+ "anyhow",
+ "glob",
+ "plist",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-plugin-log"
+version = "2.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86f2e3c40b77fd1eb7f8e1b6900fb036aee2da1ea2d3783c91255a82c636649d"
+dependencies = [
+ "android_logger",
+ "fern",
+ "log",
+ "objc2",
+ "objc2-foundation",
+ "serde",
+ "serde_json",
+ "serde_repr",
+ "swift-rs",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.20",
+ "time",
+]
+
+[[package]]
+name = "tauri-plugin-shell"
+version = "2.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8548af174c5516e4f71f142acea4d02e00316296ea9aafa58798851481003e3c"
+dependencies = [
+ "encoding_rs",
+ "log",
+ "open",
+ "os_pipe",
+ "regex",
+ "schemars 0.8.22",
+ "serde",
+ "serde_json",
+ "shared_child",
+ "tauri",
+ "tauri-plugin",
+ "thiserror 2.0.20",
+ "tokio",
+]
+
+[[package]]
+name = "tauri-runtime"
+version = "2.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8"
+dependencies = [
+ "cookie",
+ "dpi",
+ "gtk",
+ "http",
+ "jni",
+ "objc2",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "raw-window-handle",
+ "serde",
+ "serde_json",
+ "tauri-utils",
+ "thiserror 2.0.20",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows",
+]
+
+[[package]]
+name = "tauri-runtime-wry"
+version = "2.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f"
+dependencies = [
+ "gtk",
+ "http",
+ "jni",
+ "log",
+ "objc2",
+ "objc2-app-kit",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "softbuffer",
+ "tao",
+ "tauri-runtime",
+ "tauri-utils",
+ "url",
+ "webkit2gtk",
+ "webview2-com",
+ "windows",
+ "wry",
+]
+
+[[package]]
+name = "tauri-utils"
+version = "2.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887"
+dependencies = [
+ "anyhow",
+ "brotli",
+ "cargo_metadata",
+ "ctor",
+ "dom_query",
+ "dunce",
+ "glob",
+ "http",
+ "infer",
+ "json-patch",
+ "log",
+ "memchr",
+ "phf",
+ "plist",
+ "proc-macro2",
+ "quote",
+ "regex",
+ "schemars 0.8.22",
+ "semver",
+ "serde",
+ "serde-untagged",
+ "serde_json",
+ "serde_with",
+ "swift-rs",
+ "thiserror 2.0.20",
+ "toml 1.1.6+spec-1.1.0",
+ "url",
+ "urlpattern",
+ "uuid",
+ "walkdir",
+]
+
+[[package]]
+name = "tauri-winres"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6"
+dependencies = [
+ "dunce",
+ "embed-resource",
+ "toml 1.1.6+spec-1.1.0",
+]
+
+[[package]]
+name = "tendril"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08"
+dependencies = [
+ "new_debug_unreachable",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl 2.0.20",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "time"
+version = "0.3.55"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
+dependencies = [
+ "deranged",
+ "libc",
+ "num-conv",
+ "num_threads",
+ "powerfmt",
+ "serde_core",
+ "time-core",
+ "time-macros",
+]
+
+[[package]]
+name = "time-core"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
+
+[[package]]
+name = "time-macros"
+version = "0.2.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85"
+dependencies = [
+ "num-conv",
+ "time-core",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tinyvec"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
+
+[[package]]
+name = "tokio"
+version = "1.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
+dependencies = [
+ "bytes",
+ "libc",
+ "mio",
+ "pin-project-lite",
+ "socket2",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
+dependencies = [
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "toml_edit 0.20.2",
+]
+
+[[package]]
+name = "toml"
+version = "0.9.12+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863"
+dependencies = [
+ "indexmap 2.14.2",
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 0.7.5+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 0.7.15",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
+dependencies = [
+ "indexmap 2.14.2",
+ "serde_core",
+ "serde_spanned 1.1.1",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow 1.0.4",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.7.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
+dependencies = [
+ "indexmap 2.14.2",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
+dependencies = [
+ "indexmap 2.14.2",
+ "serde",
+ "serde_spanned 0.6.9",
+ "toml_datetime 0.6.3",
+ "winnow 0.5.40",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.25.15+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614"
+dependencies = [
+ "indexmap 2.14.2",
+ "toml_datetime 1.1.1+spec-1.1.0",
+ "toml_parser",
+ "winnow 1.0.4",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow 1.0.4",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
+[[package]]
+name = "tower"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-http"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
+dependencies = [
+ "bitflags 2.13.2",
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+ "tower",
+ "tower-layer",
+ "tower-service",
+ "url",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "tray-icon"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e"
+dependencies = [
+ "crossbeam-channel",
+ "dirs",
+ "libappindicator",
+ "muda",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-foundation",
+ "once_cell",
+ "png 0.18.1",
+ "serde",
+ "thiserror 2.0.20",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "typenum"
+version = "1.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
+
+[[package]]
+name = "unic-char-property"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221"
+dependencies = [
+ "unic-char-range",
+]
+
+[[package]]
+name = "unic-char-range"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc"
+
+[[package]]
+name = "unic-common"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
+
+[[package]]
+name = "unic-ucd-ident"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987"
+dependencies = [
+ "unic-char-property",
+ "unic-char-range",
+ "unic-ucd-version",
+]
+
+[[package]]
+name = "unic-ucd-version"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4"
+dependencies = [
+ "unic-common",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "url"
+version = "2.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+ "serde",
+ "serde_derive",
+]
+
+[[package]]
+name = "urlpattern"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d"
+dependencies = [
+ "regex",
+ "serde",
+ "unic-ucd-ident",
+ "url",
+]
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "uuid"
+version = "1.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce"
+dependencies = [
+ "getrandom 0.4.3",
+ "js-sys",
+ "serde_core",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "version-compare"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "vswhom"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
+dependencies = [
+ "libc",
+ "vswhom-sys",
+]
+
+[[package]]
+name = "vswhom-sys"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasip2"
+version = "1.0.4+wasi-0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.78"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasm-streams"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb"
+dependencies = [
+ "futures-util",
+ "js-sys",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web_atoms"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3"
+dependencies = [
+ "phf",
+ "phf_codegen",
+ "string_cache",
+ "string_cache_codegen",
+]
+
+[[package]]
+name = "webkit2gtk"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-rs",
+ "gdk",
+ "gdk-sys",
+ "gio",
+ "gio-sys",
+ "glib",
+ "glib-sys",
+ "gobject-sys",
+ "gtk",
+ "gtk-sys",
+ "javascriptcore-rs",
+ "libc",
+ "once_cell",
+ "soup3",
+ "webkit2gtk-sys",
+]
+
+[[package]]
+name = "webkit2gtk-sys"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5"
+dependencies = [
+ "bitflags 1.3.2",
+ "cairo-sys-rs",
+ "gdk-sys",
+ "gio-sys",
+ "glib-sys",
+ "gobject-sys",
+ "gtk-sys",
+ "javascriptcore-rs-sys",
+ "libc",
+ "pkg-config",
+ "soup3-sys",
+ "system-deps",
+]
+
+[[package]]
+name = "webview2-com"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a"
+dependencies = [
+ "webview2-com-macros",
+ "webview2-com-sys",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-implement",
+ "windows-interface",
+]
+
+[[package]]
+name = "webview2-com-macros"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "webview2-com-sys"
+version = "0.38.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c"
+dependencies = [
+ "thiserror 2.0.20",
+ "windows",
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
+dependencies = [
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "window-vibrancy"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c"
+dependencies = [
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "raw-window-handle",
+ "windows-sys 0.59.0",
+ "windows-version",
+]
+
+[[package]]
+name = "windows"
+version = "0.61.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
+dependencies = [
+ "windows-collections",
+ "windows-core 0.61.2",
+ "windows-future",
+ "windows-link 0.1.3",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
+dependencies = [
+ "windows-core 0.61.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.2.1",
+ "windows-result 0.4.1",
+ "windows-strings 0.5.1",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
+dependencies = [
+ "windows-core 0.61.2",
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm 0.52.6",
+ "windows_aarch64_msvc 0.52.6",
+ "windows_i686_gnu 0.52.6",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc 0.52.6",
+ "windows_x86_64_gnu 0.52.6",
+ "windows_x86_64_gnullvm 0.52.6",
+ "windows_x86_64_msvc 0.52.6",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
+dependencies = [
+ "windows-link 0.1.3",
+]
+
+[[package]]
+name = "windows-version"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631"
+dependencies = [
+ "windows-link 0.2.1",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.5.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "winreg"
+version = "0.55.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97"
+dependencies = [
+ "cfg-if",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "wit-bindgen"
+version = "0.57.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
+
+[[package]]
+name = "writeable"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc"
+
+[[package]]
+name = "wry"
+version = "0.55.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514"
+dependencies = [
+ "base64 0.22.1",
+ "block2",
+ "cookie",
+ "crossbeam-channel",
+ "dirs",
+ "dom_query",
+ "dpi",
+ "dunce",
+ "gdkx11",
+ "gtk",
+ "http",
+ "javascriptcore-rs",
+ "jni",
+ "libc",
+ "ndk",
+ "objc2",
+ "objc2-app-kit",
+ "objc2-core-foundation",
+ "objc2-foundation",
+ "objc2-ui-kit",
+ "objc2-web-kit",
+ "once_cell",
+ "percent-encoding",
+ "raw-window-handle",
+ "sha2",
+ "soup3",
+ "tao-macros",
+ "thiserror 2.0.20",
+ "url",
+ "webkit2gtk",
+ "webkit2gtk-sys",
+ "webview2-com",
+ "windows",
+ "windows-core 0.61.2",
+ "windows-version",
+ "x11-dl",
+]
+
+[[package]]
+name = "x11"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "x11-dl"
+version = "2.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
+dependencies = [
+ "libc",
+ "once_cell",
+ "pkg-config",
+]
+
+[[package]]
+name = "yoke"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5"
+dependencies = [
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+ "synstructure",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+ "synstructure",
+]
+
+[[package]]
+name = "zerotrie"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.11.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.6",
+]
+
+[[package]]
+name = "zlib-rs"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
+
+[[package]]
+name = "zmij"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
diff --git a/client/src-tauri/Cargo.toml b/client/src-tauri/Cargo.toml
new file mode 100644
index 0000000..6f94e64
--- /dev/null
+++ b/client/src-tauri/Cargo.toml
@@ -0,0 +1,26 @@
+[package]
+name = "app"
+version = "0.1.0"
+description = "A Tauri App"
+authors = ["you"]
+license = ""
+repository = ""
+edition = "2021"
+rust-version = "1.77.2"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+name = "app_lib"
+crate-type = ["staticlib", "cdylib", "rlib"]
+
+[build-dependencies]
+tauri-build = { version = "2.6.3", features = [] }
+
+[dependencies]
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+log = "0.4"
+tauri = { version = "2.11.6", features = [] }
+tauri-plugin-log = "2"
+tauri-plugin-shell = "2.3.6"
diff --git a/client/src-tauri/build.rs b/client/src-tauri/build.rs
new file mode 100644
index 0000000..795b9b7
--- /dev/null
+++ b/client/src-tauri/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ tauri_build::build()
+}
diff --git a/client/src-tauri/capabilities/default.json b/client/src-tauri/capabilities/default.json
new file mode 100644
index 0000000..c135d7f
--- /dev/null
+++ b/client/src-tauri/capabilities/default.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "../gen/schemas/desktop-schema.json",
+ "identifier": "default",
+ "description": "enables the default permissions",
+ "windows": [
+ "main"
+ ],
+ "permissions": [
+ "core:default"
+ ]
+}
diff --git a/client/src-tauri/icons/128x128.png b/client/src-tauri/icons/128x128.png
new file mode 100644
index 0000000..77e7d23
Binary files /dev/null and b/client/src-tauri/icons/128x128.png differ
diff --git a/client/src-tauri/icons/128x128@2x.png b/client/src-tauri/icons/128x128@2x.png
new file mode 100644
index 0000000..0f7976f
Binary files /dev/null and b/client/src-tauri/icons/128x128@2x.png differ
diff --git a/client/src-tauri/icons/32x32.png b/client/src-tauri/icons/32x32.png
new file mode 100644
index 0000000..98fda06
Binary files /dev/null and b/client/src-tauri/icons/32x32.png differ
diff --git a/client/src-tauri/icons/Square107x107Logo.png b/client/src-tauri/icons/Square107x107Logo.png
new file mode 100644
index 0000000..f35d84f
Binary files /dev/null and b/client/src-tauri/icons/Square107x107Logo.png differ
diff --git a/client/src-tauri/icons/Square142x142Logo.png b/client/src-tauri/icons/Square142x142Logo.png
new file mode 100644
index 0000000..1823bb2
Binary files /dev/null and b/client/src-tauri/icons/Square142x142Logo.png differ
diff --git a/client/src-tauri/icons/Square150x150Logo.png b/client/src-tauri/icons/Square150x150Logo.png
new file mode 100644
index 0000000..dc2b22c
Binary files /dev/null and b/client/src-tauri/icons/Square150x150Logo.png differ
diff --git a/client/src-tauri/icons/Square284x284Logo.png b/client/src-tauri/icons/Square284x284Logo.png
new file mode 100644
index 0000000..0ed3984
Binary files /dev/null and b/client/src-tauri/icons/Square284x284Logo.png differ
diff --git a/client/src-tauri/icons/Square30x30Logo.png b/client/src-tauri/icons/Square30x30Logo.png
new file mode 100644
index 0000000..60bf0ea
Binary files /dev/null and b/client/src-tauri/icons/Square30x30Logo.png differ
diff --git a/client/src-tauri/icons/Square310x310Logo.png b/client/src-tauri/icons/Square310x310Logo.png
new file mode 100644
index 0000000..c8ca0ad
Binary files /dev/null and b/client/src-tauri/icons/Square310x310Logo.png differ
diff --git a/client/src-tauri/icons/Square44x44Logo.png b/client/src-tauri/icons/Square44x44Logo.png
new file mode 100644
index 0000000..8756459
Binary files /dev/null and b/client/src-tauri/icons/Square44x44Logo.png differ
diff --git a/client/src-tauri/icons/Square71x71Logo.png b/client/src-tauri/icons/Square71x71Logo.png
new file mode 100644
index 0000000..2c8023c
Binary files /dev/null and b/client/src-tauri/icons/Square71x71Logo.png differ
diff --git a/client/src-tauri/icons/Square89x89Logo.png b/client/src-tauri/icons/Square89x89Logo.png
new file mode 100644
index 0000000..2c5e603
Binary files /dev/null and b/client/src-tauri/icons/Square89x89Logo.png differ
diff --git a/client/src-tauri/icons/StoreLogo.png b/client/src-tauri/icons/StoreLogo.png
new file mode 100644
index 0000000..17d142c
Binary files /dev/null and b/client/src-tauri/icons/StoreLogo.png differ
diff --git a/client/src-tauri/icons/icon.icns b/client/src-tauri/icons/icon.icns
new file mode 100644
index 0000000..a2993ad
Binary files /dev/null and b/client/src-tauri/icons/icon.icns differ
diff --git a/client/src-tauri/icons/icon.ico b/client/src-tauri/icons/icon.ico
new file mode 100644
index 0000000..06c23c8
Binary files /dev/null and b/client/src-tauri/icons/icon.ico differ
diff --git a/client/src-tauri/icons/icon.png b/client/src-tauri/icons/icon.png
new file mode 100644
index 0000000..d1756ce
Binary files /dev/null and b/client/src-tauri/icons/icon.png differ
diff --git a/client/src-tauri/src/lib.rs b/client/src-tauri/src/lib.rs
new file mode 100644
index 0000000..3836125
--- /dev/null
+++ b/client/src-tauri/src/lib.rs
@@ -0,0 +1,137 @@
+use std::sync::Mutex;
+use tauri::{Manager, RunEvent};
+use tauri_plugin_shell::process::CommandEvent;
+use tauri_plugin_shell::ShellExt;
+
+struct ServerProcess(Mutex>);
+
+const PORT: u16 = 3001;
+
+#[cfg_attr(mobile, tauri::mobile_entry_point)]
+pub fn run() {
+ tauri::Builder::default()
+ .plugin(tauri_plugin_shell::init())
+ .manage(ServerProcess(Mutex::new(None)))
+ .setup(|app| {
+ // Unconditional (not just debug builds) -- this is a
+ // personal-use-only app with no external distribution, and
+ // seeing the spawned server's own stdout/stderr (forwarded via
+ // log::info!/log::error! below) is the only way to diagnose a
+ // startup failure in a release build.
+ app.handle().plugin(
+ tauri_plugin_log::Builder::default()
+ .level(log::LevelFilter::Info)
+ .build(),
+ )?;
+
+ let handle = app.handle().clone();
+
+ // GUI-launched macOS apps don't inherit the interactive shell's
+ // PATH (~/.zshrc etc. are only sourced for shell sessions), so
+ // a bare "bun" command lookup fails with ENOENT even though
+ // `which bun` works fine in a terminal -- confirmed by running
+ // this binary directly outside a shell with PATH exported.
+ // Absolute path, same pragmatic choice as FFMPEG_PATH below.
+ let home = std::env::var("HOME").expect("HOME env var not set");
+ let bun_path = format!("{home}/.bun/bin/bun");
+
+ let resource_dir = app.path().resource_dir()?;
+ let server_dir = resource_dir.join("server-bundle");
+
+ let app_data_dir = app.path().app_data_dir()?;
+ std::fs::create_dir_all(&app_data_dir)?;
+ let data_dir = app_data_dir.join("data");
+ std::fs::create_dir_all(&data_dir)?;
+
+ let db_path = data_dir.join("app.db");
+ if !db_path.exists() {
+ std::fs::copy(server_dir.join("app.db.template"), &db_path)?;
+ }
+
+ let (mut rx, child) = handle
+ .shell()
+ .command(bun_path)
+ .args(["run", "src/index.ts"])
+ .current_dir(server_dir.clone())
+ .env("DATABASE_URL", format!("file:{}", db_path.display()))
+ .env("DATA_DIR", data_dir.display().to_string())
+ .env("PORT", PORT.to_string())
+ .env("CORS_ORIGIN", format!("http://localhost:{PORT}"))
+ .env(
+ "FFMPEG_PATH",
+ "/opt/homebrew/opt/ffmpeg-full/bin/ffmpeg",
+ )
+ .env(
+ "CLIENT_DIST_DIR",
+ server_dir.join("client-dist").display().to_string(),
+ )
+ .spawn()
+ .map_err(|e| format!("failed to spawn bundled server process: {e}"))?;
+
+ *handle.state::().0.lock().unwrap() = Some(child);
+
+ // Drain stdout/stderr (so the child's pipes never fill up and
+ // block it) and watch for the "server listening" line the
+ // server logs on startup (see server/src/index.ts) -- once
+ // seen, the server is ready to accept requests, so reveal the
+ // window that was created hidden (see tauri.conf.json) to
+ // avoid a startup race against the window trying to load
+ // http://localhost:3001 before anything is listening there.
+ let handle2 = handle.clone();
+ tauri::async_runtime::spawn(async move {
+ while let Some(event) = rx.recv().await {
+ match event {
+ CommandEvent::Stdout(line) => {
+ let line = String::from_utf8_lossy(&line);
+ log::info!("[server] {line}");
+ if line.contains("server listening") {
+ if let Some(w) = handle2.get_webview_window("main") {
+ // The webview starts loading frontendDist
+ // (http://localhost:PORT) as soon as it's
+ // created -- almost certainly before the
+ // server has finished starting, so that
+ // first load fails and the window is left
+ // showing a blank page. Just calling
+ // show() here does NOT retry that load --
+ // confirmed by testing the built .app,
+ // not just curling the server directly
+ // (which proves the server works but
+ // says nothing about what the webview
+ // itself rendered). Explicitly navigate
+ // now that the server is actually up.
+ if let Ok(url) = tauri::Url::parse(&format!("http://localhost:{PORT}")) {
+ let _ = w.navigate(url);
+ }
+ let _ = w.show();
+ }
+ }
+ }
+ CommandEvent::Stderr(line) => {
+ log::error!("[server] {}", String::from_utf8_lossy(&line));
+ }
+ CommandEvent::Error(err) => {
+ log::error!("[server] process error: {err}");
+ }
+ CommandEvent::Terminated(payload) => {
+ log::warn!("[server] process exited: {payload:?}");
+ }
+ _ => {}
+ }
+ }
+ });
+
+ Ok(())
+ })
+ .build(tauri::generate_context!())
+ .expect("error while building tauri application")
+ .run(|app_handle, event| {
+ // On macOS, a normal Cmd+Q quit reaches RunEvent::Exit directly
+ // without RunEvent::ExitRequested firing first -- kill the
+ // spawned server here, not in an ExitRequested handler.
+ if let RunEvent::Exit = event {
+ if let Some(child) = app_handle.state::().0.lock().unwrap().take() {
+ let _ = child.kill();
+ }
+ }
+ });
+}
diff --git a/client/src-tauri/src/main.rs b/client/src-tauri/src/main.rs
new file mode 100644
index 0000000..ad5fe83
--- /dev/null
+++ b/client/src-tauri/src/main.rs
@@ -0,0 +1,6 @@
+// Prevents additional console window on Windows in release, DO NOT REMOVE!!
+#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
+
+fn main() {
+ app_lib::run();
+}
diff --git a/client/src-tauri/tauri.conf.json b/client/src-tauri/tauri.conf.json
new file mode 100644
index 0000000..c0a67c7
--- /dev/null
+++ b/client/src-tauri/tauri.conf.json
@@ -0,0 +1,49 @@
+{
+ "$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
+ "productName": "AI Video Editor",
+ "version": "0.1.0",
+ "identifier": "com.local.aivideoeditor",
+ "build": {
+ "frontendDist": "http://localhost:3001",
+ "devUrl": "http://localhost:5173",
+ "beforeDevCommand": "pnpm dev",
+ "beforeBuildCommand": "cd .. && pnpm --filter client build && bash scripts/prepare-server-bundle.sh"
+ },
+ "app": {
+ "windows": [
+ {
+ "title": "AI Video Editor",
+ "width": 1280,
+ "height": 800,
+ "minWidth": 960,
+ "minHeight": 600,
+ "resizable": true,
+ "fullscreen": false,
+ "visible": false
+ }
+ ],
+ "security": {
+ "csp": null
+ }
+ },
+ "bundle": {
+ "active": true,
+ "targets": ["app", "dmg"],
+ "icon": [
+ "icons/32x32.png",
+ "icons/128x128.png",
+ "icons/128x128@2x.png",
+ "icons/icon.icns",
+ "icons/icon.ico"
+ ],
+ "resources": {
+ "../../server-bundle/": "server-bundle/"
+ },
+ "macOS": {
+ "signingIdentity": "-"
+ },
+ "android": {
+ "debugApplicationIdSuffix": ".debug"
+ }
+ }
+}
diff --git a/client/src/App.tsx b/client/src/App.tsx
new file mode 100644
index 0000000..07d5618
--- /dev/null
+++ b/client/src/App.tsx
@@ -0,0 +1,73 @@
+import { useEffect, useState } from "react";
+import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
+import { DashboardRoute } from "@/routes/DashboardRoute";
+import { NewProjectRoute } from "@/routes/NewProjectRoute";
+import { EditorRoute } from "@/routes/EditorRoute";
+import { SetupRoute } from "@/routes/SetupRoute";
+import { NotFoundRoute } from "@/routes/NotFoundRoute";
+
+/** Redirects to /setup until an OpenAI API key is configured (checked once on mount). */
+function RequireApiKey({ children }: { children: React.ReactNode }) {
+ const [hasApiKey, setHasApiKey] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ fetch("/api/settings")
+ .then((res) => res.json())
+ .then((data: { success: true; hasApiKey: boolean }) => {
+ if (!cancelled) setHasApiKey(data.hasApiKey);
+ })
+ .catch(() => {
+ // If the check itself fails, don't hard-block the app on it --
+ // let the user in; any actual AI call will fail with its own
+ // clear error if the key is really missing.
+ if (!cancelled) setHasApiKey(true);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ if (hasApiKey === null) {
+ return Loading…
;
+ }
+ if (!hasApiKey) {
+ return ;
+ }
+ return children;
+}
+
+export function App() {
+ return (
+
+
+ } />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+
+
+
+ }
+ />
+ } />
+
+
+ );
+}
diff --git a/client/src/app/api/projects/[id]/filler-words/route.ts b/client/src/app/api/projects/[id]/filler-words/route.ts
deleted file mode 100644
index 33bc2f0..0000000
--- a/client/src/app/api/projects/[id]/filler-words/route.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { detectFillerWords } from "@/lib/ai/filler-words";
-import type { Transcript } from "@/types/transcript";
-
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function POST(_request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- const record = await prisma.transcript.findUnique({ where: { projectId: id } });
- if (!record) {
- return errorResponse("NO_TRANSCRIPT", "This project has no transcript yet.", 400);
- }
-
- const transcript: Transcript = { id: record.id, segments: JSON.parse(record.segmentsJson) };
-
- try {
- const fillerWordIds = await detectFillerWords(transcript);
- return NextResponse.json({ success: true, fillerWordIds });
- } catch (err) {
- console.error("Filler word detection failed:", err);
- return errorResponse("DETECTION_FAILED", "Could not analyze the transcript for filler words.", 502);
- }
-}
diff --git a/client/src/app/api/projects/[id]/operations/[operationId]/route.ts b/client/src/app/api/projects/[id]/operations/[operationId]/route.ts
deleted file mode 100644
index a4bb1ef..0000000
--- a/client/src/app/api/projects/[id]/operations/[operationId]/route.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-
-export const runtime = "nodejs";
-
-type RouteContext = { params: Promise<{ id: string; operationId: string }> };
-
-export async function DELETE(_request: Request, { params }: RouteContext) {
- const { id: projectId, operationId } = await params;
-
- const op = await prisma.editOperation.findUnique({ where: { id: operationId } });
- if (!op || op.projectId !== projectId) {
- return NextResponse.json(
- { success: false, error: { code: "NOT_FOUND", message: "Operation not found." } },
- { status: 404 }
- );
- }
-
- await prisma.editOperation.delete({ where: { id: operationId } });
- return NextResponse.json({ success: true });
-}
diff --git a/client/src/app/api/projects/[id]/operations/route.ts b/client/src/app/api/projects/[id]/operations/route.ts
deleted file mode 100644
index b70a3f6..0000000
--- a/client/src/app/api/projects/[id]/operations/route.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { editOperationSchema } from "@/lib/validation/edit-operation";
-
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function POST(request: Request, { params }: RouteContext) {
- const { id: projectId } = await params;
-
- let body: unknown;
- try {
- body = await request.json();
- } catch {
- return errorResponse("INVALID_REQUEST", "Expected a JSON body.", 400);
- }
-
- const parsed = editOperationSchema.safeParse(body);
- if (!parsed.success) {
- return errorResponse(
- "INVALID_OPERATION",
- parsed.error.issues[0]?.message ?? "Invalid edit operation.",
- 400
- );
- }
-
- const op = parsed.data;
- if ((op.type === "cut" || op.type === "trim") && op.end <= op.start) {
- return errorResponse("INVALID_OPERATION", "end must be after start.", 400);
- }
-
- const project = await prisma.project.findUnique({ where: { id: projectId }, select: { id: true } });
- if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
-
- // Client generates the id (needed so undo/redo can address the exact row);
- // upsert so a redo that restores a previously-undone op is idempotent.
- const saved = await prisma.editOperation.upsert({
- where: { id: op.id },
- create: { id: op.id, projectId, type: op.type, dataJson: JSON.stringify(op) },
- update: { dataJson: JSON.stringify(op) },
- });
-
- return NextResponse.json({ success: true, operation: JSON.parse(saved.dataJson) }, { status: 201 });
-}
diff --git a/client/src/app/api/projects/[id]/render/[jobId]/download/route.ts b/client/src/app/api/projects/[id]/render/[jobId]/download/route.ts
deleted file mode 100644
index cc5dd79..0000000
--- a/client/src/app/api/projects/[id]/render/[jobId]/download/route.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { readAssetStream, statAsset, streamToWebReadable } from "@/lib/storage/local";
-
-export const runtime = "nodejs";
-
-type RouteContext = { params: Promise<{ id: string; jobId: string }> };
-
-export async function GET(_request: Request, { params }: RouteContext) {
- const { id, jobId } = await params;
-
- const job = await prisma.renderJob.findUnique({ where: { id: jobId } });
- if (!job || job.projectId !== id || job.status !== "completed" || !job.outputPath) {
- return NextResponse.json(
- { success: false, error: { code: "NOT_READY", message: "This render isn't ready to download." } },
- { status: 404 }
- );
- }
-
- const project = await prisma.project.findUnique({ where: { id }, select: { name: true } });
- const filename = `${(project?.name ?? "video").replace(/[^a-zA-Z0-9_-]/g, "_")}.mp4`;
-
- const stats = await statAsset(job.outputPath);
- const stream = readAssetStream(job.outputPath);
-
- return new NextResponse(streamToWebReadable(stream), {
- status: 200,
- headers: {
- "Content-Type": "video/mp4",
- "Content-Length": String(stats.size),
- "Content-Disposition": `attachment; filename="${filename}"`,
- },
- });
-}
diff --git a/client/src/app/api/projects/[id]/render/[jobId]/route.ts b/client/src/app/api/projects/[id]/render/[jobId]/route.ts
deleted file mode 100644
index 06e80de..0000000
--- a/client/src/app/api/projects/[id]/render/[jobId]/route.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-
-export const runtime = "nodejs";
-
-type RouteContext = { params: Promise<{ id: string; jobId: string }> };
-
-export async function GET(_request: Request, { params }: RouteContext) {
- const { id, jobId } = await params;
-
- const job = await prisma.renderJob.findUnique({ where: { id: jobId } });
- if (!job || job.projectId !== id) {
- return NextResponse.json(
- { success: false, error: { code: "NOT_FOUND", message: "Render job not found." } },
- { status: 404 }
- );
- }
-
- return NextResponse.json({
- success: true,
- job: {
- id: job.id,
- status: job.status,
- error: job.error,
- downloadUrl: job.status === "completed" ? `/api/projects/${id}/render/${jobId}/download` : null,
- },
- });
-}
diff --git a/client/src/app/api/projects/[id]/render/route.ts b/client/src/app/api/projects/[id]/render/route.ts
deleted file mode 100644
index bae3495..0000000
--- a/client/src/app/api/projects/[id]/render/route.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { runRenderJob } from "@/lib/ffmpeg/render-job";
-import { captionStyleSchema } from "@/lib/validation/caption-style";
-import type { CaptionStyle } from "@/lib/captions/style";
-
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function POST(request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- let burnInCaptions = false;
- let captionStyle: CaptionStyle | undefined;
- try {
- const body = await request.json();
- burnInCaptions = body?.burnInCaptions === true;
- if (burnInCaptions && body?.captionStyle !== undefined) {
- const parsed = captionStyleSchema.safeParse(body.captionStyle);
- if (!parsed.success) {
- return errorResponse("INVALID_CAPTION_STYLE", "Invalid caption style.", 400);
- }
- captionStyle = parsed.data;
- }
- } catch {
- // No body (or invalid JSON) just means "no captions" -- not an error.
- }
-
- const project = await prisma.project.findUnique({
- where: { id },
- include: { assets: true },
- });
- if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
- if (!project.assets.some((a) => a.kind === "original")) {
- return errorResponse("NO_VIDEO", "This project has no video to render.", 400);
- }
-
- const job = await prisma.renderJob.create({
- data: { projectId: id, status: "queued" },
- });
-
- // Fire-and-forget: never block the request on FFmpeg (spec section 14).
- void runRenderJob(job.id, { burnInCaptions, captionStyle });
-
- return NextResponse.json({ success: true, jobId: job.id }, { status: 202 });
-}
diff --git a/client/src/app/api/projects/[id]/route.ts b/client/src/app/api/projects/[id]/route.ts
deleted file mode 100644
index d1c869b..0000000
--- a/client/src/app/api/projects/[id]/route.ts
+++ /dev/null
@@ -1,107 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { deleteProjectDir } from "@/lib/storage/local";
-import { updateProjectSchema } from "@/lib/validation/project";
-import type { Transcript } from "@/types/transcript";
-import type { EditOperation } from "@/types/edit-operation";
-
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function GET(_request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- const project = await prisma.project.findUnique({
- where: { id },
- include: {
- assets: true,
- transcript: true,
- editOperations: { orderBy: { createdAt: "asc" } },
- },
- });
-
- if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
-
- const transcript: Transcript | null = project.transcript
- ? { id: project.transcript.id, segments: JSON.parse(project.transcript.segmentsJson) }
- : null;
-
- const operations: EditOperation[] = project.editOperations.map(
- (op) => JSON.parse(op.dataJson) as EditOperation
- );
-
- const originalAsset = project.assets.find((a) => a.kind === "original") ?? null;
- const logoAsset = project.assets.find((a) => a.kind === "logo") ?? null;
-
- return NextResponse.json({
- success: true,
- project: {
- id: project.id,
- name: project.name,
- duration: project.duration,
- filterId: project.filterId,
- burnInCaptions: project.burnInCaptions,
- captionStyle: project.captionStyleJson ? JSON.parse(project.captionStyleJson) : null,
- properties: project.propertiesJson ? JSON.parse(project.propertiesJson) : null,
- logoPosition: project.logoPosition,
- logoPaddingX: project.logoPaddingX,
- logoPaddingY: project.logoPaddingY,
- logoOpacity: project.logoOpacity,
- createdAt: project.createdAt,
- updatedAt: project.updatedAt,
- },
- transcript,
- operations,
- videoUrl: originalAsset ? `/api/projects/${project.id}/video` : null,
- logoUrl: logoAsset ? `/api/projects/${project.id}/logo` : null,
- });
-}
-
-export async function PATCH(request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- let body: unknown;
- try {
- body = await request.json();
- } catch {
- return errorResponse("INVALID_REQUEST", "Expected a JSON body.", 400);
- }
-
- const parsed = updateProjectSchema.safeParse(body);
- if (!parsed.success) {
- return errorResponse("INVALID_REQUEST", parsed.error.issues[0]?.message ?? "Invalid update.", 400);
- }
-
- const exists = await prisma.project.findUnique({ where: { id }, select: { id: true } });
- if (!exists) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
-
- const project = await prisma.project.update({
- where: { id },
- data: parsed.data,
- });
-
- return NextResponse.json({ success: true, project });
-}
-
-export async function DELETE(_request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- const exists = await prisma.project.findUnique({ where: { id }, select: { id: true } });
- if (!exists) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
-
- await prisma.project.delete({ where: { id } });
- await deleteProjectDir(id);
-
- return NextResponse.json({ success: true });
-}
diff --git a/client/src/app/api/projects/[id]/upload/route.ts b/client/src/app/api/projects/[id]/upload/route.ts
deleted file mode 100644
index 6ff3dcc..0000000
--- a/client/src/app/api/projects/[id]/upload/route.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { writeAsset } from "@/lib/storage/local";
-
-export const runtime = "nodejs";
-
-const MAX_FILE_BYTES = 500 * 1024 * 1024; // 500MB local upload cap
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function POST(request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- const project = await prisma.project.findUnique({ where: { id }, select: { id: true } });
- if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
- }
-
- let formData: FormData;
- try {
- formData = await request.formData();
- } catch {
- return errorResponse("INVALID_REQUEST", "Expected multipart/form-data.", 400);
- }
-
- const file = formData.get("file");
- if (!(file instanceof File)) {
- return errorResponse("MISSING_FILE", "No file was provided.", 400);
- }
- if (file.size === 0) {
- return errorResponse("EMPTY_FILE", "The uploaded file is empty.", 400);
- }
- if (file.size > MAX_FILE_BYTES) {
- return errorResponse(
- "FILE_TOO_LARGE",
- `File exceeds the ${MAX_FILE_BYTES / (1024 * 1024)}MB upload limit.`,
- 413
- );
- }
- if (!file.type.startsWith("video/")) {
- return errorResponse("INVALID_FILE_TYPE", "Only video files are supported.", 400);
- }
-
- const bytes = new Uint8Array(await file.arrayBuffer());
- const { relativePath } = await writeAsset(id, "original", file.name, bytes);
-
- // Only one "original" asset per project for v1 — replace any previous one.
- await prisma.asset.deleteMany({ where: { projectId: id, kind: "original" } });
- const asset = await prisma.asset.create({
- data: {
- projectId: id,
- kind: "original",
- filePath: relativePath,
- mimeType: file.type,
- sizeBytes: file.size,
- },
- });
-
- return NextResponse.json({
- success: true,
- asset,
- videoUrl: `/api/projects/${id}/video`,
- });
-}
diff --git a/client/src/app/api/projects/[id]/video/route.ts b/client/src/app/api/projects/[id]/video/route.ts
deleted file mode 100644
index a195824..0000000
--- a/client/src/app/api/projects/[id]/video/route.ts
+++ /dev/null
@@ -1,57 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { readAssetStream, statAsset, streamToWebReadable } from "@/lib/storage/local";
-
-export const runtime = "nodejs";
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function GET(request: Request, { params }: RouteContext) {
- const { id } = await params;
-
- const asset = await prisma.asset.findFirst({
- where: { projectId: id, kind: "original" },
- });
- if (!asset) {
- return NextResponse.json({ success: false, error: { code: "NOT_FOUND", message: "No video for this project." } }, { status: 404 });
- }
-
- const stats = await statAsset(asset.filePath);
- const fileSize = stats.size;
- const rangeHeader = request.headers.get("range");
-
- if (!rangeHeader) {
- const stream = readAssetStream(asset.filePath);
- return new NextResponse(streamToWebReadable(stream), {
- status: 200,
- headers: {
- "Content-Type": asset.mimeType,
- "Content-Length": String(fileSize),
- "Accept-Ranges": "bytes",
- },
- });
- }
-
- const match = /^bytes=(\d*)-(\d*)$/.exec(rangeHeader);
- if (!match) {
- return new NextResponse(null, { status: 416, headers: { "Content-Range": `bytes */${fileSize}` } });
- }
-
- const start = match[1] ? Number(match[1]) : 0;
- const end = match[2] ? Number(match[2]) : fileSize - 1;
-
- if (Number.isNaN(start) || Number.isNaN(end) || start > end || end >= fileSize) {
- return new NextResponse(null, { status: 416, headers: { "Content-Range": `bytes */${fileSize}` } });
- }
-
- const stream = readAssetStream(asset.filePath, { start, end });
- return new NextResponse(streamToWebReadable(stream), {
- status: 206,
- headers: {
- "Content-Type": asset.mimeType,
- "Content-Length": String(end - start + 1),
- "Content-Range": `bytes ${start}-${end}/${fileSize}`,
- "Accept-Ranges": "bytes",
- },
- });
-}
diff --git a/client/src/app/api/projects/route.ts b/client/src/app/api/projects/route.ts
deleted file mode 100644
index abcd478..0000000
--- a/client/src/app/api/projects/route.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { NextResponse } from "next/server";
-import { prisma } from "@/lib/db/client";
-import { ensureProjectDirs } from "@/lib/storage/local";
-import { createProjectSchema } from "@/lib/validation/project";
-
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-export async function GET() {
- const projects = await prisma.project.findMany({
- orderBy: { updatedAt: "desc" },
- });
- return NextResponse.json({ success: true, projects });
-}
-
-export async function POST(request: Request) {
- let body: unknown;
- try {
- body = await request.json();
- } catch {
- return errorResponse("INVALID_REQUEST", "Expected a JSON body.", 400);
- }
-
- const parsed = createProjectSchema.safeParse(body);
- if (!parsed.success) {
- return errorResponse("INVALID_PROJECT", parsed.error.issues[0]?.message ?? "Invalid project.", 400);
- }
-
- const project = await prisma.project.create({
- data: { name: parsed.data.name },
- });
- await ensureProjectDirs(project.id);
-
- return NextResponse.json({ success: true, project }, { status: 201 });
-}
diff --git a/client/src/app/editor/[projectId]/page.tsx b/client/src/app/editor/[projectId]/page.tsx
deleted file mode 100644
index ea15e1f..0000000
--- a/client/src/app/editor/[projectId]/page.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import { notFound } from "next/navigation";
-import { prisma } from "@/lib/db/client";
-import { EditorHydrator } from "@/components/editor/EditorHydrator";
-import { toLogoPosition } from "@/lib/video/logo";
-import type { Transcript } from "@/types/transcript";
-import type { EditOperation } from "@/types/edit-operation";
-
-export default async function EditorPage(props: PageProps<"/editor/[projectId]">) {
- const { projectId } = await props.params;
-
- const project = await prisma.project.findUnique({
- where: { id: projectId },
- include: {
- assets: true,
- transcript: true,
- editOperations: { orderBy: { createdAt: "asc" } },
- },
- });
-
- if (!project) notFound();
-
- const transcript: Transcript | null = project.transcript
- ? { id: project.transcript.id, segments: JSON.parse(project.transcript.segmentsJson) }
- : null;
-
- const operations: EditOperation[] = project.editOperations.map(
- (op) => JSON.parse(op.dataJson) as EditOperation
- );
-
- const hasVideo = project.assets.some((a) => a.kind === "original");
- const hasLogo = project.assets.some((a) => a.kind === "logo");
-
- return (
-
- );
-}
diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx
deleted file mode 100644
index 689b41c..0000000
--- a/client/src/app/layout.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-import type { Metadata } from "next";
-import { IBM_Plex_Sans, IBM_Plex_Mono } from "next/font/google";
-import { TooltipProvider } from "@/components/ui/tooltip";
-import "./globals.css";
-
-const plexSans = IBM_Plex_Sans({
- variable: "--font-plex-sans",
- subsets: ["latin"],
- weight: ["400", "500", "600"],
-});
-
-const plexMono = IBM_Plex_Mono({
- variable: "--font-plex-mono",
- subsets: ["latin"],
- weight: ["400", "500"],
-});
-
-export const metadata: Metadata = {
- title: "AI Video Editor",
- description: "Edit video by editing its transcript.",
-};
-
-export default function RootLayout({ children }: LayoutProps<"/">) {
- return (
-
-
- {children}
-
-
- );
-}
diff --git a/client/src/app/page.tsx b/client/src/app/page.tsx
deleted file mode 100644
index b64596b..0000000
--- a/client/src/app/page.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { prisma } from "@/lib/db/client";
-import { Dashboard } from "@/components/dashboard/Dashboard";
-import type { Project } from "@/types/project";
-
-// Reads live project data on every request — must not be statically cached
-// at build time (the default Next.js would otherwise pick for a page with
-// no dynamic APIs/params).
-export const dynamic = "force-dynamic";
-
-export default async function Home() {
- const rows = await prisma.project.findMany({
- orderBy: { updatedAt: "desc" },
- include: {
- assets: { select: { kind: true } },
- transcript: { select: { id: true } },
- _count: { select: { editOperations: true } },
- },
- });
-
- const projects: Project[] = rows.map((p) => ({
- id: p.id,
- name: p.name,
- duration: p.duration,
- filterId: p.filterId,
- createdAt: p.createdAt.toISOString(),
- updatedAt: p.updatedAt.toISOString(),
- hasVideo: p.assets.some((a) => a.kind === "original"),
- hasTranscript: p.transcript !== null,
- cutCount: p._count.editOperations,
- }));
-
- return ;
-}
diff --git a/client/src/components/dashboard/Dashboard.tsx b/client/src/components/dashboard/Dashboard.tsx
index c2695cf..b1c9434 100644
--- a/client/src/components/dashboard/Dashboard.tsx
+++ b/client/src/components/dashboard/Dashboard.tsx
@@ -1,7 +1,7 @@
"use client";
import { useState } from "react";
-import Link from "next/link";
+import { Link } from "react-router-dom";
import { AudioLines, FolderPlus } from "lucide-react";
import { Button } from "@/components/ui/button";
import { ProjectRow } from "@/components/dashboard/ProjectRow";
@@ -44,7 +44,7 @@ export function Dashboard({ initialProjects }: { initialProjects: Project[] }) {
-
+
New project
@@ -61,7 +61,7 @@ export function Dashboard({ initialProjects }: { initialProjects: Project[] }) {
Upload a video to auto-transcribe it, then edit the video by editing the words.
- New project
+ New project
) : (
diff --git a/client/src/components/dashboard/ProjectRow.tsx b/client/src/components/dashboard/ProjectRow.tsx
index 8db65c5..604c7ad 100644
--- a/client/src/components/dashboard/ProjectRow.tsx
+++ b/client/src/components/dashboard/ProjectRow.tsx
@@ -1,7 +1,7 @@
"use client";
import { useEffect, useRef, useState } from "react";
-import Link from "next/link";
+import { Link } from "react-router-dom";
import { Captions, Film, MoreVertical, Scissors } from "lucide-react";
import { Input } from "@/components/ui/input";
import {
@@ -125,7 +125,7 @@ export function ProjectRow({
return (
{project.hasVideo ? (
@@ -160,7 +160,7 @@ export function ProjectRow({
className="h-7"
/>
) : (
-
+
{project.name}
)}
diff --git a/client/src/components/editor/EditorLayout.tsx b/client/src/components/editor/EditorLayout.tsx
index 4185663..ca5c5fe 100644
--- a/client/src/components/editor/EditorLayout.tsx
+++ b/client/src/components/editor/EditorLayout.tsx
@@ -1,6 +1,6 @@
"use client";
-import Link from "next/link";
+import { Link } from "react-router-dom";
import { AudioLines, ChevronLeft } from "lucide-react";
import { VideoPlayer } from "@/components/video-player/VideoPlayer";
import { TranscriptPanel } from "@/components/transcript/TranscriptPanel";
@@ -24,7 +24,7 @@ export function EditorLayout({ videoUrl }: { videoUrl: string }) {
diff --git a/client/src/components/editor/ElementsPanel.tsx b/client/src/components/editor/ElementsPanel.tsx
index e85e50a..64014cd 100644
--- a/client/src/components/editor/ElementsPanel.tsx
+++ b/client/src/components/editor/ElementsPanel.tsx
@@ -79,7 +79,6 @@ export function ElementsPanel() {
{logoUrl && (
- {/* eslint-disable-next-line @next/next/no-img-element -- served from local storage, not a static asset Next can optimize */}
{thumbnail ? (
- // eslint-disable-next-line @next/next/no-img-element -- locally generated data URL, not a static asset
(url: string, body: unknown): Promise
{
}
export function UploadScreen() {
- const router = useRouter();
+ const navigate = useNavigate();
const fileInputRef = useRef(null);
const [nameDraft, setNameDraft] = useState("Untitled Project");
@@ -43,12 +42,14 @@ export function UploadScreen() {
setStatus("uploading");
try {
- const formData = new FormData();
- formData.append("file", file);
- const uploadRes = await fetch(`/api/projects/${projectId}/upload`, {
- method: "POST",
- body: formData,
- });
+ const uploadRes = await fetch(
+ `/api/projects/${projectId}/upload?filename=${encodeURIComponent(file.name)}`,
+ {
+ method: "POST",
+ headers: { "Content-Type": file.type },
+ body: file,
+ }
+ );
const uploaded: { success: true } | ApiError = await uploadRes.json();
if (!uploaded.success) {
setErrorState(uploaded.error.message);
@@ -72,7 +73,7 @@ export function UploadScreen() {
return;
}
- router.push(`/editor/${projectId}`);
+ navigate(`/editor/${projectId}`);
};
const busy = status === "creating" || status === "uploading" || status === "transcribing";
@@ -91,7 +92,7 @@ export function UploadScreen() {
{!busy && (
diff --git a/client/src/components/timeline/Timeline.tsx b/client/src/components/timeline/Timeline.tsx
index 359d99a..cb9f65e 100644
--- a/client/src/components/timeline/Timeline.tsx
+++ b/client/src/components/timeline/Timeline.tsx
@@ -164,7 +164,6 @@ export function Timeline() {
title={`${r.start.toFixed(1)}s – ${r.end.toFixed(1)}s`}
>
{thumbnails[i]?.map((src, k) => (
- // eslint-disable-next-line @next/next/no-img-element -- locally generated data URL, not a static asset
{logoUrl && (
- // eslint-disable-next-line @next/next/no-img-element -- served from local storage, not a static asset Next can optimize
tag
+ * in index.html instead -- see index.html for why (no build-time
+ * font-fetch-and-self-host step in a plain Vite SPA, and this is a
+ * locally-run single-user tool, not a public site optimizing first-load
+ * metrics on a slow network).
+ */
+:root {
+ --font-plex-sans: "IBM Plex Sans", ui-sans-serif, system-ui, sans-serif;
+ --font-plex-mono: "IBM Plex Mono", ui-monospace, monospace;
+}
+
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
diff --git a/client/src/main.tsx b/client/src/main.tsx
new file mode 100644
index 0000000..bd4961a
--- /dev/null
+++ b/client/src/main.tsx
@@ -0,0 +1,13 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import { TooltipProvider } from "@/components/ui/tooltip";
+import { App } from "./App";
+import "./globals.css";
+
+createRoot(document.getElementById("root")!).render(
+
+
+
+
+
+);
diff --git a/client/src/routes/DashboardRoute.tsx b/client/src/routes/DashboardRoute.tsx
new file mode 100644
index 0000000..d232af1
--- /dev/null
+++ b/client/src/routes/DashboardRoute.tsx
@@ -0,0 +1,25 @@
+import { useEffect, useState } from "react";
+import { Dashboard } from "@/components/dashboard/Dashboard";
+import type { Project } from "@/types/project";
+
+export function DashboardRoute() {
+ const [projects, setProjects] = useState
(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ fetch("/api/projects")
+ .then((res) => res.json())
+ .then((data: { success: true; projects: Project[] }) => {
+ if (!cancelled) setProjects(data.projects);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ if (projects === null) {
+ return Loading…
;
+ }
+
+ return ;
+}
diff --git a/client/src/routes/EditorRoute.tsx b/client/src/routes/EditorRoute.tsx
new file mode 100644
index 0000000..f2242d4
--- /dev/null
+++ b/client/src/routes/EditorRoute.tsx
@@ -0,0 +1,103 @@
+import { useEffect, useState } from "react";
+import { useParams } from "react-router-dom";
+import { EditorHydrator } from "@/components/editor/EditorHydrator";
+import { NotFoundRoute } from "@/routes/NotFoundRoute";
+import { toLogoPosition } from "@/lib/video/logo";
+import type { Transcript } from "@/types/transcript";
+import type { EditOperation } from "@/types/edit-operation";
+import type { CaptionStyle } from "@/lib/captions/style";
+import type { VideoProperties } from "@/types/video-properties";
+
+type ProjectDetailResponse = {
+ success: true;
+ project: {
+ id: string;
+ name: string;
+ duration: number | null;
+ filterId: string;
+ burnInCaptions: boolean;
+ captionStyle: CaptionStyle | null;
+ properties: VideoProperties | null;
+ logoPosition: string;
+ logoPaddingX: number;
+ logoPaddingY: number;
+ logoOpacity: number;
+ createdAt: string;
+ updatedAt: string;
+ };
+ transcript: Transcript | null;
+ operations: EditOperation[];
+ videoUrl: string | null;
+ logoUrl: string | null;
+};
+
+type FetchState =
+ | { status: "loading" }
+ | { status: "not-found" }
+ | { status: "error" }
+ | ({ status: "loaded" } & ProjectDetailResponse);
+
+export function EditorRoute() {
+ const { projectId } = useParams<{ projectId: string }>();
+ const [state, setState] = useState({ status: "loading" });
+
+ useEffect(() => {
+ let cancelled = false;
+ setState({ status: "loading" });
+
+ fetch(`/api/projects/${projectId}`)
+ .then(async (res) => {
+ if (res.status === 404) return { status: "not-found" as const };
+ if (!res.ok) return { status: "error" as const };
+ const data: ProjectDetailResponse = await res.json();
+ return { status: "loaded" as const, ...data };
+ })
+ .then((next) => {
+ if (!cancelled) setState(next);
+ })
+ .catch(() => {
+ if (!cancelled) setState({ status: "error" });
+ });
+
+ return () => {
+ cancelled = true;
+ };
+ }, [projectId]);
+
+ if (state.status === "loading") {
+ return Loading…
;
+ }
+ if (state.status === "not-found") {
+ return ;
+ }
+ if (state.status === "error") {
+ return (
+
+ Could not load this project. Try refreshing.
+
+ );
+ }
+
+ const { project, transcript, operations, videoUrl, logoUrl } = state;
+
+ return (
+
+ );
+}
diff --git a/client/src/app/new/page.tsx b/client/src/routes/NewProjectRoute.tsx
similarity index 68%
rename from client/src/app/new/page.tsx
rename to client/src/routes/NewProjectRoute.tsx
index 4580488..dff45e7 100644
--- a/client/src/app/new/page.tsx
+++ b/client/src/routes/NewProjectRoute.tsx
@@ -1,5 +1,5 @@
import { UploadScreen } from "@/components/editor/UploadScreen";
-export default function NewProjectPage() {
+export function NewProjectRoute() {
return ;
}
diff --git a/client/src/routes/NotFoundRoute.tsx b/client/src/routes/NotFoundRoute.tsx
new file mode 100644
index 0000000..a2df7f9
--- /dev/null
+++ b/client/src/routes/NotFoundRoute.tsx
@@ -0,0 +1,20 @@
+import { Link } from "react-router-dom";
+import { ChevronLeft } from "lucide-react";
+import { Button } from "@/components/ui/button";
+
+export function NotFoundRoute() {
+ return (
+
+
Project not found
+
+ It may have been deleted, or the link is wrong.
+
+
+
+
+ Back to projects
+
+
+
+ );
+}
diff --git a/client/src/routes/SetupRoute.tsx b/client/src/routes/SetupRoute.tsx
new file mode 100644
index 0000000..746269d
--- /dev/null
+++ b/client/src/routes/SetupRoute.tsx
@@ -0,0 +1,106 @@
+import { useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+
+type ApiError = { success: false; error: { code: string; message: string } };
+
+export function SetupRoute() {
+ const navigate = useNavigate();
+ const [checking, setChecking] = useState(true);
+ const [hasApiKey, setHasApiKey] = useState(false);
+ const [keyDraft, setKeyDraft] = useState("");
+ const [saving, setSaving] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ let cancelled = false;
+ fetch("/api/settings")
+ .then((res) => res.json())
+ .then((data: { success: true; hasApiKey: boolean }) => {
+ if (!cancelled) {
+ setHasApiKey(data.hasApiKey);
+ setChecking(false);
+ }
+ })
+ .catch(() => {
+ if (!cancelled) setChecking(false);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const handleSave = async () => {
+ if (!keyDraft.trim()) return;
+ setSaving(true);
+ setError(null);
+
+ try {
+ const res = await fetch("/api/settings", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ openaiApiKey: keyDraft.trim() }),
+ });
+ const data: { success: true } | ApiError = await res.json();
+ if (!data.success) {
+ setError(data.error.message);
+ setSaving(false);
+ return;
+ }
+ navigate("/");
+ } catch {
+ setError("Could not reach the server. Try again.");
+ setSaving(false);
+ }
+ };
+
+ if (checking) {
+ return Loading…
;
+ }
+
+ return (
+
+
+
+
{hasApiKey ? "Replace API key" : "Welcome"}
+
+ {hasApiKey
+ ? "Enter a new OpenAI API key to replace the one currently configured."
+ : "This app uses your own OpenAI API key for transcription and AI features -- it's stored only on this machine, never sent anywhere but OpenAI."}
+
+
+
+
+ setKeyDraft(e.target.value)}
+ placeholder="sk-..."
+ disabled={saving}
+ autoFocus
+ onKeyDown={(e) => {
+ if (e.key === "Enter") void handleSave();
+ }}
+ />
+ void handleSave()} disabled={saving || !keyDraft.trim()} className="w-full">
+ {saving ? "Checking key…" : "Save"}
+
+ {hasApiKey && (
+ navigate("/")} disabled={saving} className="w-full">
+ Cancel
+
+ )}
+
+
+ {error ? (
+
{error}
+ ) : (
+
+ platform.openai.com/api-keys
+
+ )}
+
+
+ );
+}
diff --git a/client/tsconfig.json b/client/tsconfig.json
index cf9c65d..6240442 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
- "target": "ES2017",
+ "target": "ES2020",
+ "useDefineForClassFields": true,
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@@ -13,22 +14,11 @@
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
- "plugins": [
- {
- "name": "next"
- }
- ],
+ "types": ["vite/client", "node"],
"paths": {
"@/*": ["./src/*"]
}
},
- "include": [
- "next-env.d.ts",
- "**/*.ts",
- "**/*.tsx",
- ".next/types/**/*.ts",
- ".next/dev/types/**/*.ts",
- "**/*.mts"
- ],
- "exclude": ["node_modules"]
+ "include": ["**/*.ts", "**/*.tsx", "**/*.mts"],
+ "exclude": ["node_modules", "dist", "src-tauri"]
}
diff --git a/client/vite.config.mts b/client/vite.config.mts
new file mode 100644
index 0000000..23c709e
--- /dev/null
+++ b/client/vite.config.mts
@@ -0,0 +1,17 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+ plugins: [react()],
+ resolve: {
+ tsconfigPaths: true,
+ },
+ server: {
+ proxy: {
+ "/api": {
+ target: process.env.API_PROXY_TARGET ?? "http://localhost:3001",
+ changeOrigin: true,
+ },
+ },
+ },
+});
diff --git a/client/vitest.config.mts b/client/vitest.config.mts
index 105083c..961a6c9 100644
--- a/client/vitest.config.mts
+++ b/client/vitest.config.mts
@@ -7,12 +7,5 @@ export default defineConfig({
test: {
environment: "node",
include: ["src/**/*.test.ts"],
- env: {
- // lib/ai/filler-words.ts constructs its OpenAI client at module load
- // time, which throws without a key -- this dummy value only needs to
- // exist, never a real one, since the tests that import it only
- // exercise the deterministic path that returns before any API call.
- OPENAI_API_KEY: "test-key-not-used",
- },
},
});
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index 48cbcc5..43db7cf 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -17,6 +17,7 @@ export default defineConfig({
logo: "/favicon.svg",
nav: [
+ { text: "Download", link: "/download" },
{ text: "Guide", link: "/guide/getting-started" },
{ text: "Screenshots", link: "/screenshots" },
{ text: "Status", link: "/status" },
diff --git a/docs/download.md b/docs/download.md
new file mode 100644
index 0000000..e187d81
--- /dev/null
+++ b/docs/download.md
@@ -0,0 +1,71 @@
+# Download the macOS app
+
+A native Mac app is available, built with [Tauri](https://tauri.app/) —
+same editor, wrapped as a real `.app` you launch from Finder or Spotlight
+instead of running `pnpm run dev` and opening a browser tab.
+
+**Current scope: Apple Silicon (M1/M2/M3/M4) only.** No Intel build yet.
+
+
+
+Prefer a `.dmg` installer instead of a `.app.zip`? Grab it from the
+[latest release page](https://github.com/amide-init/transcriptcut/releases/latest)
+directly — its filename includes the version number, so it doesn't have
+a permanent link the way the zip above does.
+
+## Before you install
+
+The app needs two things already on your Mac — the same prerequisites a
+local dev setup uses, just without the rest of the dev toolchain:
+
+```bash
+# Bun -- the app spawns it to run its own backend
+curl -fsSL https://bun.sh/install | bash
+
+# FFmpeg with libass (subtitle burn-in) support, via Homebrew
+brew install ffmpeg-full
+```
+
+Without these, the app will open but fail to actually start its backend
+— see [Architecture](/guide/architecture) if you want the details of why.
+
+## Opening it the first time
+
+This build is **ad-hoc signed**, not signed with a paid Apple Developer
+account — a deliberate scope choice for now, not an oversight (see the
+project's `claude.md` section 35 if you're curious why). That means
+Gatekeeper will refuse a plain double-click the first time. To open it:
+
+1. Unzip the download (or mount the `.dmg` and drag the app to
+ Applications).
+2. **Right-click the app → Open**, then confirm in the dialog that
+ appears. A regular double-click will still be blocked — this step has
+ to be the right-click.
+3. On newer macOS, if that doesn't work: **System Settings → Privacy &
+ Security**, scroll down, and click **Open Anyway** next to the app's
+ name.
+
+You only need to do this once per download.
+
+## First launch
+
+The app asks for your own OpenAI API key on first launch (used only for
+transcription and filler-word detection, stored only on your machine,
+never sent anywhere but OpenAI) — get one at
+[platform.openai.com/api-keys](https://platform.openai.com/api-keys) if
+you don't already have one.
+
+## Building it yourself
+
+Prefer to build from source instead of downloading a release? See
+[Getting Started](/guide/getting-started) for the full dev setup, then:
+
+```bash
+cd client
+pnpm exec tauri build
+```
diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md
index 1f8392f..fecc485 100644
--- a/docs/guide/architecture.md
+++ b/docs/guide/architecture.md
@@ -18,6 +18,17 @@ If you want to self-host this on a VPS, a container, or a home server,
that should work by pointing `DATA_DIR` at a persistent volume — no
rewrite required.
+## Two processes, one dev command
+
+`client/` is a Vite + React SPA (no server-side rendering); `server/` is
+a Bun + Hono backend serving the REST API, FFmpeg, Prisma/SQLite, and
+the OpenAI calls. They run as separate processes, started together by
+`pnpm run dev` from the repo root, with Vite proxying `/api/*` to the
+backend in development. This split — one deployable backend unit, no
+server-rendering step to work around — is also what makes the native
+macOS app possible: the packaged app spawns the same Bun server as a
+child process and points a Tauri window at it. See [Download](/download).
+
## Data on disk
```text
diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md
index 1a0807e..6bef667 100644
--- a/docs/guide/getting-started.md
+++ b/docs/guide/getting-started.md
@@ -1,14 +1,18 @@
# Getting Started
transcriptcut is local-first: everything runs on your own machine with
-`npm install && npm run dev` (well — `pnpm`, see below). The only
-external dependency is an LLM provider API key, used purely for
-transcription and word classification, never for storage.
+`pnpm install && pnpm run dev`. The only external dependency is an LLM
+provider API key, used purely for transcription and word classification,
+never for storage.
+
+Prefer not to run anything at all? A native macOS app is also available
+— see [Download](/download).
## Requirements
- [Node.js](https://nodejs.org/) 20+
- [pnpm](https://pnpm.io/) 10+
+- [Bun](https://bun.sh) (the backend's runtime)
- [FFmpeg](https://ffmpeg.org/download.html) on your `PATH` (used for
cuts and export)
- An OpenAI API key (used server-side only, for Whisper transcription)
@@ -16,9 +20,9 @@ transcription and word classification, never for storage.
## macOS quick start
[`scripts/setup-mac.sh`](https://github.com/amide-init/transcriptcut/blob/main/scripts/setup-mac.sh)
-installs Node/pnpm/FFmpeg (with subtitle burn-in support) via Homebrew if
-you don't already have them, installs dependencies, and walks you through
-`.env`:
+installs Node/pnpm/Bun/FFmpeg (with subtitle burn-in support) via
+Homebrew if you don't already have them, installs dependencies for the
+whole workspace, and walks you through `.env`:
```bash
git clone https://github.com/amide-init/transcriptcut.git
@@ -26,10 +30,10 @@ cd transcriptcut
./scripts/setup-mac.sh
```
-Then:
+Then, from the repo root:
```bash
-cd client && pnpm run dev
+pnpm run dev
```
Safe to re-run — it only installs what's missing and never overwrites an
@@ -38,15 +42,16 @@ existing `.env`.
## Manual setup (any platform)
```bash
-cd client
-pnpm install
-cp .env.example .env # then fill in OPENAI_API_KEY
-pnpm exec prisma generate
-pnpm run dev
+pnpm install # installs deps for client + server
+cd server
+cp .env.example .env # then fill in OPENAI_API_KEY
+bunx prisma generate
+cd ..
+pnpm run dev # runs the Vite client and Bun backend together
```
-Open `http://localhost:3000`. See
-[`.env.example`](https://github.com/amide-init/transcriptcut/blob/main/client/.env.example)
+Open `http://localhost:5173`. See
+[`server/.env.example`](https://github.com/amide-init/transcriptcut/blob/main/server/.env.example)
for what each variable does.
## Your first edit
@@ -71,8 +76,7 @@ the AI does (and deliberately does not do).
## Testing
```bash
-cd client
-pnpm run test
+pnpm run test # runs both client and server test suites
```
Deterministic Vitest unit tests cover timeline cut/trim math,
@@ -85,9 +89,10 @@ same command on every PR.
```text
docs/ — this documentation site (VitePress)
claude.md — full product spec
-client/ — Next.js app (editor UI + API routes)
-server/ — reserved for future use, not in use yet
+client/ — Vite + React editor UI (SPA, no server rendering)
+server/ — Bun + Hono backend (API routes, FFmpeg, Prisma/SQLite, OpenAI)
```
-Everything currently runs inside `client/`, including the API routes —
-`server/` is reserved for a future split and isn't in use yet.
+The client and server run as separate processes, kept in one piece by
+`pnpm run dev` from the repo root. See [Architecture](/guide/architecture)
+for how they talk to each other.
diff --git a/docs/guide/security.md b/docs/guide/security.md
index 3c73dc3..ce25d49 100644
--- a/docs/guide/security.md
+++ b/docs/guide/security.md
@@ -41,10 +41,13 @@ behind a cloud API boundary.
- `.env` is gitignored and must never be committed — this is worth
double-checking by eye before every commit, not just trusted to
`.gitignore`.
-- Your LLM provider API key is used server-side only (in Next.js API
- route handlers). It's never sent to the browser.
+- Your LLM provider API key is used server-side only (in the Bun/Hono
+ backend). It's never sent to the browser.
- `.env.example` documents every variable without real values, so
contributors know what to fill in.
+- The macOS app doesn't bake a key into the build — each person enters
+ their own on first launch, stored only in that app's local data
+ directory. See [Download](/download).
## If you're contributing
diff --git a/docs/index.md b/docs/index.md
index b560e3b..1d1f8cd 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -13,6 +13,9 @@ hero:
alt: transcriptcut editor showing video, transcript, and timeline
actions:
- theme: brand
+ text: Download for macOS
+ link: /download
+ - theme: alt
text: Get Started
link: /guide/getting-started
- theme: alt
diff --git a/docs/status.md b/docs/status.md
index 1e27317..d9e1016 100644
--- a/docs/status.md
+++ b/docs/status.md
@@ -35,12 +35,15 @@ practice for several of these (see the issue tracker's closed bugs).
Local persistence (SQLite via Prisma) and project CRUD are built and in
use.
-## Not yet in use
+## Testing
-The separate `server/` directory is reserved for future use but isn't
-active — everything currently runs inside the `client/` Next.js app,
-including the API routes. There's no end-to-end/UI test suite yet (only
-deterministic Vitest unit tests).
+There's no end-to-end/UI test suite yet — only deterministic Vitest unit
+tests (client + server), run on every PR in CI.
+
+## macOS app
+
+A native `.app` build (Tauri) is also available, built and released via
+CI — see [Download](/download).
## No accounts
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8567a06
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "text2video",
+ "private": true,
+ "scripts": {
+ "dev": "concurrently -n client,server -c blue,green \"pnpm --filter client dev\" \"pnpm --filter server dev\"",
+ "build": "pnpm --filter client build && pnpm --filter server build",
+ "test": "pnpm --filter client test && pnpm --filter server test",
+ "lint": "pnpm --filter client lint"
+ },
+ "devDependencies": {
+ "concurrently": "^9.1.2"
+ },
+ "pnpm": {
+ "onlyBuiltDependencies": [
+ "@prisma/engines",
+ "better-sqlite3",
+ "prisma",
+ "protobufjs",
+ "unrs-resolver"
+ ]
+ }
+}
diff --git a/client/pnpm-lock.yaml b/pnpm-lock.yaml
similarity index 74%
rename from client/pnpm-lock.yaml
rename to pnpm-lock.yaml
index 80e82d4..1e46b57 100644
--- a/client/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,31 +7,22 @@ settings:
importers:
.:
+ devDependencies:
+ concurrently:
+ specifier: ^9.1.2
+ version: 9.2.4
+
+ client:
dependencies:
- '@prisma/adapter-better-sqlite3':
- specifier: ^7.10.0
- version: 7.10.0
- '@prisma/client':
- specifier: ^7.10.0
- version: 7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(better-sqlite3@13.0.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(typescript@5.9.3)
- better-sqlite3:
- specifier: ^13.0.3
- version: 13.0.3
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
cn:
specifier: ^0.3.0
- version: 0.3.0
+ version: 0.3.3
lucide-react:
specifier: ^1.46.0
- version: 1.46.0(react@19.2.8)
- next:
- specifier: 16.3.5
- version: 16.3.5(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- openai:
- specifier: ^6.49.0
- version: 6.49.0(zod@4.6.5)
+ version: 1.47.0(react@19.2.8)
radix-ui:
specifier: ^1.6.7
version: 1.6.7(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -41,58 +32,101 @@ importers:
react-dom:
specifier: 19.2.8
version: 19.2.8(react@19.2.8)
+ react-router-dom:
+ specifier: ^7.18.4
+ version: 7.18.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
shadcn:
specifier: ^4.21.0
- version: 4.21.0(@cfworker/json-schema@4.1.1)(typescript@5.9.3)
+ version: 4.21.0(typescript@5.9.3)
tw-animate-css:
specifier: ^1.4.0
version: 1.4.0
vaul:
specifier: ^1.1.2
version: 1.1.2(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
- zod:
- specifier: ^4.6.5
- version: 4.6.5
zustand:
specifier: ^5.0.15
version: 5.0.15(@types/react@19.3.0)(react@19.2.8)
devDependencies:
+ '@eslint/js':
+ specifier: ^9.39.5
+ version: 9.39.5
'@tailwindcss/postcss':
specifier: ^4
version: 4.3.3
- '@types/better-sqlite3':
- specifier: ^9.6.0
- version: 9.6.0
+ '@tauri-apps/cli':
+ specifier: ^2.11.5
+ version: 2.11.5
'@types/node':
- specifier: ^20
- version: 20.19.43
+ specifier: ^26.6.2
+ version: 26.6.2
'@types/react':
specifier: ^19
version: 19.3.0
'@types/react-dom':
specifier: ^19
version: 19.3.0(@types/react@19.3.0)
- dotenv:
- specifier: ^17.4.2
- version: 17.4.2
+ '@vitejs/plugin-react':
+ specifier: ^6.1.1
+ version: 6.1.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))
eslint:
specifier: ^9
version: 9.39.5(jiti@2.7.0)
- eslint-config-next:
- specifier: 16.3.5
- version: 16.3.5(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- prisma:
- specifier: ^7.10.0
- version: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(better-sqlite3@13.0.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3)
+ eslint-plugin-react-hooks:
+ specifier: ^7.1.1
+ version: 7.1.1(eslint@9.39.5(jiti@2.7.0))
+ eslint-plugin-react-refresh:
+ specifier: ^0.5.7
+ version: 0.5.7(eslint@9.39.5(jiti@2.7.0))
+ globals:
+ specifier: ^17.12.0
+ version: 17.12.0
tailwindcss:
specifier: ^4
version: 4.3.3
typescript:
specifier: ^5
version: 5.9.3
+ typescript-eslint:
+ specifier: ^8.70.1
+ version: 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ vite:
+ specifier: ^8.3.0
+ version: 8.3.0(@types/node@26.6.2)(jiti@2.7.0)
+ vitest:
+ specifier: ^5.0.1
+ version: 5.0.1(@types/node@26.6.2)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))
+
+ server:
+ dependencies:
+ '@prisma/adapter-libsql':
+ specifier: ^7.10.0
+ version: 7.10.0
+ '@prisma/client':
+ specifier: ^7.10.0
+ version: 7.10.0(prisma@7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(better-sqlite3@13.0.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3))(typescript@5.9.3)
+ hono:
+ specifier: ^4.9.10
+ version: 4.13.8
+ openai:
+ specifier: ^6.49.0
+ version: 6.49.0(ws@8.21.3)(zod@4.6.5)
+ zod:
+ specifier: ^4.6.5
+ version: 4.6.5
+ devDependencies:
+ '@types/bun':
+ specifier: ^1.2.0
+ version: 1.4.2
+ prisma:
+ specifier: ^7.10.0
+ version: 7.10.0(@types/react-dom@19.3.0(@types/react@19.3.0))(@types/react@19.3.0)(better-sqlite3@13.0.3)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@5.9.3)
+ typescript:
+ specifier: ^5
+ version: 5.9.3
vitest:
specifier: ^5.0.1
- version: 5.0.1(@types/node@20.19.43)(vite@8.3.0(@types/node@20.19.43)(jiti@2.7.0))
+ version: 5.0.1(@types/node@26.6.2)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))
packages:
@@ -182,8 +216,8 @@ packages:
resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.8':
- resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
+ '@babel/parser@7.29.9':
+ resolution: {integrity: sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -205,8 +239,8 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
- '@babel/plugin-transform-typescript@7.29.7':
- resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==}
+ '@babel/plugin-transform-typescript@7.29.9':
+ resolution: {integrity: sha512-FFwIwzU+7SCOuxxV4YtJql6T9981ZVTm+FHO5GhVsRqCTdy0WwrEZh3l42ARpXruJUDGOptdduHW6Zr7pNPLNg==}
engines: {node: '>=6.9.0'}
peerDependencies:
'@babel/core': ^7.0.0-0
@@ -229,9 +263,6 @@ packages:
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
engines: {node: '>=6.9.0'}
- '@cfworker/json-schema@4.1.1':
- resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
-
'@dotenvx/dotenvx@1.75.1':
resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==}
hasBin: true
@@ -253,30 +284,12 @@ packages:
'@electric-sql/pglite@0.4.3':
resolution: {integrity: sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==}
- '@emnapi/core@1.10.0':
- resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==}
-
- '@emnapi/runtime@1.10.0':
- resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
-
- '@emnapi/runtime@1.11.3':
- resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
-
- '@emnapi/wasi-threads@1.2.1':
- resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
-
'@eslint-community/eslint-utils@4.10.1':
resolution: {integrity: sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
- '@eslint-community/eslint-utils@4.9.1':
- resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
- engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- peerDependencies:
- eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
-
'@eslint-community/regexpp@4.12.2':
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -350,184 +363,79 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
- '@img/colour@1.1.0':
- resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
- engines: {node: '>=18'}
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
- '@img/sharp-darwin-arm64@0.35.4':
- resolution: {integrity: sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==}
- engines: {node: '>=20.9.0'}
- cpu: [arm64]
- os: [darwin]
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
- '@img/sharp-darwin-x64@0.35.4':
- resolution: {integrity: sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==}
- engines: {node: '>=20.9.0'}
- cpu: [x64]
- os: [darwin]
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
- '@img/sharp-freebsd-wasm32@0.35.4':
- resolution: {integrity: sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==}
- engines: {node: '>=20.9.0'}
- os: [freebsd]
+ '@jridgewell/sourcemap-codec@1.6.0':
+ resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
+
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
+ '@libsql/client@0.17.4':
+ resolution: {integrity: sha512-lYayFWasDV78A+TjlEhr6ubb3odBV6OHjb+wdp8VQcyWWAEIjuwbCHaraEUS4m4yWoo0BvZo96It4VdzZRmRWw==}
- '@img/sharp-libvips-darwin-arm64@1.3.3':
- resolution: {integrity: sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==}
+ '@libsql/core@0.17.4':
+ resolution: {integrity: sha512-LqF9gIvnJ38nmAH1y/ChizHqDO/MO1wLgA96XrraulEEbqXxLjleSH92YWTolbuJKgPUmGu4aJk9W3UnAcxLOQ==}
+
+ '@libsql/darwin-arm64@0.5.29':
+ resolution: {integrity: sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==}
cpu: [arm64]
os: [darwin]
- '@img/sharp-libvips-darwin-x64@1.3.3':
- resolution: {integrity: sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==}
+ '@libsql/darwin-x64@0.5.29':
+ resolution: {integrity: sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==}
cpu: [x64]
os: [darwin]
- '@img/sharp-libvips-linux-arm64@1.3.3':
- resolution: {integrity: sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
-
- '@img/sharp-libvips-linux-arm@1.3.3':
- resolution: {integrity: sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==}
- cpu: [arm]
- os: [linux]
- libc: [glibc]
-
- '@img/sharp-libvips-linux-ppc64@1.3.3':
- resolution: {integrity: sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==}
- cpu: [ppc64]
- os: [linux]
- libc: [glibc]
+ '@libsql/hrana-client@0.10.0':
+ resolution: {integrity: sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw==}
- '@img/sharp-libvips-linux-riscv64@1.3.3':
- resolution: {integrity: sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==}
- cpu: [riscv64]
- os: [linux]
- libc: [glibc]
+ '@libsql/isomorphic-ws@0.1.5':
+ resolution: {integrity: sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==}
- '@img/sharp-libvips-linux-s390x@1.3.3':
- resolution: {integrity: sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==}
- cpu: [s390x]
+ '@libsql/linux-arm-gnueabihf@0.5.29':
+ resolution: {integrity: sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==}
+ cpu: [arm]
os: [linux]
- libc: [glibc]
- '@img/sharp-libvips-linux-x64@1.3.3':
- resolution: {integrity: sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==}
- cpu: [x64]
+ '@libsql/linux-arm-musleabihf@0.5.29':
+ resolution: {integrity: sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==}
+ cpu: [arm]
os: [linux]
- libc: [glibc]
- '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
- resolution: {integrity: sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==}
+ '@libsql/linux-arm64-gnu@0.5.29':
+ resolution: {integrity: sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==}
cpu: [arm64]
os: [linux]
- libc: [musl]
-
- '@img/sharp-libvips-linuxmusl-x64@1.3.3':
- resolution: {integrity: sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==}
- cpu: [x64]
- os: [linux]
- libc: [musl]
- '@img/sharp-linux-arm64@0.35.4':
- resolution: {integrity: sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==}
- engines: {node: '>=20.9.0'}
+ '@libsql/linux-arm64-musl@0.5.29':
+ resolution: {integrity: sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==}
cpu: [arm64]
os: [linux]
- libc: [glibc]
-
- '@img/sharp-linux-arm@0.35.4':
- resolution: {integrity: sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==}
- engines: {node: '>=20.9.0'}
- cpu: [arm]
- os: [linux]
- libc: [glibc]
-
- '@img/sharp-linux-ppc64@0.35.4':
- resolution: {integrity: sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==}
- engines: {node: '>=20.9.0'}
- cpu: [ppc64]
- os: [linux]
- libc: [glibc]
-
- '@img/sharp-linux-riscv64@0.35.4':
- resolution: {integrity: sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==}
- engines: {node: '>=20.9.0'}
- cpu: [riscv64]
- os: [linux]
- libc: [glibc]
-
- '@img/sharp-linux-s390x@0.35.4':
- resolution: {integrity: sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==}
- engines: {node: '>=20.9.0'}
- cpu: [s390x]
- os: [linux]
- libc: [glibc]
- '@img/sharp-linux-x64@0.35.4':
- resolution: {integrity: sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==}
- engines: {node: '>=20.9.0'}
+ '@libsql/linux-x64-gnu@0.5.29':
+ resolution: {integrity: sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==}
cpu: [x64]
os: [linux]
- libc: [glibc]
-
- '@img/sharp-linuxmusl-arm64@0.35.4':
- resolution: {integrity: sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==}
- engines: {node: '>=20.9.0'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
- '@img/sharp-linuxmusl-x64@0.35.4':
- resolution: {integrity: sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==}
- engines: {node: '>=20.9.0'}
+ '@libsql/linux-x64-musl@0.5.29':
+ resolution: {integrity: sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==}
cpu: [x64]
os: [linux]
- libc: [musl]
-
- '@img/sharp-wasm32@0.35.4':
- resolution: {integrity: sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==}
- engines: {node: '>=20.9.0'}
-
- '@img/sharp-webcontainers-wasm32@0.35.4':
- resolution: {integrity: sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==}
- engines: {node: '>=20.9.0'}
- cpu: [wasm32]
-
- '@img/sharp-win32-arm64@0.35.4':
- resolution: {integrity: sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==}
- engines: {node: '>=20.9.0'}
- cpu: [arm64]
- os: [win32]
-
- '@img/sharp-win32-ia32@0.35.4':
- resolution: {integrity: sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==}
- engines: {node: ^20.9.0}
- cpu: [ia32]
- os: [win32]
- '@img/sharp-win32-x64@0.35.4':
- resolution: {integrity: sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==}
- engines: {node: '>=20.9.0'}
+ '@libsql/win32-x64-msvc@0.5.29':
+ resolution: {integrity: sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==}
cpu: [x64]
os: [win32]
- '@jridgewell/gen-mapping@0.3.13':
- resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
-
- '@jridgewell/remapping@2.3.5':
- resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
-
- '@jridgewell/resolve-uri@3.1.2':
- resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
- engines: {node: '>=6.0.0'}
-
- '@jridgewell/sourcemap-codec@1.6.0':
- resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==}
-
- '@jridgewell/trace-mapping@0.3.31':
- resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
-
'@modelcontextprotocol/sdk@1.30.0':
resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==}
engines: {node: '>=18'}
@@ -538,70 +446,8 @@ packages:
'@cfworker/json-schema':
optional: true
- '@napi-rs/wasm-runtime@1.2.4':
- resolution: {integrity: sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==}
- engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
- peerDependencies:
- '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
- '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
-
- '@next/env@16.3.5':
- resolution: {integrity: sha512-NWEXVDMqoEo0ktmU6u0sE2Vg0LOcsD7NnOTJNo3/fEaTfsg+F1bMIxuDmQbda4e3yTIQwVdUREF2yIuMOusKtg==}
-
- '@next/eslint-plugin-next@16.3.5':
- resolution: {integrity: sha512-PGfSeItHJ12DH8t+6sEbuMe59NE5rAhCfgk06QKTH2ne9VUL1JlaXdYXY3B8RaiF2SO50rDYvd39TulP6A6xZQ==}
-
- '@next/swc-darwin-arm64@16.3.5':
- resolution: {integrity: sha512-pMmGgETfKvElucLHtVaeiMRbp2zUbvKx7b1yGko0liBz3cw1mKSggWN/Rp/wPz8z+E1O82u3r4L1Co+ZS5hokQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [darwin]
-
- '@next/swc-darwin-x64@16.3.5':
- resolution: {integrity: sha512-76VaGYvf6HPa5/w12yLkE3dXTn9AfdEviI79oEL3aZoAmRLc9rWitjWqyjViVysK/ht/y9YKzFkBrUdi/wGkow==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [darwin]
-
- '@next/swc-linux-arm64-gnu@16.3.5':
- resolution: {integrity: sha512-zKDELJ5jSQMHeO/hmXUQsAzagX4bQD4OiMi3pQ5FbUj+yK506oLVHnKA2YXMlbg1EHHqJYtyePOgByIDXD1lqw==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
-
- '@next/swc-linux-arm64-musl@16.3.5':
- resolution: {integrity: sha512-7Vql0pgzCoHagv6+FNOZoqmJqA52c6zeVbhtS/47qFozO1MSx4ms7x7GHiciY8R5CDsSMKMQjJEryoJLcsBIbA==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
-
- '@next/swc-linux-x64-gnu@16.3.5':
- resolution: {integrity: sha512-NH/xzehyHEFWE2nlcZon7TB/0+H4shfWCi7S1zka815XCOhJDYZhoeJtOYy0dh0WVRWACVXSyGNFFytoMxUhRg==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
-
- '@next/swc-linux-x64-musl@16.3.5':
- resolution: {integrity: sha512-lV4+EhWMfS8jcC+EH2nn/Cm5cn6XsgbE07bU9tMH8fCo0tNAqhyzi1b5wQ/Tn6NGFTvKDY65w3ZH95EjwBRAnQ==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [linux]
- libc: [musl]
-
- '@next/swc-win32-arm64-msvc@16.3.5':
- resolution: {integrity: sha512-/wKzAREX2RF++MhicjDbg8tGn2AiBIM0+EFeTFKoUEUbW5D6amCJehd5Z5G1H5/gxNdgnwoXMcHz24H/c2tGkQ==}
- engines: {node: '>= 10'}
- cpu: [arm64]
- os: [win32]
-
- '@next/swc-win32-x64-msvc@16.3.5':
- resolution: {integrity: sha512-LNdCHzgLFc+UeqMS84LzXPaeBRKyqDN9OMyFAr1OrB0XrNw78IRrEVtZvvA7245W/HsaoeVOQX9jPjPk8jojwA==}
- engines: {node: '>= 10'}
- cpu: [x64]
- os: [win32]
+ '@neon-rs/load@0.0.4':
+ resolution: {integrity: sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==}
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -615,15 +461,11 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
- '@nolyfill/is-core-module@1.0.39':
- resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
- engines: {node: '>=12.4.0'}
-
- '@oxc-project/types@0.150.0':
- resolution: {integrity: sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw==}
+ '@oxc-project/types@0.151.0':
+ resolution: {integrity: sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==}
- '@prisma/adapter-better-sqlite3@7.10.0':
- resolution: {integrity: sha512-lqEUP9aTaJ0aDbIIrr/jmCqmkmmoAB5i7xeK6LLOTIxrOeK9KDLJDjtlqKKxAQulSwk5fNSshor8Az1QNZdI5Q==}
+ '@prisma/adapter-libsql@7.10.0':
+ resolution: {integrity: sha512-ANjUjJ/TrDQhR8jFU8HvYWRdwP17Lox1WL81zZvHcPVeFehZfNomAsPMNY24EBTLLZp0Xw4Na5Zf1S+EuKMKxw==}
'@prisma/client-runtime-utils@7.10.0':
resolution: {integrity: sha512-cnCy7lUV8/CctgKVEmqAbSLAmwqJdE/qAlqTBk/0NDk59zEb2cZ0M0M0E4vVPnqbSEYudRroQDvOWfUZH6RIfw==}
@@ -1449,98 +1291,98 @@ packages:
'@radix-ui/rect@1.1.3':
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
- '@rolldown/binding-android-arm-eabi@1.2.9':
- resolution: {integrity: sha512-tNISae1QEf/vkb3xkRcjV5SEdzPE97We5IVaa2Z8jSszQPZ8U60B/YCYpw4QI7VidYsBtKavczXf+DyDs9WGxw==}
+ '@rolldown/binding-android-arm-eabi@1.2.10':
+ resolution: {integrity: sha512-bp9svZb+QurZeh+8H4BhrZkifEB0YBNvTVzNSJnJQkj4NrRwmQoDUCGP0vSN7PbvLeM7l1tK6GXL8mrTiH2myg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [android]
- '@rolldown/binding-android-arm64@1.2.9':
- resolution: {integrity: sha512-YC8YsI30o606GTZi0VyzYlsDKFP8W61i/QzayHDkLbNEz/IShqAmTa+hsJRj13xTHA0H+6fk4b2UmGn+Q/cMlg==}
+ '@rolldown/binding-android-arm64@1.2.10':
+ resolution: {integrity: sha512-wm6Dld3RXUAZ/gRWKyUy+4W1B5CB5UeFaOzsSWJWEdxZXHH8rCYiZ5dGe6oJmhsunAPWzL7FZV+VtvmN5Ye2eA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [android]
- '@rolldown/binding-darwin-arm64@1.2.9':
- resolution: {integrity: sha512-IwhlH3qK5urrY8hZiEgGkHKEFN901p/p2bjxCxJlr4GyNnF7wYpUvK+Y43uaRYuC4hpfjzbR3SJC3arX1jGvmw==}
+ '@rolldown/binding-darwin-arm64@1.2.10':
+ resolution: {integrity: sha512-UbEfXq/AqGNgRTV3ik+X/iR6mUxu2QdYAadwRxJWquUGnW6gDqdP1FtLtFXRow7RJx0ssRwi80XAPr4r+4DtsA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [darwin]
- '@rolldown/binding-darwin-x64@1.2.9':
- resolution: {integrity: sha512-XxpJfVzFh+jilRxIXUqcfYAYcunIc/XEzIizsOL1fcJee5Sf7H3mH8WlLmfHfluz5amqR88QQo9izKtmMlavAw==}
+ '@rolldown/binding-darwin-x64@1.2.10':
+ resolution: {integrity: sha512-7f5h17q5KZVx/ji1vb8OTq31ch1O2I7K8NPIr44GkyWTApXMIsmhWqZfgpOH10xeauqghDAvGlZktasCkcF6Eg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [darwin]
- '@rolldown/binding-freebsd-x64@1.2.9':
- resolution: {integrity: sha512-kSfvhmgeWyfkbT3p/1s5vSgboogoah2zkm9fX2zjg2hHxSV7T4KhMWRUUaRk4OXNqoD3QAUeRqLcs1aZOK4U1g==}
+ '@rolldown/binding-freebsd-x64@1.2.10':
+ resolution: {integrity: sha512-ynOk/eEYhC6ZB2xCGvKrEOwE58oBy9LnrAqtkrDF9Fz1VTaNdGZTsV0VarJdhPwb+sOJTGjCLwcuyRJZ1dnMcQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [freebsd]
- '@rolldown/binding-linux-arm-gnueabihf@1.2.9':
- resolution: {integrity: sha512-1RVzG17pxqbTfYLC352JlLt6kKLG+6Hr30n8DlIJqsnV5luUDd2Qdx9Ayw1Cabfyb1K9k0jXEZ7evxkRoT+uiw==}
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.10':
+ resolution: {integrity: sha512-ERrAs185meZZhGan7a4l3RiiJK1ArSDlHdST++uvSxe+FDbR4TwUPahT/cbZJvaG6fIpDpF78surN+tX708Y4Q==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm]
os: [linux]
- '@rolldown/binding-linux-arm64-gnu@1.2.9':
- resolution: {integrity: sha512-BXqPvZ2drqVD+/Z8UpKwcs4Mp7grM+eGFku4CAEKrEtcbAsUpzREphK1sogCRZGreVPiMkiiBtw0n3TPteuqvw==}
+ '@rolldown/binding-linux-arm64-gnu@1.2.10':
+ resolution: {integrity: sha512-KN7OHKD0J3jy1UzBwZWPxpwhODf9IARUIJcrH+yLYKOcmegZ8luEUM38lDP1bDVj40yP6PsSzCqOJF76vljFnQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-arm64-musl@1.2.9':
- resolution: {integrity: sha512-11vWvo8YDwLzukt27J3aYDWU+gg2P7J+ZOmiJ0hkF5BXZDW7pVya7r40MXDy6ya0i9KamoENSVKIugvJNgFXIA==}
+ '@rolldown/binding-linux-arm64-musl@1.2.10':
+ resolution: {integrity: sha512-8l9wP8O+wa8zD6iw6egSfzVtu7oZVfH3hlUsMM4MwbLMhxleqeoXbZzjddyK3YyNlwLhqznq3tF7PkNJ8T/V2w==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@rolldown/binding-linux-ppc64-gnu@1.2.9':
- resolution: {integrity: sha512-a1tijMkdwsIARtc0F39ApURROkf3NwqinI6TOiSSWCTR7dT96dffNvMUtDHnq64wKNTIZOIlzKrFvvFUznJiyw==}
+ '@rolldown/binding-linux-ppc64-gnu@1.2.10':
+ resolution: {integrity: sha512-SeXNKeQzA5kLhz/J0CH6ZP0/HJ3v1xm/0YbiYpE0kK7emfRC2OIGGIaE14xzkISEGv2aYuUSpiLiU5Gbq+OI0A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [ppc64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-s390x-gnu@1.2.9':
- resolution: {integrity: sha512-x6SQNdAvv4c3hWqTMaWuawzMX9myaCs/yEmlGsxJzkdClnHW7FbrjQuSiRDhuSYzEYoEMhsaJy9qHG/XNemJPQ==}
+ '@rolldown/binding-linux-s390x-gnu@1.2.10':
+ resolution: {integrity: sha512-mtht0nR+y8/hart4175Ll15w7lY8dg7CtQ+j2FDNTsDRspOWTK/2V3l0aj9sIj7XmvqxT8Yli/wq22e7feTTWg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [s390x]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-x64-gnu@1.2.9':
- resolution: {integrity: sha512-9s0AZ8BFK5/n7B/TBoa2yJE3gI3KURrbXcPBlsAsvjU4VeJKgE90y1YtNxyEUIcHPQkg6/yfF3qihUrcM/Kf0Q==}
+ '@rolldown/binding-linux-x64-gnu@1.2.10':
+ resolution: {integrity: sha512-FSM94nGd55NYo48usCyM/nHfUKRnqc9+b0vJNuKV0oCCpIp/OGims7rO1Nv/DkFkt0S/s2rxsJ2kkS8J3HcpeA==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@rolldown/binding-linux-x64-musl@1.2.9':
- resolution: {integrity: sha512-P7VWAmV+WdJluH7ovnRGoiv2i8To7GAZ+kGzfGup635cyL7SyYl3lSUaA3Gp5THf0n/Co5EyEqb2zbqq+nMOHQ==}
+ '@rolldown/binding-linux-x64-musl@1.2.10':
+ resolution: {integrity: sha512-C3YxNB16myRLs7o+B+6PnQ6jBsdIS4+AE4Ah8glVGhDpEv9AOvxhZ/1duAb4B0UGczEK/lBbccksd8VI+p6zfw==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [linux]
libc: [musl]
- '@rolldown/binding-openharmony-arm64@1.2.9':
- resolution: {integrity: sha512-1qixtsE4BK8h+yS3BfmZ09UhA7O/N4IACva6YBr7EBvCJraByTuRcgOTaiA62Tm0vey3UcKXLOaoGHtYmNGEVg==}
+ '@rolldown/binding-openharmony-arm64@1.2.10':
+ resolution: {integrity: sha512-571TlE/F1eeTjjdjYAMMMPs1Mfv3MtX6s3+ZKVU6HiUjZ5Njc6c/qzNy/8K3zALTZnaw3JQVYrHxvNfjm43KAg==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [openharmony]
- '@rolldown/binding-win32-arm64-msvc@1.2.9':
- resolution: {integrity: sha512-ok8IQjcEPs1AKZfuEUznVBrJw+gK4soq+bx8b1X2XoMqVClarc1q5JDmVtWXY1xfr6ZuHTAsPXHTgTrqKTZeww==}
+ '@rolldown/binding-win32-arm64-msvc@1.2.10':
+ resolution: {integrity: sha512-QXW+ZWaiqs2c7Fi++D/SsW07LTPcUrncxcskJGfGNBoaLik1IU6fJymz4HsqwEO0u5Iq11yTO0B/mc4cPk7jrQ==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [arm64]
os: [win32]
- '@rolldown/binding-win32-x64-msvc@1.2.9':
- resolution: {integrity: sha512-Ip2mXoU0hM0boq3Rf+ekuT653OROSo6aSYcPT1VHE4q52KvyxgFkQgrgb/IEsxOuvQ2fZZbs8khJAyCEPM24/g==}
+ '@rolldown/binding-win32-x64-msvc@1.2.10':
+ resolution: {integrity: sha512-5FQFGgah17YeMtG1Yd5a+rMxQpTksyNXxRtKz06FVTaQw3RKYUJQbUoKk0/5jrXBpDo+7makNP7UHA2LQyH64A==}
engines: {node: ^20.19.0 || >=22.12.0}
cpu: [x64]
os: [win32]
@@ -1548,9 +1390,6 @@ packages:
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
- '@rtsao/scc@1.1.0':
- resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
-
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@@ -1561,9 +1400,6 @@ packages:
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
- '@swc/helpers@0.5.23':
- resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==}
-
'@tailwindcss/node@4.3.3':
resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
@@ -1656,14 +1492,87 @@ packages:
'@tailwindcss/postcss@4.3.3':
resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==}
+ '@tauri-apps/cli-darwin-arm64@2.11.5':
+ resolution: {integrity: sha512-tggOiVOohjIHdiElbjeBfB41s6cmfsq+ZQ0PX0fEtCdsDdZ9cCi54e5gNK7tdPbR6JbdDe7RA4kbQdyyBUUk/Q==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@tauri-apps/cli-darwin-x64@2.11.5':
+ resolution: {integrity: sha512-SrO+KCbqvG1IvVPdlzQX4GEGZRQDISrmDY9bGcW68DLVFBCC06H+Loz6oyUZETUxsSMR6B4oDfvbQVgD4hxsqQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@tauri-apps/cli-linux-arm-gnueabihf@2.11.5':
+ resolution: {integrity: sha512-qLrOaCa8hLD5QVPQRjAgOXvhlTFKXmTliVF773GgvADYcWZSdmncnF5zBFAkdu1uQK8KfQbDlBUwqQYZqBfBRQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm]
+ os: [linux]
+
+ '@tauri-apps/cli-linux-arm64-gnu@2.11.5':
+ resolution: {integrity: sha512-Tq32xpQjiQEdIrvwDq7lwHhXemn070CXWdbCvr9rdi9rsv9NFTbQ6izVSIfNE+59QaerxuwtUtZ5IxDNWaoYBw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tauri-apps/cli-linux-arm64-musl@2.11.5':
+ resolution: {integrity: sha512-+OdWKj5Aq+C8HXypQ4t926V8cpVnc0ZcJIdhTXWjVAJHkaNuo8EuQR6KRZKHWIbJJgKdMPCDosvwzIFaBBqHKQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@tauri-apps/cli-linux-riscv64-gnu@2.11.5':
+ resolution: {integrity: sha512-Q0V2hjpllyDW8dWQZSsITuQDfcY9BpBZuhXkMa2T5AFLZAn+FJPXQSyvnxWNVxRowdQ59Xn0QVtGm6HEHNUEvw==}
+ engines: {node: '>= 10'}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tauri-apps/cli-linux-x64-gnu@2.11.5':
+ resolution: {integrity: sha512-Xz1s87gFjZJZMQKAhLa77YeiCJM6yVog3f9OxDgVd/KxDIoB1dA5V2xOnMM+SRkOGk9juwIBqesVAjV0kUB0DA==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@tauri-apps/cli-linux-x64-musl@2.11.5':
+ resolution: {integrity: sha512-6A88wqAZXZHgMaBrGiy/Bpa4GgUnftXM7uquKOeOXCuK78/XdBxEECicK/1oHgrT179XBFOaA6lQEHysjRmGig==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@tauri-apps/cli-win32-arm64-msvc@2.11.5':
+ resolution: {integrity: sha512-sRGgF/ObRfLcS3PtUdyTbJtkGq3E+PNZTknvAURwMNUtHw9DU/BF6S5si43y9lS5yEO9bWJCA+GGYvnn6cphQw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@tauri-apps/cli-win32-ia32-msvc@2.11.5':
+ resolution: {integrity: sha512-bHOv/yxqfKpI7ioIFQi5wOfZ7jkbPzJD/UhXY52I/hrmE3qNh71cohIPyqZB5GQn3LZeJWdCUOqmIg/w6XX+Wg==}
+ engines: {node: '>= 10'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@tauri-apps/cli-win32-x64-msvc@2.11.5':
+ resolution: {integrity: sha512-vaaOmavqdCeS7oMib2banBZemgrC5v54tSkwoBV+bxrXQZhQZrOwE2EO2i4x+CHNtvQUM/6bSs3iymZMAsycWg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
+ '@tauri-apps/cli@2.11.5':
+ resolution: {integrity: sha512-YbeJ6tctNoo40purO4u3Eeo3RVqmsjJr0NoKQRu+4pvO4gMLzkjta7x0k96SxSLmx+rQ+8um1ThOlw+HLpgFug==}
+ engines: {node: '>= 10'}
+ hasBin: true
+
'@ts-morph/common@0.27.0':
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
- '@tybys/wasm-util@0.10.4':
- resolution: {integrity: sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==}
-
- '@types/better-sqlite3@9.6.0':
- resolution: {integrity: sha512-ZEEwBSgMu7GYJOynoagg5X9JbxfL6dTJDsgViJIqh67jV44kyOr9RXfmFjLK5rzC4MWssP06t9hu/JwGDnUbCg==}
+ '@types/bun@1.4.2':
+ resolution: {integrity: sha512-GimotNn7+ZV0uVArItBbriZsR1oNf0+WTzPkdcFrzShI7k2norL0uzEaJT8T33dWr7O/c9ZDuAFQrctKCi72oQ==}
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -1713,15 +1622,15 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/json5@0.0.29':
- resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
-
'@types/lodash@4.17.25':
resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==}
'@types/node@20.19.43':
resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==}
+ '@types/node@26.6.2':
+ resolution: {integrity: sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==}
+
'@types/react-dom@19.3.0':
resolution: {integrity: sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==}
peerDependencies:
@@ -1733,195 +1642,78 @@ packages:
'@types/validate-npm-package-name@4.0.2':
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
- '@typescript-eslint/eslint-plugin@8.70.0':
- resolution: {integrity: sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==}
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
+ '@typescript-eslint/eslint-plugin@8.70.1':
+ resolution: {integrity: sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.70.0
+ '@typescript-eslint/parser': ^8.70.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.70.0':
- resolution: {integrity: sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==}
+ '@typescript-eslint/parser@8.70.1':
+ resolution: {integrity: sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.70.0':
- resolution: {integrity: sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==}
+ '@typescript-eslint/project-service@8.70.1':
+ resolution: {integrity: sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/scope-manager@8.70.0':
- resolution: {integrity: sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==}
+ '@typescript-eslint/scope-manager@8.70.1':
+ resolution: {integrity: sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.70.0':
- resolution: {integrity: sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==}
+ '@typescript-eslint/tsconfig-utils@8.70.1':
+ resolution: {integrity: sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.70.0':
- resolution: {integrity: sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==}
+ '@typescript-eslint/type-utils@8.70.1':
+ resolution: {integrity: sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/types@8.70.0':
- resolution: {integrity: sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==}
+ '@typescript-eslint/types@8.70.1':
+ resolution: {integrity: sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.70.0':
- resolution: {integrity: sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==}
+ '@typescript-eslint/typescript-estree@8.70.1':
+ resolution: {integrity: sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.70.0':
- resolution: {integrity: sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==}
+ '@typescript-eslint/utils@8.70.1':
+ resolution: {integrity: sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/visitor-keys@8.70.0':
- resolution: {integrity: sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==}
+ '@typescript-eslint/visitor-keys@8.70.1':
+ resolution: {integrity: sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@unrs/resolver-binding-android-arm-eabi@1.12.2':
- resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==}
- cpu: [arm]
- os: [android]
-
- '@unrs/resolver-binding-android-arm64@1.12.2':
- resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==}
- cpu: [arm64]
- os: [android]
+ '@visx/curve@4.0.1-alpha.0':
+ resolution: {integrity: sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==}
- '@unrs/resolver-binding-darwin-arm64@1.12.2':
- resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==}
- cpu: [arm64]
- os: [darwin]
+ '@visx/event@4.0.1-alpha.0':
+ resolution: {integrity: sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==}
- '@unrs/resolver-binding-darwin-x64@1.12.2':
- resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==}
- cpu: [x64]
- os: [darwin]
-
- '@unrs/resolver-binding-freebsd-x64@1.12.2':
- resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==}
- cpu: [x64]
- os: [freebsd]
-
- '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
- resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==}
- cpu: [arm]
- os: [linux]
-
- '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
- resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==}
- cpu: [arm]
- os: [linux]
-
- '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
- resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==}
- cpu: [arm64]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
- resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==}
- cpu: [arm64]
- os: [linux]
- libc: [musl]
-
- '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
- resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==}
- cpu: [loong64]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
- resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==}
- cpu: [loong64]
- os: [linux]
- libc: [musl]
-
- '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
- resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==}
- cpu: [ppc64]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
- resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==}
- cpu: [riscv64]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
- resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==}
- cpu: [riscv64]
- os: [linux]
- libc: [musl]
-
- '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
- resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==}
- cpu: [s390x]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
- resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==}
- cpu: [x64]
- os: [linux]
- libc: [glibc]
-
- '@unrs/resolver-binding-linux-x64-musl@1.12.2':
- resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==}
- cpu: [x64]
- os: [linux]
- libc: [musl]
-
- '@unrs/resolver-binding-openharmony-arm64@1.12.2':
- resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==}
- cpu: [arm64]
- os: [openharmony]
-
- '@unrs/resolver-binding-wasm32-wasi@1.12.2':
- resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==}
- engines: {node: '>=14.0.0'}
- cpu: [wasm32]
-
- '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
- resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==}
- cpu: [arm64]
- os: [win32]
-
- '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
- resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==}
- cpu: [ia32]
- os: [win32]
-
- '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
- resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==}
- cpu: [x64]
- os: [win32]
-
- '@visx/curve@4.0.1-alpha.0':
- resolution: {integrity: sha512-jRu61Uz274pV1zyioXmboyrLutYbnKsgjj4njSGCnhdXj5GkZvZbg+ThDb6oOzoAnJOBRLz4rzPlWvNJOzuVMg==}
-
- '@visx/event@4.0.1-alpha.0':
- resolution: {integrity: sha512-EQqCMSv/s8NbFjo+hz3FKsvvYfP+2QslsFJ/24/O5l/W+7UC6J6aAvO0ujVwrTwdYbuQ+vhxKi1xdPdKR/qj1g==}
-
- '@visx/grid@4.0.1-alpha.0':
- resolution: {integrity: sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==}
- peerDependencies:
- react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0
+ '@visx/grid@4.0.1-alpha.0':
+ resolution: {integrity: sha512-rycutGmTHO+znNdPumheWMglm7YfpffvRwUkVy5zy4WoORIuKTMkDxwnOzHG2xMxU3EE/YCd37xFV5AxA30yeg==}
+ peerDependencies:
+ react: ^16.14.0 || ^17.0.0-0 || ^18.0.0-0 || ^19.0.0-0
'@visx/group@4.0.1-alpha.0':
resolution: {integrity: sha512-V19l7iQ7jccBv8kao/EByuI6o4xtxzzLV9nqVI1hRvmdzTVsuLpqlwzYCZUXJaTVvUWf8s4D2SQFjGkj/Nw+0w==}
@@ -1947,6 +1739,22 @@ packages:
'@visx/vendor@4.0.0-alpha.0':
resolution: {integrity: sha512-6I+MuqXBcv9jnlcVowHoHKSdk9gXTWkHLKyqBwRWg7LY6A3Ei8SHfubpqGV5rBUSppxMq2RszPJUS6w+H0YgmQ==}
+ '@vitejs/plugin-react@6.1.1':
+ resolution: {integrity: sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ peerDependencies:
+ '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0
+ babel-plugin-react-compiler: ^1.0.0
+ oxc-transform-react: ^0.145.0
+ vite: ^8.0.0
+ peerDependenciesMeta:
+ '@rolldown/plugin-babel':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ oxc-transform-react:
+ optional: true
+
'@vitest/mocker@5.0.1':
resolution: {integrity: sha512-6K1DoBNAPGvuOcSsGA4D6x+5zEEff/KmOOP3uetT2TrGpVfI+HRHRnJJfKi5ib/g1vx8IYHQD8s0pbJz8WQI7Q==}
peerDependencies:
@@ -2020,77 +1828,25 @@ packages:
resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==}
engines: {node: '>=10'}
- aria-query@5.3.2:
- resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
- engines: {node: '>= 0.4'}
-
- array-buffer-byte-length@1.0.2:
- resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
- engines: {node: '>= 0.4'}
-
- array-includes@3.2.0:
- resolution: {integrity: sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==}
- engines: {node: '>= 0.4'}
-
- array.prototype.findlast@1.2.5:
- resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
- engines: {node: '>= 0.4'}
-
- array.prototype.findlastindex@1.2.6:
- resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==}
- engines: {node: '>= 0.4'}
-
- array.prototype.flat@1.3.3:
- resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
- engines: {node: '>= 0.4'}
-
- array.prototype.flatmap@1.3.3:
- resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
- engines: {node: '>= 0.4'}
-
- array.prototype.tosorted@1.1.4:
- resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
- engines: {node: '>= 0.4'}
-
- arraybuffer.prototype.slice@1.0.4:
- resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
- engines: {node: '>= 0.4'}
-
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
- ast-types-flow@0.0.8:
- resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}
-
ast-types@0.16.3:
resolution: {integrity: sha512-FvWoWYfSCM6kRxCSH+MGLHIKKGRL6A6AW7Zek2O32REPQRdg131428uRTKMBYAeRd3XXAaHDS60Wpri7CdKDrA==}
engines: {node: '>=4'}
- async-function@1.0.0:
- resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
- engines: {node: '>= 0.4'}
+ async-mutex@0.5.0:
+ resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==}
atomically@1.7.0:
resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==}
engines: {node: '>=10.12.0'}
- available-typed-arrays@1.0.7:
- resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
- engines: {node: '>= 0.4'}
-
aws-ssl-profiles@1.1.2:
resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
engines: {node: '>= 6.0.0'}
- axe-core@4.13.0:
- resolution: {integrity: sha512-UzGt8zg7Ny8djbYMhxl2zuEevVa7r2gJjYY5Lwr1xM7+XU2nd6CkIWFTVcCIbAP63vSz71NaVyyuSk9lHKcy0A==}
- engines: {node: '>=4'}
-
- axobject-query@4.1.0:
- resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==}
- engines: {node: '>= 0.4'}
-
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -2098,31 +1854,18 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
- base64-js@1.5.1:
- resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
-
- baseline-browser-mapping@2.11.24:
- resolution: {integrity: sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==}
+ baseline-browser-mapping@2.11.25:
+ resolution: {integrity: sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==}
engines: {node: '>=6.0.0'}
hasBin: true
better-result@2.10.0:
resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==}
- better-sqlite3@12.11.1:
- resolution: {integrity: sha512-dq9AtApgg5PGFtBzPFSBl3HZQjHok5gaQCM6zh2Yk0aSmDCs1CbnVI8/HgASQkNKsWFpseIO9beg5xxpYhbIfA==}
- engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x || 26.x}
-
better-sqlite3@13.0.3:
resolution: {integrity: sha512-RbOBxmLBG8uvFUc15X9+9SFemKcQ0WBuISBVkpuiaUB2qblC8UWlHEjdWVoZ8AdhSwmoEgsiXKfopX0CQxaACQ==}
engines: {node: '>=22'}
- bindings@1.5.0:
- resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
-
- bl@4.1.0:
- resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
-
body-parser@2.3.0:
resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==}
engines: {node: '>=18'}
@@ -2143,8 +1886,8 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
- buffer@5.7.1:
- resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+ bun-types@1.4.2:
+ resolution: {integrity: sha512-bxV1FgK7yBIzjRe5zBozIM4Bem11ZJcCXSrjWRG3YWLt8yFDePu4cLjpebO8OvPeIE9trbyPF4fuj3Cia4Fj3w==}
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
@@ -2166,10 +1909,6 @@ packages:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
- call-bind@1.0.9:
- resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==}
- engines: {node: '>= 0.4'}
-
call-bound@1.0.4:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
@@ -2197,9 +1936,6 @@ packages:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'}
- chownr@1.1.4:
- resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
-
class-variance-authority@0.7.1:
resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==}
@@ -2214,8 +1950,9 @@ packages:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
- client-only@0.0.1:
- resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
@@ -2226,8 +1963,8 @@ packages:
engines: {node: '>=20'}
hasBin: true
- cn@0.3.0:
- resolution: {integrity: sha512-qRoU0ARrZD3Bbj1wKFftEKxPjSnzTEdFkf22NVhW+zU9D/wajhoP+qUZ5DChhhSOBCH1onV12hrmGqfu1+5Z2Q==}
+ cn@0.3.3:
+ resolution: {integrity: sha512-OEiyJkSpYm2kOuNOKQ+c2wmzZRG8orrb9fnObtHTbCGNx/E9KLnGcmcocItOzREKk5KzQN5I2e3BUCj563KEpw==}
engines: {node: '>=20'}
hasBin: true
@@ -2252,6 +1989,11 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ concurrently@9.2.4:
+ resolution: {integrity: sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
conf@10.2.0:
resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==}
engines: {node: '>=12'}
@@ -2285,6 +2027,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
+ cookie@1.1.1:
+ resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
+ engines: {node: '>=18'}
+
cors@2.8.6:
resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
engines: {node: '>= 0.10'}
@@ -2358,33 +2104,10 @@ packages:
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
engines: {node: '>=12'}
- damerau-levenshtein@1.0.8:
- resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
-
- data-view-buffer@1.0.2:
- resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
- engines: {node: '>= 0.4'}
-
- data-view-byte-length@1.0.2:
- resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
- engines: {node: '>= 0.4'}
-
- data-view-byte-offset@1.0.1:
- resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
- engines: {node: '>= 0.4'}
-
debounce-fn@4.0.0:
resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==}
engines: {node: '>=10'}
- debug@3.2.7:
- resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
- peerDependencies:
- supports-color: '*'
- peerDependenciesMeta:
- supports-color:
- optional: true
-
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -2394,10 +2117,6 @@ packages:
supports-color:
optional: true
- decompress-response@6.0.0:
- resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
- engines: {node: '>=10'}
-
dedent@1.7.2:
resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
peerDependencies:
@@ -2406,10 +2125,6 @@ packages:
babel-plugin-macros:
optional: true
- deep-extend@0.6.0:
- resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
- engines: {node: '>=4.0.0'}
-
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
@@ -2429,10 +2144,6 @@ packages:
resolution: {integrity: sha512-m1pAzaJgZ/gssEqlOhJkPJp8Xly7QyW6xcrkUa2KKcDeDSEMP7X8xipU3snUcfisTQx0w1AGae+9UtJSfVnXGw==}
engines: {node: '>=18'}
- define-data-property@1.1.4:
- resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
- engines: {node: '>= 0.4'}
-
define-lazy-prop@2.0.0:
resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==}
engines: {node: '>=8'}
@@ -2441,10 +2152,6 @@ packages:
resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
engines: {node: '>=12'}
- define-properties@1.2.1:
- resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
- engines: {node: '>= 0.4'}
-
defu@6.1.7:
resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
@@ -2462,6 +2169,10 @@ packages:
destr@2.0.5:
resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==}
+ detect-libc@2.0.2:
+ resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
+ engines: {node: '>=8'}
+
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
@@ -2473,10 +2184,6 @@ packages:
resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==}
engines: {node: '>=0.3.1'}
- doctrine@2.1.0:
- resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
- engines: {node: '>=0.10.0'}
-
dot-prop@6.0.1:
resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==}
engines: {node: '>=10'}
@@ -2495,8 +2202,8 @@ packages:
effect@3.20.0:
resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==}
- electron-to-chromium@1.5.429:
- resolution: {integrity: sha512-/1ENIE3cx4HTIx4IfPZFaOunJmsrSVTnj6coXoRVbiJUbkeTyFkJvBeWGkdgh08OhFbxYLMT1kbkwFVSarq6Ow==}
+ electron-to-chromium@1.5.438:
+ resolution: {integrity: sha512-AN9xMU1hJiT65LkCUPL1DZm5TCbOPb2Qsm5pYwFLEXp6/qj9SdT4yMiqs7Qqstwvkn1zF7l36SRGt+s6XcJ0FA==}
elkjs@0.11.1:
resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==}
@@ -2504,8 +2211,8 @@ packages:
emoji-regex@10.6.0:
resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
- emoji-regex@9.2.2:
- resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
empathic@2.0.0:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
@@ -2515,9 +2222,6 @@ packages:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
- end-of-stream@1.4.5:
- resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
-
enhanced-resolve@5.25.1:
resolution: {integrity: sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==}
engines: {node: '>=10.13.0'}
@@ -2537,14 +2241,6 @@ packages:
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
- es-abstract-get@1.0.0:
- resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==}
- engines: {node: '>= 0.4'}
-
- es-abstract@1.24.2:
- resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==}
- engines: {node: '>= 0.4'}
-
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -2553,10 +2249,6 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
- es-iterator-helpers@1.4.0:
- resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==}
- engines: {node: '>= 0.4'}
-
es-module-lexer@2.3.2:
resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==}
@@ -2564,18 +2256,6 @@ packages:
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
engines: {node: '>= 0.4'}
- es-set-tostringtag@2.1.0:
- resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
- engines: {node: '>= 0.4'}
-
- es-shim-unscopables@1.1.0:
- resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
- engines: {node: '>= 0.4'}
-
- es-to-primitive@1.3.4:
- resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==}
- engines: {node: '>= 0.4'}
-
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -2587,79 +2267,16 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
- eslint-config-next@16.3.5:
- resolution: {integrity: sha512-wPjq9MLQuWykHs8tsm2gH6OJThk6N27L8Te2JatlaGZ7jT1gtgGmJKukygL28xOlWsg5J1a1bGy/PvLyQC8OYA==}
- peerDependencies:
- eslint: '>=9.0.0'
- typescript: '>=3.3.1'
- peerDependenciesMeta:
- typescript:
- optional: true
-
- eslint-import-resolver-node@0.3.10:
- resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==}
-
- eslint-import-resolver-typescript@3.10.1:
- resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==}
- engines: {node: ^14.18.0 || >=16.0.0}
- peerDependencies:
- eslint: '*'
- eslint-plugin-import: '*'
- eslint-plugin-import-x: '*'
- peerDependenciesMeta:
- eslint-plugin-import:
- optional: true
- eslint-plugin-import-x:
- optional: true
-
- eslint-module-utils@2.14.0:
- resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: '*'
- eslint-import-resolver-node: '*'
- eslint-import-resolver-typescript: '*'
- eslint-import-resolver-webpack: '*'
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
- eslint:
- optional: true
- eslint-import-resolver-node:
- optional: true
- eslint-import-resolver-typescript:
- optional: true
- eslint-import-resolver-webpack:
- optional: true
-
- eslint-plugin-import@2.32.0:
- resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==}
- engines: {node: '>=4'}
- peerDependencies:
- '@typescript-eslint/parser': '*'
- eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9
- peerDependenciesMeta:
- '@typescript-eslint/parser':
- optional: true
-
- eslint-plugin-jsx-a11y@6.10.2:
- resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==}
- engines: {node: '>=4.0'}
- peerDependencies:
- eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9
-
eslint-plugin-react-hooks@7.1.1:
resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==}
engines: {node: '>=18'}
peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0
- eslint-plugin-react@7.37.5:
- resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
- engines: {node: '>=4'}
+ eslint-plugin-react-refresh@0.5.7:
+ resolution: {integrity: sha512-XhJSzLljuYD4UjNuFGJu2v7aD3sqNVK12w5/DCUfrNfHWegZo2+TsavNx8MuxMwEquuFAzNbNfXKU0WoZ+YUIg==}
peerDependencies:
- eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
+ eslint: ^9 || ^10
eslint-scope@8.4.0:
resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
@@ -2736,10 +2353,6 @@ packages:
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
engines: {node: ^18.19.0 || >=20.5.0}
- expand-template@2.0.3:
- resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
- engines: {node: '>=6'}
-
expect-type@1.4.0:
resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
engines: {node: '>=12.0.0'}
@@ -2767,10 +2380,6 @@ packages:
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
- fast-glob@3.3.1:
- resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
- engines: {node: '>=8.6.0'}
-
fast-glob@3.3.3:
resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
engines: {node: '>=8.6.0'}
@@ -2807,9 +2416,6 @@ packages:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
- file-uri-to-path@1.0.0:
- resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
-
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -2837,10 +2443,6 @@ packages:
flatted@3.4.4:
resolution: {integrity: sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==}
- for-each@0.3.5:
- resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
- engines: {node: '>= 0.4'}
-
foreground-child@3.3.1:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
@@ -2853,11 +2455,8 @@ packages:
resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
engines: {node: '>= 0.8'}
- fs-constants@1.0.0:
- resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
-
- fs-extra@11.4.0:
- resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==}
+ fs-extra@11.4.1:
+ resolution: {integrity: sha512-KYAb4c9BJQI6QqGKthV68OHe0badztdXJWKo0WtBA9IuCFPTKvE5ZdUBglP833aMjhaSPNO4A5j/EkzZtGlKjA==}
engines: {node: '>=14.14'}
fsevents@2.3.3:
@@ -2868,29 +2467,22 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
- function.prototype.name@1.2.0:
- resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==}
- engines: {node: '>= 0.4'}
-
- functions-have-names@1.2.3:
- resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
-
fuzzysort@3.1.0:
resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==}
generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
- generator-function@2.0.1:
- resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==}
- engines: {node: '>= 0.4'}
-
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
- get-east-asian-width@1.6.0:
- resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
+ get-east-asian-width@1.7.0:
+ resolution: {integrity: sha512-XjH1AECxf0giL2V1aU8vKyRR2ppRUb5c0EvT7zuJTokQ74bNo52zOtghqdWIqrhUD79fo3x0WfKZdOqxF6LG1Q==}
engines: {node: '>=18'}
get-intrinsic@1.3.0:
@@ -2920,20 +2512,10 @@ packages:
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
engines: {node: '>=18'}
- get-symbol-description@1.1.0:
- resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
- engines: {node: '>= 0.4'}
-
- get-tsconfig@4.14.3:
- resolution: {integrity: sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==}
-
giget@3.3.1:
resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==}
hasBin: true
- github-from-package@0.0.0:
- resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
-
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@@ -2946,14 +2528,10 @@ packages:
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
engines: {node: '>=18'}
- globals@16.4.0:
- resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==}
+ globals@17.12.0:
+ resolution: {integrity: sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==}
engines: {node: '>=18'}
- globalthis@1.0.4:
- resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
- engines: {node: '>= 0.4'}
-
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -2967,29 +2545,14 @@ packages:
graphmatch@1.1.1:
resolution: {integrity: sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==}
- has-bigints@1.1.0:
- resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
- engines: {node: '>= 0.4'}
-
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
- has-property-descriptors@1.0.2:
- resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
-
- has-proto@1.2.0:
- resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
- engines: {node: '>= 0.4'}
-
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
- has-tostringtag@1.0.2:
- resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
- engines: {node: '>= 0.4'}
-
hasown@2.0.4:
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'}
@@ -3020,15 +2583,12 @@ packages:
resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
engines: {node: '>=0.10.0'}
- ieee754@1.2.1:
- resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
-
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
- ignore@7.0.9:
- resolution: {integrity: sha512-brTTsvFRt5C1gGHtPst/281UjPD5t9fBqbgoMPlVWy11ZLTPfu7HxK4ZYqO9H7o/yC9rSTCI85EaQ4OoY12qYw==}
+ ignore@7.0.10:
+ resolution: {integrity: sha512-HpbUakT7xp5miBUywCHf36ZEuAJNklBJDDsGpUIjMzOSmM8ELSfA9Sa/QDPeNeqeoN31u+UTCkL4klCOVvRm4Q==}
engines: {node: '>= 4'}
import-fresh@3.3.1:
@@ -3042,13 +2602,6 @@ packages:
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
- ini@1.3.8:
- resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
-
- internal-slot@1.1.0:
- resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
- engines: {node: '>= 0.4'}
-
internmap@2.0.3:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
@@ -3061,44 +2614,9 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
- is-array-buffer@3.0.5:
- resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
- engines: {node: '>= 0.4'}
-
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
- is-async-function@2.1.1:
- resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
- engines: {node: '>= 0.4'}
-
- is-bigint@1.1.0:
- resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
- engines: {node: '>= 0.4'}
-
- is-boolean-object@1.2.2:
- resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
- engines: {node: '>= 0.4'}
-
- is-bun-module@2.0.0:
- resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==}
-
- is-callable@1.2.7:
- resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
- engines: {node: '>= 0.4'}
-
- is-core-module@2.16.2:
- resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==}
- engines: {node: '>= 0.4'}
-
- is-data-view@1.0.2:
- resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
- engines: {node: '>= 0.4'}
-
- is-date-object@1.1.0:
- resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
- engines: {node: '>= 0.4'}
-
is-docker@2.2.1:
resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
engines: {node: '>=8'}
@@ -3109,21 +2627,13 @@ packages:
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
hasBin: true
- is-document.all@1.0.0:
- resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==}
- engines: {node: '>= 0.4'}
-
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
- is-finalizationregistry@1.1.1:
- resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
- engines: {node: '>= 0.4'}
-
- is-generator-function@1.1.2:
- resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==}
- engines: {node: '>= 0.4'}
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
@@ -3142,18 +2652,6 @@ packages:
resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
engines: {node: '>=12'}
- is-map@2.0.3:
- resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
- engines: {node: '>= 0.4'}
-
- is-negative-zero@2.0.3:
- resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
- engines: {node: '>= 0.4'}
-
- is-number-object@1.1.1:
- resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
- engines: {node: '>= 0.4'}
-
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@@ -3176,22 +2674,10 @@ packages:
is-property@1.0.2:
resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==}
- is-regex@1.2.1:
- resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
- engines: {node: '>= 0.4'}
-
is-regexp@3.1.0:
resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==}
engines: {node: '>=12'}
- is-set@2.0.3:
- resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
- engines: {node: '>= 0.4'}
-
- is-shared-array-buffer@1.0.4:
- resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
- engines: {node: '>= 0.4'}
-
is-stream@2.0.1:
resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
engines: {node: '>=8'}
@@ -3200,38 +2686,14 @@ packages:
resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==}
engines: {node: '>=18'}
- is-string@1.1.1:
- resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
- engines: {node: '>= 0.4'}
-
- is-symbol@1.1.1:
- resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
- engines: {node: '>= 0.4'}
-
- is-typed-array@1.1.15:
- resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
- engines: {node: '>= 0.4'}
-
- is-unicode-supported@1.3.0:
- resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
- engines: {node: '>=12'}
+ is-unicode-supported@1.3.0:
+ resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+ engines: {node: '>=12'}
is-unicode-supported@2.1.0:
resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
engines: {node: '>=18'}
- is-weakmap@2.0.2:
- resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
- engines: {node: '>= 0.4'}
-
- is-weakref@1.1.1:
- resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
- engines: {node: '>= 0.4'}
-
- is-weakset@2.0.4:
- resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
- engines: {node: '>= 0.4'}
-
is-wsl@2.2.0:
resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==}
engines: {node: '>=8'}
@@ -3240,9 +2702,6 @@ packages:
resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==}
engines: {node: '>=16'}
- isarray@2.0.5:
- resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
-
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
@@ -3250,10 +2709,6 @@ packages:
resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
engines: {node: '>=18'}
- iterator.prototype@1.1.5:
- resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
- engines: {node: '>= 0.4'}
-
jiti@2.7.0:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
@@ -3261,6 +2716,9 @@ packages:
jose@6.2.12:
resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==}
+ js-base64@3.9.4:
+ resolution: {integrity: sha512-PtOMXpEGuP0RRiRXsjzHzl44dMHxSu2CPvAhinupR1tBa88me+1DPqsobl7eupn5UukjLkln1ZnAOAOXOrYG0Q==}
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -3294,10 +2752,6 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
- json5@1.0.2:
- resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
- hasBin: true
-
json5@2.2.3:
resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
engines: {node: '>=6'}
@@ -3306,10 +2760,6 @@ packages:
jsonfile@6.2.1:
resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==}
- jsx-ast-utils@3.3.5:
- resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
- engines: {node: '>=4.0'}
-
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3321,17 +2771,15 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
- language-subtag-registry@0.3.23:
- resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==}
-
- language-tags@1.0.9:
- resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==}
- engines: {node: '>=0.10'}
-
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
+ libsql@0.5.29:
+ resolution: {integrity: sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==}
+ cpu: [x64, arm64, wasm32, arm]
+ os: [darwin, linux, win32]
+
lightningcss-android-arm64@1.32.0:
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
engines: {node: '>= 12.0.0'}
@@ -3504,10 +2952,6 @@ packages:
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
- loose-envify@1.4.0:
- resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
- hasBin: true
-
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
@@ -3515,16 +2959,16 @@ packages:
resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==}
engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
- lucide-react@1.46.0:
- resolution: {integrity: sha512-Bv+FZXgZPrxc/NCl1e7JJVQFLdiCxYgxNVhqoV7X0p6I8ADJo8DxBnK1auH0fZz4AmqOJ3jgneL4f1i8LJQRAA==}
+ lucide-react@1.47.0:
+ resolution: {integrity: sha512-o8C23aXpNQypRY73W7fW02EyvWJinEMXgKeGjFoKym0zj3Q73hD97A1IQps1g8C14HTGsOpZL0Am+xjfgFZAbg==}
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- magic-string@1.4.1:
- resolution: {integrity: sha512-8lyCu36ErXR0J9uaGKlKQoiLZKmtI63YGLE8G2o9jyRPdr4X47LusSOwgOJOzcVtp81fTAAjxR7BwKz682Jhow==}
+ magic-string@1.4.2:
+ resolution: {integrity: sha512-vG+rjFRj1PqdIBozIxAGMjPlOhaVe+GXpbttY/iSK7rGcJRMlwNJO7dcUwmUqkymsFLJiNGI06t4D7Fr7yRC9g==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
@@ -3569,10 +3013,6 @@ packages:
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
engines: {node: '>=18'}
- mimic-response@3.1.0:
- resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
- engines: {node: '>=10'}
-
minimatch@10.2.6:
resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==}
engines: {node: 18 || 20 || >=22}
@@ -3583,9 +3023,6 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
- mkdirp-classic@0.5.3:
- resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
-
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -3602,14 +3039,6 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- napi-build-utils@2.0.0:
- resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
-
- napi-postinstall@0.3.4:
- resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
- engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
- hasBin: true
-
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
@@ -3617,41 +3046,12 @@ packages:
resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==}
engines: {node: '>=18'}
- next@16.3.5:
- resolution: {integrity: sha512-MdtsTgzyfCPRLC6uJ1mN8ao7lyJ4BB0U6Inhnx3gta1UcCIdHK3yxLG0E8OWQteWD8/Q0qb8A5o7wJaL8M9y2w==}
- engines: {node: '>=20.9.0'}
- hasBin: true
- peerDependencies:
- '@opentelemetry/api': ^1.1.0
- '@playwright/test': ^1.51.1
- babel-plugin-react-compiler: '*'
- react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
- sass: ^1.3.0
- peerDependenciesMeta:
- '@opentelemetry/api':
- optional: true
- '@playwright/test':
- optional: true
- babel-plugin-react-compiler:
- optional: true
- sass:
- optional: true
-
- node-abi@3.96.0:
- resolution: {integrity: sha512-rebQ/lz7i0EkoLzUVSrKRzA69zMkwLp95kKMWoMDkkM00Suxz0D7zEQPwRml5fQum24mj7bPvmlgLAmu2JCiYg==}
- engines: {node: '>=10'}
-
node-addon-api@8.9.2:
resolution: {integrity: sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==}
engines: {node: ^18 || ^20 || >= 21}
- node-exports-info@1.6.2:
- resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==}
- engines: {node: '>= 0.4'}
-
- node-releases@2.0.55:
- resolution: {integrity: sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==}
+ node-releases@2.0.57:
+ resolution: {integrity: sha512-kQK9LGGFiHtrWiNhZtA7Qbw17AQz+dmsEKODRIVTXA9+e5MS/2gZEBhYJt13GrAz5/IOZKddH/0Z3TP/Zgo+yw==}
engines: {node: '>=18'}
npm-run-path@4.0.1:
@@ -3670,34 +3070,10 @@ packages:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
- object-keys@1.1.1:
- resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
- engines: {node: '>= 0.4'}
-
object-treeify@1.1.33:
resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==}
engines: {node: '>= 10'}
- object.assign@4.1.7:
- resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
- engines: {node: '>= 0.4'}
-
- object.entries@1.1.9:
- resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
- engines: {node: '>= 0.4'}
-
- object.fromentries@2.0.8:
- resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
- engines: {node: '>= 0.4'}
-
- object.groupby@1.0.3:
- resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==}
- engines: {node: '>= 0.4'}
-
- object.values@1.2.1:
- resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
- engines: {node: '>= 0.4'}
-
obug@2.2.1:
resolution: {integrity: sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==}
engines: {node: '>=12.20.0'}
@@ -3756,10 +3132,6 @@ packages:
resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
engines: {node: '>=18'}
- own-keys@1.0.2:
- resolution: {integrity: sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==}
- engines: {node: '>= 0.4'}
-
p-limit@2.3.0:
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
engines: {node: '>=6'}
@@ -3815,9 +3187,6 @@ packages:
resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==}
engines: {node: '>=12'}
- path-parse@1.0.7:
- resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
-
path-to-regexp@8.4.2:
resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==}
@@ -3849,18 +3218,10 @@ packages:
resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==}
engines: {node: '>=8'}
- possible-typed-array-names@1.1.0:
- resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
- engines: {node: '>= 0.4'}
-
postcss-selector-parser@7.1.6:
resolution: {integrity: sha512-7qASPzhKF2l2KLboRZux8CCTRMdGiV08vWmyKzPz22qZ7ZjQBOeY7rNzNoCLSUiftJ7HUq0GERHmxw/t0dCdMw==}
engines: {node: '>=4'}
- postcss@8.5.23:
- resolution: {integrity: sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==}
- engines: {node: ^10 || ^12 || >=14}
-
postcss@8.5.28:
resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
@@ -3877,12 +3238,6 @@ packages:
resolution: {integrity: sha512-C+y9x90UElAddDZmV4qOx9W53B61PO7cIqWz2dQsWlwswuq4mr8NEwytdGKboYbQlGZ3awrkTeNvcZiZNHnQ8A==}
engines: {node: '>=20'}
- prebuild-install@7.1.3:
- resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
- engines: {node: '>=10'}
- deprecated: No longer maintained. Please contact the author of the relevant native addon; alternatives are available.
- hasBin: true
-
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -3904,13 +3259,13 @@ packages:
typescript:
optional: true
+ promise-limit@2.7.0:
+ resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
+
prompts@2.4.2:
resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
engines: {node: '>= 6'}
- prop-types@15.8.1:
- resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
-
proper-lockfile@4.1.2:
resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==}
@@ -3918,9 +3273,6 @@ packages:
resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==}
engines: {node: '>= 0.10'}
- pump@3.0.4:
- resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -3959,18 +3311,11 @@ packages:
rc9@3.1.0:
resolution: {integrity: sha512-ufjkNVzbRHKcCOmTahZkmVsyc3W+MSk3jY03m+a7tGHkIsdVMG9l10/3HvFbWkkKzY5VFp3pkRsIo/UYgmFL7Q==}
- rc@1.2.8:
- resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
- hasBin: true
-
react-dom@19.2.8:
resolution: {integrity: sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==}
peerDependencies:
react: ^19.2.8
- react-is@16.13.1:
- resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
-
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@@ -3991,6 +3336,23 @@ packages:
'@types/react':
optional: true
+ react-router-dom@7.18.4:
+ resolution: {integrity: sha512-yrfmJHIpDG7taCpqKjT1G5B6q3O2K+RN8/fgNf0lTjCwiPbQ0ei6vXX9ZjQR+7ld8Tr7Z5xmyMnZ8YJrphWQUw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
+ react-router@7.18.4:
+ resolution: {integrity: sha512-PUPQcMhMGRAslLcvtlPz/kmzBEWPhLdgLFrL7pLNepBL6dX0lWj4WD2cUYVgYCuT3jxvghYFg81cDTj44DhetQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+ peerDependenciesMeta:
+ react-dom:
+ optional: true
+
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -4005,10 +3367,6 @@ packages:
resolution: {integrity: sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==}
engines: {node: '>=0.10.0'}
- readable-stream@3.6.2:
- resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
- engines: {node: '>= 6'}
-
readdirp@5.1.1:
resolution: {integrity: sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==}
engines: {node: '>= 20.19.0'}
@@ -4017,17 +3375,13 @@ packages:
resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==}
engines: {node: '>= 4'}
- reflect.getprototypeof@1.0.10:
- resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
- engines: {node: '>= 0.4'}
-
- regexp.prototype.flags@1.5.4:
- resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
- engines: {node: '>= 0.4'}
-
remeda@2.33.4:
resolution: {integrity: sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==}
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
@@ -4036,14 +3390,6 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
- resolve-pkg-maps@1.0.0:
- resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
-
- resolve@2.0.0-next.7:
- resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==}
- engines: {node: '>= 0.4'}
- hasBin: true
-
restore-cursor@5.1.0:
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
engines: {node: '>=18'}
@@ -4063,8 +3409,8 @@ packages:
robust-predicates@3.0.3:
resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==}
- rolldown@1.2.9:
- resolution: {integrity: sha512-hx/Pv0N1haXRb11qkfnK5MXB/iqr7i0yjWQqmO9uHqZpBgQSqzc8UsSnEpalsh+j1I8qQ2CkXAkJC8Br3dKSlg==}
+ rolldown@1.2.10:
+ resolution: {integrity: sha512-OxkA08pSryMK7B3XiFA09B4OJ1xJMPgIYCBMY2xchzpqgBGsV1o0DetPAE+Sl3N3L4oCPiEzmHVSOj7iR04Zog==}
engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
@@ -4079,20 +3425,8 @@ packages:
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
- safe-array-concat@1.1.4:
- resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==}
- engines: {node: '>=0.4'}
-
- safe-buffer@5.2.1:
- resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
-
- safe-push-apply@1.0.0:
- resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
- engines: {node: '>= 0.4'}
-
- safe-regex-test@1.1.0:
- resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
- engines: {node: '>= 0.4'}
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
safe-regex2@5.1.1:
resolution: {integrity: sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==}
@@ -4124,17 +3458,8 @@ packages:
resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
engines: {node: '>= 18'}
- set-function-length@1.2.2:
- resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
- engines: {node: '>= 0.4'}
-
- set-function-name@2.0.2:
- resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
- engines: {node: '>= 0.4'}
-
- set-proto@1.0.0:
- resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
- engines: {node: '>= 0.4'}
+ set-cookie-parser@2.7.2:
+ resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
@@ -4144,15 +3469,6 @@ packages:
engines: {node: '>=20.18.1'}
hasBin: true
- sharp@0.35.4:
- resolution: {integrity: sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==}
- engines: {node: '>=20.9.0'}
- peerDependencies:
- '@types/node': '*'
- peerDependenciesMeta:
- '@types/node':
- optional: true
-
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -4161,6 +3477,10 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.9.0:
+ resolution: {integrity: sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==}
+ engines: {node: '>= 0.4'}
+
side-channel-list@1.0.1:
resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==}
engines: {node: '>= 0.4'}
@@ -4187,12 +3507,6 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
- simple-concat@1.0.1:
- resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
-
- simple-get@4.0.1:
- resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
-
sisteransi@1.0.5:
resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
@@ -4216,9 +3530,6 @@ packages:
resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==}
engines: {node: '>= 0.6'}
- stable-hash@0.0.5:
- resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==}
-
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -4236,40 +3547,14 @@ packages:
resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
engines: {node: '>=18'}
- stop-iteration-iterator@1.1.0:
- resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
- engines: {node: '>= 0.4'}
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
string-width@7.2.0:
resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
engines: {node: '>=18'}
- string.prototype.includes@2.0.1:
- resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==}
- engines: {node: '>= 0.4'}
-
- string.prototype.matchall@4.1.0:
- resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==}
- engines: {node: '>= 0.4'}
-
- string.prototype.repeat@1.0.0:
- resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
-
- string.prototype.trim@1.2.11:
- resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==}
- engines: {node: '>= 0.4'}
-
- string.prototype.trimend@1.0.10:
- resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==}
- engines: {node: '>= 0.4'}
-
- string.prototype.trimstart@1.0.8:
- resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
- engines: {node: '>= 0.4'}
-
- string_decoder@1.3.0:
- resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
-
stringify-object@5.0.0:
resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==}
engines: {node: '>=14.16'}
@@ -4294,37 +3579,20 @@ packages:
resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==}
engines: {node: '>=18'}
- strip-json-comments@2.0.1:
- resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
- engines: {node: '>=0.10.0'}
-
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
- styled-jsx@5.1.6:
- resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
- engines: {node: '>= 12.0.0'}
- peerDependencies:
- '@babel/core': '*'
- babel-plugin-macros: '*'
- react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0'
- peerDependenciesMeta:
- '@babel/core':
- optional: true
- babel-plugin-macros:
- optional: true
-
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
- supports-preserve-symlinks-flag@1.0.0:
- resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
- engines: {node: '>= 0.4'}
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
- systeminformation@5.33.10:
- resolution: {integrity: sha512-/NXbMVASt2UbSVgmWto4aBTIAeuYY7zOELpZybmNxqpsrbo5uYHpuEPf5nkyrPng0uG5YOb+Yv6s0FyKY4mDQg==}
+ systeminformation@5.33.13:
+ resolution: {integrity: sha512-GtyakkchTQKymM1WeAB652zi3rM1q00RKdc+jZe9KGstuc/mWUwAnQGN/RE5gVYvHIR8xYOUIUD9irVKDtA0wA==}
engines: {node: '>=10.0.0'}
os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
hasBin: true
@@ -4336,13 +3604,6 @@ packages:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
- tar-fs@2.1.5:
- resolution: {integrity: sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==}
-
- tar-stream@2.2.0:
- resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
- engines: {node: '>=6'}
-
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -4366,6 +3627,10 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
ts-api-utils@2.5.0:
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
@@ -4375,9 +3640,6 @@ packages:
ts-morph@26.0.0:
resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==}
- tsconfig-paths@3.15.0:
- resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==}
-
tsconfig-paths@4.2.0:
resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
engines: {node: '>=6'}
@@ -4385,9 +3647,6 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
- tunnel-agent@0.6.0:
- resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
-
tw-animate-css@1.4.0:
resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==}
@@ -4399,24 +3658,8 @@ packages:
resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==}
engines: {node: '>= 18'}
- typed-array-buffer@1.0.3:
- resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
- engines: {node: '>= 0.4'}
-
- typed-array-byte-length@1.0.3:
- resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
- engines: {node: '>= 0.4'}
-
- typed-array-byte-offset@1.0.4:
- resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
- engines: {node: '>= 0.4'}
-
- typed-array-length@1.0.8:
- resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==}
- engines: {node: '>= 0.4'}
-
- typescript-eslint@8.70.0:
- resolution: {integrity: sha512-P/W5cz70/cQAuKfY3xwQMWWTV7BvJ0mAQmi+9mBcsVPaBUpd6Ohpa+fECv9rBFrQcig86jAiNBFNWUqnTjr4pw==}
+ typescript-eslint@8.70.1:
+ resolution: {integrity: sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -4427,13 +3670,12 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
- unbox-primitive@1.1.0:
- resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
- engines: {node: '>= 0.4'}
-
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ undici-types@8.9.0:
+ resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==}
+
undici@7.29.1:
resolution: {integrity: sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==}
engines: {node: '>=20.18.1'}
@@ -4450,9 +3692,6 @@ packages:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
- unrs-resolver@1.12.2:
- resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==}
-
update-browserslist-db@1.3.3:
resolution: {integrity: sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==}
hasBin: true
@@ -4591,22 +3830,6 @@ packages:
jsdom:
optional: true
- which-boxed-primitive@1.1.1:
- resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
- engines: {node: '>= 0.4'}
-
- which-builtin-type@1.2.1:
- resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
- engines: {node: '>= 0.4'}
-
- which-collection@1.0.2:
- resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
- engines: {node: '>= 0.4'}
-
- which-typed-array@1.1.22:
- resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==}
- engines: {node: '>= 0.4'}
-
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -4626,16 +3849,44 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ ws@8.21.3:
+ resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
wsl-utils@1.0.0:
resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==}
engines: {node: '>=20'}
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -4705,7 +3956,7 @@ snapshots:
'@babel/helper-compilation-targets': 7.29.7
'@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7)
'@babel/helpers': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/traverse': 7.29.8
'@babel/types': 7.29.8
@@ -4720,7 +3971,7 @@ snapshots:
'@babel/generator@7.29.8':
dependencies:
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -4809,7 +4060,7 @@ snapshots:
'@babel/template': 7.29.7
'@babel/types': 7.29.8
- '@babel/parser@7.29.8':
+ '@babel/parser@7.29.9':
dependencies:
'@babel/types': 7.29.8
@@ -4831,7 +4082,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)':
+ '@babel/plugin-transform-typescript@7.29.9(@babel/core@7.29.7)':
dependencies:
'@babel/core': 7.29.7
'@babel/helper-annotate-as-pure': 7.29.7
@@ -4849,14 +4100,14 @@ snapshots:
'@babel/helper-validator-option': 7.29.7
'@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7)
'@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7)
- '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7)
+ '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7)
transitivePeerDependencies:
- supports-color
'@babel/template@7.29.7':
dependencies:
'@babel/code-frame': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/types': 7.29.8
'@babel/traverse@7.29.8':
@@ -4864,7 +4115,7 @@ snapshots:
'@babel/code-frame': 7.29.7
'@babel/generator': 7.29.8
'@babel/helper-globals': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
'@babel/template': 7.29.7
'@babel/types': 7.29.8
debug: 4.4.3
@@ -4876,9 +4127,6 @@ snapshots:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
- '@cfworker/json-schema@4.1.1':
- optional: true
-
'@dotenvx/dotenvx@1.75.1':
dependencies:
'@dotenvx/primitives': 0.8.0
@@ -4893,7 +4141,7 @@ snapshots:
object-treeify: 1.1.33
open: 8.4.2
picomatch: 4.0.7
- systeminformation: 5.33.10
+ systeminformation: 5.33.13
undici: 7.29.1
which: 4.0.0
yocto-spinner: 1.2.2
@@ -4910,37 +4158,11 @@ snapshots:
'@electric-sql/pglite@0.4.3': {}
- '@emnapi/core@1.10.0':
- dependencies:
- '@emnapi/wasi-threads': 1.2.1
- tslib: 2.8.1
- optional: true
-
- '@emnapi/runtime@1.10.0':
- dependencies:
- tslib: 2.8.1
- optional: true
-
- '@emnapi/runtime@1.11.3':
- dependencies:
- tslib: 2.8.1
- optional: true
-
- '@emnapi/wasi-threads@1.2.1':
- dependencies:
- tslib: 2.8.1
- optional: true
-
'@eslint-community/eslint-utils@4.10.1(eslint@9.39.5(jiti@2.7.0))':
dependencies:
eslint: 9.39.5(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
- '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))':
- dependencies:
- eslint: 9.39.5(jiti@2.7.0)
- eslint-visitor-keys: 3.4.3
-
'@eslint-community/regexpp@4.12.2': {}
'@eslint/config-array@0.21.2':
@@ -5019,133 +4241,84 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
- '@img/colour@1.1.0':
- optional: true
-
- '@img/sharp-darwin-arm64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-darwin-arm64': 1.3.3
- optional: true
-
- '@img/sharp-darwin-x64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-darwin-x64': 1.3.3
- optional: true
-
- '@img/sharp-freebsd-wasm32@0.35.4':
+ '@jridgewell/gen-mapping@0.3.13':
dependencies:
- '@img/sharp-wasm32': 0.35.4
- optional: true
-
- '@img/sharp-libvips-darwin-arm64@1.3.3':
- optional: true
-
- '@img/sharp-libvips-darwin-x64@1.3.3':
- optional: true
+ '@jridgewell/sourcemap-codec': 1.6.0
+ '@jridgewell/trace-mapping': 0.3.31
- '@img/sharp-libvips-linux-arm64@1.3.3':
- optional: true
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
- '@img/sharp-libvips-linux-arm@1.3.3':
- optional: true
+ '@jridgewell/resolve-uri@3.1.2': {}
- '@img/sharp-libvips-linux-ppc64@1.3.3':
- optional: true
+ '@jridgewell/sourcemap-codec@1.6.0': {}
- '@img/sharp-libvips-linux-riscv64@1.3.3':
- optional: true
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.6.0
- '@img/sharp-libvips-linux-s390x@1.3.3':
- optional: true
+ '@libsql/client@0.17.4':
+ dependencies:
+ '@libsql/core': 0.17.4
+ '@libsql/hrana-client': 0.10.0
+ js-base64: 3.9.4
+ libsql: 0.5.29
+ promise-limit: 2.7.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
- '@img/sharp-libvips-linux-x64@1.3.3':
- optional: true
+ '@libsql/core@0.17.4':
+ dependencies:
+ js-base64: 3.9.4
- '@img/sharp-libvips-linuxmusl-arm64@1.3.3':
+ '@libsql/darwin-arm64@0.5.29':
optional: true
- '@img/sharp-libvips-linuxmusl-x64@1.3.3':
+ '@libsql/darwin-x64@0.5.29':
optional: true
- '@img/sharp-linux-arm64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-arm64': 1.3.3
- optional: true
+ '@libsql/hrana-client@0.10.0':
+ dependencies:
+ '@libsql/isomorphic-ws': 0.1.5
+ js-base64: 3.9.4
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
- '@img/sharp-linux-arm@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-arm': 1.3.3
- optional: true
+ '@libsql/isomorphic-ws@0.1.5':
+ dependencies:
+ '@types/ws': 8.18.1
+ ws: 8.21.3
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
- '@img/sharp-linux-ppc64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-ppc64': 1.3.3
+ '@libsql/linux-arm-gnueabihf@0.5.29':
optional: true
- '@img/sharp-linux-riscv64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-riscv64': 1.3.3
+ '@libsql/linux-arm-musleabihf@0.5.29':
optional: true
- '@img/sharp-linux-s390x@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-s390x': 1.3.3
+ '@libsql/linux-arm64-gnu@0.5.29':
optional: true
- '@img/sharp-linux-x64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linux-x64': 1.3.3
+ '@libsql/linux-arm64-musl@0.5.29':
optional: true
- '@img/sharp-linuxmusl-arm64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
+ '@libsql/linux-x64-gnu@0.5.29':
optional: true
- '@img/sharp-linuxmusl-x64@0.35.4':
- optionalDependencies:
- '@img/sharp-libvips-linuxmusl-x64': 1.3.3
+ '@libsql/linux-x64-musl@0.5.29':
optional: true
- '@img/sharp-wasm32@0.35.4':
- dependencies:
- '@emnapi/runtime': 1.11.3
+ '@libsql/win32-x64-msvc@0.5.29':
optional: true
- '@img/sharp-webcontainers-wasm32@0.35.4':
- dependencies:
- '@img/sharp-wasm32': 0.35.4
- optional: true
-
- '@img/sharp-win32-arm64@0.35.4':
- optional: true
-
- '@img/sharp-win32-ia32@0.35.4':
- optional: true
-
- '@img/sharp-win32-x64@0.35.4':
- optional: true
-
- '@jridgewell/gen-mapping@0.3.13':
- dependencies:
- '@jridgewell/sourcemap-codec': 1.6.0
- '@jridgewell/trace-mapping': 0.3.31
-
- '@jridgewell/remapping@2.3.5':
- dependencies:
- '@jridgewell/gen-mapping': 0.3.13
- '@jridgewell/trace-mapping': 0.3.31
-
- '@jridgewell/resolve-uri@3.1.2': {}
-
- '@jridgewell/sourcemap-codec@1.6.0': {}
-
- '@jridgewell/trace-mapping@0.3.31':
- dependencies:
- '@jridgewell/resolve-uri': 3.1.2
- '@jridgewell/sourcemap-codec': 1.6.0
-
- '@modelcontextprotocol/sdk@1.30.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)':
+ '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)':
dependencies:
'@hono/node-server': 2.1.1(hono@4.13.8)
ajv: 8.20.0
@@ -5164,50 +4337,10 @@ snapshots:
raw-body: 3.0.2
zod: 3.25.76
zod-to-json-schema: 3.25.2(zod@3.25.76)
- optionalDependencies:
- '@cfworker/json-schema': 4.1.1
transitivePeerDependencies:
- supports-color
- '@napi-rs/wasm-runtime@1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)':
- dependencies:
- '@emnapi/core': 1.10.0
- '@emnapi/runtime': 1.10.0
- '@tybys/wasm-util': 0.10.4
- optional: true
-
- '@next/env@16.3.5': {}
-
- '@next/eslint-plugin-next@16.3.5(eslint@9.39.5(jiti@2.7.0))':
- dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0))
- fast-glob: 3.3.1
- transitivePeerDependencies:
- - eslint
-
- '@next/swc-darwin-arm64@16.3.5':
- optional: true
-
- '@next/swc-darwin-x64@16.3.5':
- optional: true
-
- '@next/swc-linux-arm64-gnu@16.3.5':
- optional: true
-
- '@next/swc-linux-arm64-musl@16.3.5':
- optional: true
-
- '@next/swc-linux-x64-gnu@16.3.5':
- optional: true
-
- '@next/swc-linux-x64-musl@16.3.5':
- optional: true
-
- '@next/swc-win32-arm64-msvc@16.3.5':
- optional: true
-
- '@next/swc-win32-x64-msvc@16.3.5':
- optional: true
+ '@neon-rs/load@0.0.4': {}
'@nodelib/fs.scandir@2.1.5':
dependencies:
@@ -5221,14 +4354,16 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.3
- '@nolyfill/is-core-module@1.0.39': {}
+ '@oxc-project/types@0.151.0': {}
- '@oxc-project/types@0.150.0': {}
-
- '@prisma/adapter-better-sqlite3@7.10.0':
+ '@prisma/adapter-libsql@7.10.0':
dependencies:
+ '@libsql/client': 0.17.4
'@prisma/driver-adapter-utils': 7.10.0
- better-sqlite3: 12.11.1
+ async-mutex: 0.5.0
+ transitivePeerDependencies:
+ - bufferutil
+ - utf-8-validate
'@prisma/client-runtime-utils@7.10.0': {}
@@ -6130,65 +5265,59 @@ snapshots:
'@radix-ui/rect@1.1.3': {}
- '@rolldown/binding-android-arm-eabi@1.2.9':
+ '@rolldown/binding-android-arm-eabi@1.2.10':
optional: true
- '@rolldown/binding-android-arm64@1.2.9':
+ '@rolldown/binding-android-arm64@1.2.10':
optional: true
- '@rolldown/binding-darwin-arm64@1.2.9':
+ '@rolldown/binding-darwin-arm64@1.2.10':
optional: true
- '@rolldown/binding-darwin-x64@1.2.9':
+ '@rolldown/binding-darwin-x64@1.2.10':
optional: true
- '@rolldown/binding-freebsd-x64@1.2.9':
+ '@rolldown/binding-freebsd-x64@1.2.10':
optional: true
- '@rolldown/binding-linux-arm-gnueabihf@1.2.9':
+ '@rolldown/binding-linux-arm-gnueabihf@1.2.10':
optional: true
- '@rolldown/binding-linux-arm64-gnu@1.2.9':
+ '@rolldown/binding-linux-arm64-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-arm64-musl@1.2.9':
+ '@rolldown/binding-linux-arm64-musl@1.2.10':
optional: true
- '@rolldown/binding-linux-ppc64-gnu@1.2.9':
+ '@rolldown/binding-linux-ppc64-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-s390x-gnu@1.2.9':
+ '@rolldown/binding-linux-s390x-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-x64-gnu@1.2.9':
+ '@rolldown/binding-linux-x64-gnu@1.2.10':
optional: true
- '@rolldown/binding-linux-x64-musl@1.2.9':
+ '@rolldown/binding-linux-x64-musl@1.2.10':
optional: true
- '@rolldown/binding-openharmony-arm64@1.2.9':
+ '@rolldown/binding-openharmony-arm64@1.2.10':
optional: true
- '@rolldown/binding-win32-arm64-msvc@1.2.9':
+ '@rolldown/binding-win32-arm64-msvc@1.2.10':
optional: true
- '@rolldown/binding-win32-x64-msvc@1.2.9':
+ '@rolldown/binding-win32-x64-msvc@1.2.10':
optional: true
'@rolldown/pluginutils@1.0.1': {}
- '@rtsao/scc@1.1.0': {}
-
'@sec-ant/readable-stream@0.4.1': {}
'@sindresorhus/merge-streams@4.0.0': {}
'@standard-schema/spec@1.1.0': {}
- '@swc/helpers@0.5.23':
- dependencies:
- tslib: 2.8.1
-
'@tailwindcss/node@4.3.3':
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -6258,20 +5387,62 @@ snapshots:
postcss: 8.5.28
tailwindcss: 4.3.3
+ '@tauri-apps/cli-darwin-arm64@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-darwin-x64@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-arm-gnueabihf@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-arm64-gnu@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-arm64-musl@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-riscv64-gnu@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-x64-gnu@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-linux-x64-musl@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-win32-arm64-msvc@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-win32-ia32-msvc@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli-win32-x64-msvc@2.11.5':
+ optional: true
+
+ '@tauri-apps/cli@2.11.5':
+ optionalDependencies:
+ '@tauri-apps/cli-darwin-arm64': 2.11.5
+ '@tauri-apps/cli-darwin-x64': 2.11.5
+ '@tauri-apps/cli-linux-arm-gnueabihf': 2.11.5
+ '@tauri-apps/cli-linux-arm64-gnu': 2.11.5
+ '@tauri-apps/cli-linux-arm64-musl': 2.11.5
+ '@tauri-apps/cli-linux-riscv64-gnu': 2.11.5
+ '@tauri-apps/cli-linux-x64-gnu': 2.11.5
+ '@tauri-apps/cli-linux-x64-musl': 2.11.5
+ '@tauri-apps/cli-win32-arm64-msvc': 2.11.5
+ '@tauri-apps/cli-win32-ia32-msvc': 2.11.5
+ '@tauri-apps/cli-win32-x64-msvc': 2.11.5
+
'@ts-morph/common@0.27.0':
dependencies:
fast-glob: 3.3.3
minimatch: 10.2.6
path-browserify: 1.0.1
- '@tybys/wasm-util@0.10.4':
+ '@types/bun@1.4.2':
dependencies:
- tslib: 2.8.1
- optional: true
-
- '@types/better-sqlite3@9.6.0':
- dependencies:
- '@types/node': 20.19.43
+ bun-types: 1.4.2
'@types/chai@5.2.3':
dependencies:
@@ -6316,14 +5487,16 @@ snapshots:
'@types/json-schema@7.0.15': {}
- '@types/json5@0.0.29': {}
-
'@types/lodash@4.17.25': {}
'@types/node@20.19.43':
dependencies:
undici-types: 6.21.0
+ '@types/node@26.6.2':
+ dependencies:
+ undici-types: 8.9.0
+
'@types/react-dom@19.3.0(@types/react@19.3.0)':
dependencies:
'@types/react': 19.3.0
@@ -6334,57 +5507,61 @@ snapshots:
'@types/validate-npm-package-name@4.0.2': {}
- '@typescript-eslint/eslint-plugin@8.70.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 20.19.43
+
+ '@typescript-eslint/eslint-plugin@8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- '@typescript-eslint/scope-manager': 8.70.0
- '@typescript-eslint/type-utils': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- '@typescript-eslint/utils': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.70.0
+ '@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/type-utils': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.70.1
eslint: 9.39.5(jiti@2.7.0)
- ignore: 7.0.9
+ ignore: 7.0.10
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
+ '@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/scope-manager': 8.70.0
- '@typescript-eslint/types': 8.70.0
- '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.70.0
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.70.1
debug: 4.4.3
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.70.0(typescript@5.9.3)':
+ '@typescript-eslint/project-service@8.70.1(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/types': 8.70.0
+ '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.70.1
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.70.0':
+ '@typescript-eslint/scope-manager@8.70.1':
dependencies:
- '@typescript-eslint/types': 8.70.0
- '@typescript-eslint/visitor-keys': 8.70.0
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/visitor-keys': 8.70.1
- '@typescript-eslint/tsconfig-utils@8.70.0(typescript@5.9.3)':
+ '@typescript-eslint/tsconfig-utils@8.70.1(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
- '@typescript-eslint/type-utils@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
+ '@typescript-eslint/type-utils@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/types': 8.70.0
- '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/utils': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
debug: 4.4.3
eslint: 9.39.5(jiti@2.7.0)
ts-api-utils: 2.5.0(typescript@5.9.3)
@@ -6392,14 +5569,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.70.0': {}
+ '@typescript-eslint/types@8.70.1': {}
- '@typescript-eslint/typescript-estree@8.70.0(typescript@5.9.3)':
+ '@typescript-eslint/typescript-estree@8.70.1(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/project-service': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/tsconfig-utils': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/types': 8.70.0
- '@typescript-eslint/visitor-keys': 8.70.0
+ '@typescript-eslint/project-service': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/visitor-keys': 8.70.1
debug: 4.4.3
minimatch: 10.2.6
semver: 7.8.5
@@ -6409,92 +5586,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
+ '@typescript-eslint/utils@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
- '@typescript-eslint/scope-manager': 8.70.0
- '@typescript-eslint/types': 8.70.0
- '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.70.1
+ '@typescript-eslint/types': 8.70.1
+ '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3)
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.70.0':
+ '@typescript-eslint/visitor-keys@8.70.1':
dependencies:
- '@typescript-eslint/types': 8.70.0
+ '@typescript-eslint/types': 8.70.1
eslint-visitor-keys: 5.0.1
- '@unrs/resolver-binding-android-arm-eabi@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-android-arm64@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-darwin-arm64@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-darwin-x64@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-freebsd-x64@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-arm64-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-arm64-musl@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-loong64-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-loong64-musl@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-riscv64-musl@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-s390x-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-x64-gnu@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-linux-x64-musl@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-openharmony-arm64@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-wasm32-wasi@1.12.2':
- dependencies:
- '@emnapi/core': 1.10.0
- '@emnapi/runtime': 1.10.0
- '@napi-rs/wasm-runtime': 1.2.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
- optional: true
-
- '@unrs/resolver-binding-win32-arm64-msvc@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-win32-ia32-msvc@1.12.2':
- optional: true
-
- '@unrs/resolver-binding-win32-x64-msvc@1.12.2':
- optional: true
-
'@visx/curve@4.0.1-alpha.0':
dependencies:
'@visx/vendor': 4.0.0-alpha.0
@@ -6572,14 +5679,19 @@ snapshots:
d3-time-format: 4.1.0
internmap: 2.0.3
- '@vitest/mocker@5.0.1(vite@8.3.0(@types/node@20.19.43)(jiti@2.7.0))':
+ '@vitejs/plugin-react@6.1.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))':
+ dependencies:
+ '@rolldown/pluginutils': 1.0.1
+ vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0)
+
+ '@vitest/mocker@5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))':
dependencies:
'@jridgewell/trace-mapping': 0.3.31
'@vitest/spy': 5.0.1
estree-walker: 3.0.3
- magic-string: 1.4.1
+ magic-string: 1.4.2
optionalDependencies:
- vite: 8.3.0(@types/node@20.19.43)(jiti@2.7.0)
+ vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0)
'@vitest/spy@5.0.1': {}
@@ -6632,125 +5744,32 @@ snapshots:
dependencies:
tslib: 2.8.1
- aria-query@5.3.2: {}
-
- array-buffer-byte-length@1.0.2:
- dependencies:
- call-bound: 1.0.4
- is-array-buffer: 3.0.5
-
- array-includes@3.2.0:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-object-atoms: 1.1.2
- es-shim-unscopables: 1.1.0
- is-string: 1.1.1
- math-intrinsics: 1.1.0
-
- array.prototype.findlast@1.2.5:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- es-shim-unscopables: 1.1.0
-
- array.prototype.findlastindex@1.2.6:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- es-shim-unscopables: 1.1.0
-
- array.prototype.flat@1.3.3:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-shim-unscopables: 1.1.0
-
- array.prototype.flatmap@1.3.3:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-shim-unscopables: 1.1.0
-
- array.prototype.tosorted@1.1.4:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-shim-unscopables: 1.1.0
-
- arraybuffer.prototype.slice@1.0.4:
- dependencies:
- array-buffer-byte-length: 1.0.2
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- is-array-buffer: 3.0.5
-
assertion-error@2.0.1: {}
- ast-types-flow@0.0.8: {}
-
ast-types@0.16.3:
dependencies:
tslib: 2.8.1
- async-function@1.0.0: {}
+ async-mutex@0.5.0:
+ dependencies:
+ tslib: 2.8.1
atomically@1.7.0: {}
- available-typed-arrays@1.0.7:
- dependencies:
- possible-typed-array-names: 1.1.0
-
aws-ssl-profiles@1.1.2: {}
- axe-core@4.13.0: {}
-
- axobject-query@4.1.0: {}
-
balanced-match@1.0.2: {}
balanced-match@4.0.4: {}
- base64-js@1.5.1: {}
-
- baseline-browser-mapping@2.11.24: {}
+ baseline-browser-mapping@2.11.25: {}
better-result@2.10.0: {}
- better-sqlite3@12.11.1:
- dependencies:
- bindings: 1.5.0
- prebuild-install: 7.1.3
-
better-sqlite3@13.0.3:
dependencies:
node-addon-api: 8.9.2
-
- bindings@1.5.0:
- dependencies:
- file-uri-to-path: 1.0.0
-
- bl@4.1.0:
- dependencies:
- buffer: 5.7.1
- inherits: 2.0.4
- readable-stream: 3.6.2
+ optional: true
body-parser@2.3.0:
dependencies:
@@ -6781,16 +5800,15 @@ snapshots:
browserslist@4.29.0:
dependencies:
- baseline-browser-mapping: 2.11.24
+ baseline-browser-mapping: 2.11.25
caniuse-lite: 1.0.30001810
- electron-to-chromium: 1.5.429
- node-releases: 2.0.55
+ electron-to-chromium: 1.5.438
+ node-releases: 2.0.57
update-browserslist-db: 1.3.3(browserslist@4.29.0)
- buffer@5.7.1:
+ bun-types@1.4.2:
dependencies:
- base64-js: 1.5.1
- ieee754: 1.2.1
+ '@types/node': 20.19.43
bundle-name@4.1.0:
dependencies:
@@ -6818,13 +5836,6 @@ snapshots:
es-errors: 1.3.0
function-bind: 1.1.2
- call-bind@1.0.9:
- dependencies:
- call-bind-apply-helpers: 1.0.2
- es-define-property: 1.0.1
- get-intrinsic: 1.3.0
- set-function-length: 1.2.2
-
call-bound@1.0.4:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -6847,8 +5858,6 @@ snapshots:
dependencies:
readdirp: 5.1.1
- chownr@1.1.4: {}
-
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -6861,13 +5870,17 @@ snapshots:
cli-spinners@2.9.2: {}
- client-only@0.0.1: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
clsx@2.1.1: {}
cn@0.2.6: {}
- cn@0.3.0: {}
+ cn@0.3.3: {}
code-block-writer@13.0.3: {}
@@ -6883,6 +5896,15 @@ snapshots:
concat-map@0.0.1: {}
+ concurrently@9.2.4:
+ dependencies:
+ chalk: 4.1.2
+ rxjs: 7.8.2
+ shell-quote: 1.9.0
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+
conf@10.2.0:
dependencies:
ajv: 8.20.0
@@ -6912,6 +5934,8 @@ snapshots:
cookie@0.7.2: {}
+ cookie@1.1.1: {}
+
cors@2.8.6:
dependencies:
object-assign: 4.1.1
@@ -6982,46 +6006,16 @@ snapshots:
dependencies:
d3-array: 3.2.4
- damerau-levenshtein@1.0.8: {}
-
- data-view-buffer@1.0.2:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
- data-view-byte-length@1.0.2:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
- data-view-byte-offset@1.0.1:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-data-view: 1.0.2
-
debounce-fn@4.0.0:
dependencies:
mimic-fn: 3.1.0
- debug@3.2.7:
- dependencies:
- ms: 2.1.3
-
debug@4.4.3:
dependencies:
ms: 2.1.3
- decompress-response@6.0.0:
- dependencies:
- mimic-response: 3.1.0
-
dedent@1.7.2: {}
- deep-extend@0.6.0: {}
-
deep-is@0.1.4: {}
deepmerge-ts@7.1.5: {}
@@ -7035,22 +6029,10 @@ snapshots:
bundle-name: 4.1.0
default-browser-id: 5.0.1
- define-data-property@1.1.4:
- dependencies:
- es-define-property: 1.0.1
- es-errors: 1.3.0
- gopd: 1.2.0
-
define-lazy-prop@2.0.0: {}
define-lazy-prop@3.0.0: {}
- define-properties@1.2.1:
- dependencies:
- define-data-property: 1.1.4
- has-property-descriptors: 1.0.2
- object-keys: 1.1.1
-
defu@6.1.7: {}
delaunator@5.1.0:
@@ -7063,16 +6045,14 @@ snapshots:
destr@2.0.5: {}
+ detect-libc@2.0.2: {}
+
detect-libc@2.1.2: {}
detect-node-es@1.1.0: {}
diff@8.0.4: {}
- doctrine@2.1.0:
- dependencies:
- esutils: 2.0.3
-
dot-prop@6.0.1:
dependencies:
is-obj: 2.0.0
@@ -7092,22 +6072,18 @@ snapshots:
'@standard-schema/spec': 1.1.0
fast-check: 3.23.2
- electron-to-chromium@1.5.429: {}
+ electron-to-chromium@1.5.438: {}
elkjs@0.11.1: {}
emoji-regex@10.6.0: {}
- emoji-regex@9.2.2: {}
+ emoji-regex@8.0.0: {}
empathic@2.0.0: {}
encodeurl@2.0.0: {}
- end-of-stream@1.4.5:
- dependencies:
- once: 1.4.0
-
enhanced-resolve@5.25.1:
dependencies:
graceful-fs: 4.2.11
@@ -7126,231 +6102,26 @@ snapshots:
dependencies:
is-arrayish: 0.2.1
- es-abstract-get@1.0.0:
- dependencies:
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- is-callable: 1.2.7
- object-inspect: 1.13.4
-
- es-abstract@1.24.2:
- dependencies:
- array-buffer-byte-length: 1.0.2
- arraybuffer.prototype.slice: 1.0.4
- available-typed-arrays: 1.0.7
- call-bind: 1.0.9
- call-bound: 1.0.4
- data-view-buffer: 1.0.2
- data-view-byte-length: 1.0.2
- data-view-byte-offset: 1.0.1
- es-define-property: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- es-set-tostringtag: 2.1.0
- es-to-primitive: 1.3.4
- function.prototype.name: 1.2.0
- get-intrinsic: 1.3.0
- get-proto: 1.0.1
- get-symbol-description: 1.1.0
- globalthis: 1.0.4
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
- has-proto: 1.2.0
- has-symbols: 1.1.0
- hasown: 2.0.4
- internal-slot: 1.1.0
- is-array-buffer: 3.0.5
- is-callable: 1.2.7
- is-data-view: 1.0.2
- is-negative-zero: 2.0.3
- is-regex: 1.2.1
- is-set: 2.0.3
- is-shared-array-buffer: 1.0.4
- is-string: 1.1.1
- is-typed-array: 1.1.15
- is-weakref: 1.1.1
- math-intrinsics: 1.1.0
- object-inspect: 1.13.4
- object-keys: 1.1.1
- object.assign: 4.1.7
- own-keys: 1.0.2
- regexp.prototype.flags: 1.5.4
- safe-array-concat: 1.1.4
- safe-push-apply: 1.0.0
- safe-regex-test: 1.1.0
- set-proto: 1.0.0
- stop-iteration-iterator: 1.1.0
- string.prototype.trim: 1.2.11
- string.prototype.trimend: 1.0.10
- string.prototype.trimstart: 1.0.8
- typed-array-buffer: 1.0.3
- typed-array-byte-length: 1.0.3
- typed-array-byte-offset: 1.0.4
- typed-array-length: 1.0.8
- unbox-primitive: 1.1.0
- which-typed-array: 1.1.22
-
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
- es-iterator-helpers@1.4.0:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-set-tostringtag: 2.1.0
- function-bind: 1.1.2
- get-intrinsic: 1.3.0
- globalthis: 1.0.4
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
- has-proto: 1.2.0
- has-symbols: 1.1.0
- internal-slot: 1.1.0
- iterator.prototype: 1.1.5
- math-intrinsics: 1.1.0
-
es-module-lexer@2.3.2: {}
es-object-atoms@1.1.2:
dependencies:
es-errors: 1.3.0
- es-set-tostringtag@2.1.0:
- dependencies:
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
- has-tostringtag: 1.0.2
- hasown: 2.0.4
-
- es-shim-unscopables@1.1.0:
- dependencies:
- hasown: 2.0.4
-
- es-to-primitive@1.3.4:
- dependencies:
- es-abstract-get: 1.0.0
- es-define-property: 1.0.1
- es-errors: 1.3.0
- is-callable: 1.2.7
- is-date-object: 1.1.0
- is-symbol: 1.1.1
-
escalade@3.2.0: {}
escape-html@1.0.3: {}
escape-string-regexp@4.0.0: {}
- eslint-config-next@16.3.5(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3):
- dependencies:
- '@next/eslint-plugin-next': 16.3.5(eslint@9.39.5(jiti@2.7.0))
- eslint: 9.39.5(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0))
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0))
- eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.5(jiti@2.7.0))
- eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0))
- eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0))
- globals: 16.4.0
- typescript-eslint: 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- optionalDependencies:
- typescript: 5.9.3
- transitivePeerDependencies:
- - '@typescript-eslint/parser'
- - eslint-import-resolver-webpack
- - eslint-plugin-import-x
- - supports-color
-
- eslint-import-resolver-node@0.3.10:
- dependencies:
- debug: 3.2.7
- is-core-module: 2.16.2
- resolve: 2.0.0-next.7
- transitivePeerDependencies:
- - supports-color
-
- eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)):
- dependencies:
- '@nolyfill/is-core-module': 1.0.39
- debug: 4.4.3
- eslint: 9.39.5(jiti@2.7.0)
- get-tsconfig: 4.14.3
- is-bun-module: 2.0.0
- stable-hash: 0.0.5
- tinyglobby: 0.2.17
- unrs-resolver: 1.12.2
- optionalDependencies:
- eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0))
- transitivePeerDependencies:
- - supports-color
-
- eslint-module-utils@2.14.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)):
- dependencies:
- debug: 3.2.7
- optionalDependencies:
- '@typescript-eslint/parser': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- eslint: 9.39.5(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0))
- transitivePeerDependencies:
- - supports-color
-
- eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)):
- dependencies:
- '@rtsao/scc': 1.1.0
- array-includes: 3.2.0
- array.prototype.findlastindex: 1.2.6
- array.prototype.flat: 1.3.3
- array.prototype.flatmap: 1.3.3
- debug: 3.2.7
- doctrine: 2.1.0
- eslint: 9.39.5(jiti@2.7.0)
- eslint-import-resolver-node: 0.3.10
- eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0))
- hasown: 2.0.4
- is-core-module: 2.16.2
- is-glob: 4.0.3
- minimatch: 3.1.5
- object.fromentries: 2.0.8
- object.groupby: 1.0.3
- object.values: 1.2.1
- semver: 6.3.1
- string.prototype.trimend: 1.0.10
- tsconfig-paths: 3.15.0
- optionalDependencies:
- '@typescript-eslint/parser': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- transitivePeerDependencies:
- - eslint-import-resolver-typescript
- - eslint-import-resolver-webpack
- - supports-color
-
- eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.5(jiti@2.7.0)):
- dependencies:
- aria-query: 5.3.2
- array-includes: 3.2.0
- array.prototype.flatmap: 1.3.3
- ast-types-flow: 0.0.8
- axe-core: 4.13.0
- axobject-query: 4.1.0
- damerau-levenshtein: 1.0.8
- emoji-regex: 9.2.2
- eslint: 9.39.5(jiti@2.7.0)
- hasown: 2.0.4
- jsx-ast-utils: 3.3.5
- language-tags: 1.0.9
- minimatch: 3.1.5
- object.fromentries: 2.0.8
- safe-regex-test: 1.1.0
- string.prototype.includes: 2.0.1
-
eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)):
dependencies:
'@babel/core': 7.29.7
- '@babel/parser': 7.29.8
+ '@babel/parser': 7.29.9
eslint: 9.39.5(jiti@2.7.0)
hermes-parser: 0.25.1
zod: 4.6.5
@@ -7358,27 +6129,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)):
+ eslint-plugin-react-refresh@0.5.7(eslint@9.39.5(jiti@2.7.0)):
dependencies:
- array-includes: 3.2.0
- array.prototype.findlast: 1.2.5
- array.prototype.flatmap: 1.3.3
- array.prototype.tosorted: 1.1.4
- doctrine: 2.1.0
- es-iterator-helpers: 1.4.0
eslint: 9.39.5(jiti@2.7.0)
- estraverse: 5.3.0
- hasown: 2.0.4
- jsx-ast-utils: 3.3.5
- minimatch: 3.1.5
- object.entries: 1.1.9
- object.fromentries: 2.0.8
- object.values: 1.2.1
- prop-types: 15.8.1
- resolve: 2.0.0-next.7
- semver: 6.3.1
- string.prototype.matchall: 4.1.0
- string.prototype.repeat: 1.0.0
eslint-scope@8.4.0:
dependencies:
@@ -7491,8 +6244,6 @@ snapshots:
strip-final-newline: 4.0.0
yoctocolors: 2.2.0
- expand-template@2.0.3: {}
-
expect-type@1.4.0: {}
express-rate-limit@8.7.0(express@5.2.1):
@@ -7546,14 +6297,6 @@ snapshots:
fast-deep-equal@3.1.3: {}
- fast-glob@3.3.1:
- dependencies:
- '@nodelib/fs.stat': 2.0.5
- '@nodelib/fs.walk': 1.2.8
- glob-parent: 5.1.2
- merge2: 1.4.1
- micromatch: 4.0.8
-
fast-glob@3.3.3:
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -7588,8 +6331,6 @@ snapshots:
dependencies:
flat-cache: 4.0.1
- file-uri-to-path@1.0.0: {}
-
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -7627,10 +6368,6 @@ snapshots:
flatted@3.4.4: {}
- for-each@0.3.5:
- dependencies:
- is-callable: 1.2.7
-
foreground-child@3.3.1:
dependencies:
cross-spawn: 7.0.6
@@ -7640,9 +6377,7 @@ snapshots:
fresh@2.0.0: {}
- fs-constants@1.0.0: {}
-
- fs-extra@11.4.0:
+ fs-extra@11.4.1:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
@@ -7653,31 +6388,17 @@ snapshots:
function-bind@1.1.2: {}
- function.prototype.name@1.2.0:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- es-define-property: 1.0.1
- es-errors: 1.3.0
- functions-have-names: 1.2.3
- has-property-descriptors: 1.0.2
- hasown: 2.0.4
- is-callable: 1.2.7
- is-document.all: 1.0.0
-
- functions-have-names@1.2.3: {}
-
fuzzysort@3.1.0: {}
generate-function@2.3.1:
dependencies:
is-property: 1.0.2
- generator-function@2.0.1: {}
-
gensync@1.0.0-beta.2: {}
- get-east-asian-width@1.6.0: {}
+ get-caller-file@2.0.5: {}
+
+ get-east-asian-width@1.7.0: {}
get-intrinsic@1.3.0:
dependencies:
@@ -7710,20 +6431,8 @@ snapshots:
'@sec-ant/readable-stream': 0.4.1
is-stream: 4.0.1
- get-symbol-description@1.1.0:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- get-intrinsic: 1.3.0
-
- get-tsconfig@4.14.3:
- dependencies:
- resolve-pkg-maps: 1.0.0
-
giget@3.3.1: {}
- github-from-package@0.0.0: {}
-
glob-parent@5.1.2:
dependencies:
is-glob: 4.0.3
@@ -7734,12 +6443,7 @@ snapshots:
globals@14.0.0: {}
- globals@16.4.0: {}
-
- globalthis@1.0.4:
- dependencies:
- define-properties: 1.2.1
- gopd: 1.2.0
+ globals@17.12.0: {}
gopd@1.2.0: {}
@@ -7749,24 +6453,10 @@ snapshots:
graphmatch@1.1.1: {}
- has-bigints@1.1.0: {}
-
has-flag@4.0.0: {}
- has-property-descriptors@1.0.2:
- dependencies:
- es-define-property: 1.0.1
-
- has-proto@1.2.0:
- dependencies:
- dunder-proto: 1.0.1
-
has-symbols@1.1.0: {}
- has-tostringtag@1.0.2:
- dependencies:
- has-symbols: 1.1.0
-
hasown@2.0.4:
dependencies:
function-bind: 1.1.2
@@ -7795,11 +6485,9 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- ieee754@1.2.1: {}
-
ignore@5.3.2: {}
- ignore@7.0.9: {}
+ ignore@7.0.10: {}
import-fresh@3.3.1:
dependencies:
@@ -7810,87 +6498,21 @@ snapshots:
inherits@2.0.4: {}
- ini@1.3.8: {}
-
- internal-slot@1.1.0:
- dependencies:
- es-errors: 1.3.0
- hasown: 2.0.4
- side-channel: 1.1.1
-
internmap@2.0.3: {}
ip-address@10.7.2: {}
ipaddr.js@1.9.1: {}
- is-array-buffer@3.0.5:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
-
is-arrayish@0.2.1: {}
- is-async-function@2.1.1:
- dependencies:
- async-function: 1.0.0
- call-bound: 1.0.4
- get-proto: 1.0.1
- has-tostringtag: 1.0.2
- safe-regex-test: 1.1.0
-
- is-bigint@1.1.0:
- dependencies:
- has-bigints: 1.1.0
-
- is-boolean-object@1.2.2:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
- is-bun-module@2.0.0:
- dependencies:
- semver: 7.8.5
-
- is-callable@1.2.7: {}
-
- is-core-module@2.16.2:
- dependencies:
- hasown: 2.0.4
-
- is-data-view@1.0.2:
- dependencies:
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
- is-typed-array: 1.1.15
-
- is-date-object@1.1.0:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
is-docker@2.2.1: {}
is-docker@3.0.0: {}
- is-document.all@1.0.0:
- dependencies:
- call-bound: 1.0.4
-
is-extglob@2.1.1: {}
- is-finalizationregistry@1.1.1:
- dependencies:
- call-bound: 1.0.4
-
- is-generator-function@1.1.2:
- dependencies:
- call-bound: 1.0.4
- generator-function: 2.0.1
- get-proto: 1.0.1
- has-tostringtag: 1.0.2
- safe-regex-test: 1.1.0
+ is-fullwidth-code-point@3.0.0: {}
is-glob@4.0.3:
dependencies:
@@ -7904,15 +6526,6 @@ snapshots:
is-interactive@2.0.0: {}
- is-map@2.0.3: {}
-
- is-negative-zero@2.0.3: {}
-
- is-number-object@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
is-number@7.0.0: {}
is-obj@2.0.0: {}
@@ -7925,54 +6538,15 @@ snapshots:
is-property@1.0.2: {}
- is-regex@1.2.1:
- dependencies:
- call-bound: 1.0.4
- gopd: 1.2.0
- has-tostringtag: 1.0.2
- hasown: 2.0.4
-
- is-regexp@3.1.0: {}
-
- is-set@2.0.3: {}
-
- is-shared-array-buffer@1.0.4:
- dependencies:
- call-bound: 1.0.4
-
- is-stream@2.0.1: {}
-
- is-stream@4.0.1: {}
-
- is-string@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
- is-symbol@1.1.1:
- dependencies:
- call-bound: 1.0.4
- has-symbols: 1.1.0
- safe-regex-test: 1.1.0
-
- is-typed-array@1.1.15:
- dependencies:
- which-typed-array: 1.1.22
-
- is-unicode-supported@1.3.0: {}
+ is-regexp@3.1.0: {}
- is-unicode-supported@2.1.0: {}
+ is-stream@2.0.1: {}
- is-weakmap@2.0.2: {}
+ is-stream@4.0.1: {}
- is-weakref@1.1.1:
- dependencies:
- call-bound: 1.0.4
+ is-unicode-supported@1.3.0: {}
- is-weakset@2.0.4:
- dependencies:
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
+ is-unicode-supported@2.1.0: {}
is-wsl@2.2.0:
dependencies:
@@ -7982,25 +6556,16 @@ snapshots:
dependencies:
is-inside-container: 1.0.0
- isarray@2.0.5: {}
-
isexe@2.0.0: {}
isexe@3.1.5: {}
- iterator.prototype@1.1.5:
- dependencies:
- define-data-property: 1.1.4
- es-object-atoms: 1.1.2
- get-intrinsic: 1.3.0
- get-proto: 1.0.1
- has-symbols: 1.1.0
- set-function-name: 2.0.2
-
jiti@2.7.0: {}
jose@6.2.12: {}
+ js-base64@3.9.4: {}
+
js-tokens@4.0.0: {}
js-yaml@4.3.2:
@@ -8023,10 +6588,6 @@ snapshots:
json-stable-stringify-without-jsonify@1.0.1: {}
- json5@1.0.2:
- dependencies:
- minimist: 1.2.8
-
json5@2.2.3: {}
jsonfile@6.2.1:
@@ -8035,13 +6596,6 @@ snapshots:
optionalDependencies:
graceful-fs: 4.2.11
- jsx-ast-utils@3.3.5:
- dependencies:
- array-includes: 3.2.0
- array.prototype.flat: 1.3.3
- object.assign: 4.1.7
- object.values: 1.2.1
-
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -8050,17 +6604,26 @@ snapshots:
kleur@4.1.5: {}
- language-subtag-registry@0.3.23: {}
-
- language-tags@1.0.9:
- dependencies:
- language-subtag-registry: 0.3.23
-
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
type-check: 0.4.0
+ libsql@0.5.29:
+ dependencies:
+ '@neon-rs/load': 0.0.4
+ detect-libc: 2.0.2
+ optionalDependencies:
+ '@libsql/darwin-arm64': 0.5.29
+ '@libsql/darwin-x64': 0.5.29
+ '@libsql/linux-arm-gnueabihf': 0.5.29
+ '@libsql/linux-arm-musleabihf': 0.5.29
+ '@libsql/linux-arm64-gnu': 0.5.29
+ '@libsql/linux-arm64-musl': 0.5.29
+ '@libsql/linux-x64-gnu': 0.5.29
+ '@libsql/linux-x64-musl': 0.5.29
+ '@libsql/win32-x64-msvc': 0.5.29
+
lightningcss-android-arm64@1.32.0:
optional: true
@@ -8181,17 +6744,13 @@ snapshots:
long@5.3.2: {}
- loose-envify@1.4.0:
- dependencies:
- js-tokens: 4.0.0
-
lru-cache@5.1.1:
dependencies:
yallist: 3.1.1
lru.min@1.1.5: {}
- lucide-react@1.46.0(react@19.2.8):
+ lucide-react@1.47.0(react@19.2.8):
dependencies:
react: 19.2.8
@@ -8199,7 +6758,7 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
- magic-string@1.4.1:
+ magic-string@1.4.2:
dependencies:
'@jridgewell/sourcemap-codec': 1.6.0
@@ -8230,8 +6789,6 @@ snapshots:
mimic-function@5.0.1: {}
- mimic-response@3.1.0: {}
-
minimatch@10.2.6:
dependencies:
brace-expansion: 5.0.12
@@ -8242,8 +6799,6 @@ snapshots:
minimist@1.2.8: {}
- mkdirp-classic@0.5.3: {}
-
ms@2.1.3: {}
mysql2@3.15.3:
@@ -8264,55 +6819,16 @@ snapshots:
nanoid@3.3.19: {}
- napi-build-utils@2.0.0: {}
-
- napi-postinstall@0.3.4: {}
-
natural-compare@1.4.0: {}
negotiator@1.1.0:
dependencies:
content-type: 2.1.0
- next@16.3.5(@babel/core@7.29.7)(@types/node@20.19.43)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
- dependencies:
- '@next/env': 16.3.5
- '@swc/helpers': 0.5.23
- baseline-browser-mapping: 2.11.24
- caniuse-lite: 1.0.30001810
- postcss: 8.5.23
- react: 19.2.8
- react-dom: 19.2.8(react@19.2.8)
- styled-jsx: 5.1.6(@babel/core@7.29.7)(react@19.2.8)
- optionalDependencies:
- '@next/swc-darwin-arm64': 16.3.5
- '@next/swc-darwin-x64': 16.3.5
- '@next/swc-linux-arm64-gnu': 16.3.5
- '@next/swc-linux-arm64-musl': 16.3.5
- '@next/swc-linux-x64-gnu': 16.3.5
- '@next/swc-linux-x64-musl': 16.3.5
- '@next/swc-win32-arm64-msvc': 16.3.5
- '@next/swc-win32-x64-msvc': 16.3.5
- sharp: 0.35.4(@types/node@20.19.43)
- transitivePeerDependencies:
- - '@babel/core'
- - '@types/node'
- - babel-plugin-macros
-
- node-abi@3.96.0:
- dependencies:
- semver: 7.8.5
-
- node-addon-api@8.9.2: {}
-
- node-exports-info@1.6.2:
- dependencies:
- array.prototype.flatmap: 1.3.3
- es-errors: 1.3.0
- object.entries: 1.1.9
- semver: 6.3.1
+ node-addon-api@8.9.2:
+ optional: true
- node-releases@2.0.55: {}
+ node-releases@2.0.57: {}
npm-run-path@4.0.1:
dependencies:
@@ -8327,46 +6843,8 @@ snapshots:
object-inspect@1.13.4: {}
- object-keys@1.1.1: {}
-
object-treeify@1.1.33: {}
- object.assign@4.1.7:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.2
- has-symbols: 1.1.0
- object-keys: 1.1.1
-
- object.entries@1.1.9:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.2
-
- object.fromentries@2.0.8:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-object-atoms: 1.1.2
-
- object.groupby@1.0.3:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
-
- object.values@1.2.1:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.2
-
obug@2.2.1: {}
ohash@2.0.12: {}
@@ -8402,8 +6880,9 @@ snapshots:
is-docker: 2.2.1
is-wsl: 2.2.0
- openai@6.49.0(zod@4.6.5):
+ openai@6.49.0(ws@8.21.3)(zod@4.6.5):
optionalDependencies:
+ ws: 8.21.3
zod: 4.6.5
optionator@0.9.4:
@@ -8427,13 +6906,6 @@ snapshots:
string-width: 7.2.0
strip-ansi: 7.2.0
- own-keys@1.0.2:
- dependencies:
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
- object-keys: 1.1.1
- safe-push-apply: 1.0.0
-
p-limit@2.3.0:
dependencies:
p-try: 2.2.0
@@ -8477,8 +6949,6 @@ snapshots:
path-key@4.0.0: {}
- path-parse@1.0.7: {}
-
path-to-regexp@8.4.2: {}
pathe@2.0.3: {}
@@ -8503,19 +6973,11 @@ snapshots:
dependencies:
find-up: 3.0.0
- possible-typed-array-names@1.1.0: {}
-
postcss-selector-parser@7.1.6:
dependencies:
cssesc: 3.0.0
util-deprecate: 1.0.2
- postcss@8.5.23:
- dependencies:
- nanoid: 3.3.19
- picocolors: 1.1.1
- source-map-js: 1.2.1
-
postcss@8.5.28:
dependencies:
nanoid: 3.3.19
@@ -8528,21 +6990,6 @@ snapshots:
powershell-utils@0.2.1: {}
- prebuild-install@7.1.3:
- dependencies:
- detect-libc: 2.1.2
- expand-template: 2.0.3
- github-from-package: 0.0.0
- minimist: 1.2.8
- mkdirp-classic: 0.5.3
- napi-build-utils: 2.0.0
- node-abi: 3.96.0
- pump: 3.0.4
- rc: 1.2.8
- simple-get: 4.0.1
- tar-fs: 2.1.5
- tunnel-agent: 0.6.0
-
prelude-ls@1.2.1: {}
pretty-ms@9.3.1:
@@ -8567,17 +7014,13 @@ snapshots:
- react
- react-dom
+ promise-limit@2.7.0: {}
+
prompts@2.4.2:
dependencies:
kleur: 3.0.3
sisteransi: 1.0.5
- prop-types@15.8.1:
- dependencies:
- loose-envify: 1.4.0
- object-assign: 4.1.1
- react-is: 16.13.1
-
proper-lockfile@4.1.2:
dependencies:
graceful-fs: 4.2.11
@@ -8589,11 +7032,6 @@ snapshots:
forwarded: 0.2.0
ipaddr.js: 1.9.1
- pump@3.0.4:
- dependencies:
- end-of-stream: 1.4.5
- once: 1.4.0
-
punycode@2.3.1: {}
pure-rand@6.1.0: {}
@@ -8682,20 +7120,11 @@ snapshots:
defu: 6.1.7
destr: 2.0.5
- rc@1.2.8:
- dependencies:
- deep-extend: 0.6.0
- ini: 1.3.8
- minimist: 1.2.8
- strip-json-comments: 2.0.1
-
react-dom@19.2.8(react@19.2.8):
dependencies:
react: 19.2.8
scheduler: 0.27.0
- react-is@16.13.1: {}
-
react-remove-scroll-bar@2.3.8(@types/react@19.3.0)(react@19.2.8):
dependencies:
react: 19.2.8
@@ -8715,6 +7144,20 @@ snapshots:
optionalDependencies:
'@types/react': 19.3.0
+ react-router-dom@7.18.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+ dependencies:
+ react: 19.2.8
+ react-dom: 19.2.8(react@19.2.8)
+ react-router: 7.18.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+
+ react-router@7.18.4(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+ dependencies:
+ cookie: 1.1.1
+ react: 19.2.8
+ set-cookie-parser: 2.7.2
+ optionalDependencies:
+ react-dom: 19.2.8(react@19.2.8)
+
react-style-singleton@2.2.3(@types/react@19.3.0)(react@19.2.8):
dependencies:
get-nonce: 1.0.1
@@ -8725,12 +7168,6 @@ snapshots:
react@19.2.8: {}
- readable-stream@3.6.2:
- dependencies:
- inherits: 2.0.4
- string_decoder: 1.3.0
- util-deprecate: 1.0.2
-
readdirp@5.1.1: {}
recast@0.23.21:
@@ -8741,43 +7178,14 @@ snapshots:
tiny-invariant: 1.3.3
tslib: 2.8.1
- reflect.getprototypeof@1.0.10:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- get-intrinsic: 1.3.0
- get-proto: 1.0.1
- which-builtin-type: 1.2.1
-
- regexp.prototype.flags@1.5.4:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-errors: 1.3.0
- get-proto: 1.0.1
- gopd: 1.2.0
- set-function-name: 2.0.2
-
remeda@2.33.4: {}
+ require-directory@2.1.1: {}
+
require-from-string@2.0.2: {}
resolve-from@4.0.0: {}
- resolve-pkg-maps@1.0.0: {}
-
- resolve@2.0.0-next.7:
- dependencies:
- es-errors: 1.3.0
- is-core-module: 2.16.2
- node-exports-info: 1.6.2
- object-keys: 1.1.1
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
-
restore-cursor@5.1.0:
dependencies:
onetime: 7.0.0
@@ -8791,26 +7199,26 @@ snapshots:
robust-predicates@3.0.3: {}
- rolldown@1.2.9:
+ rolldown@1.2.10:
dependencies:
- '@oxc-project/types': 0.150.0
+ '@oxc-project/types': 0.151.0
'@rolldown/pluginutils': 1.0.1
optionalDependencies:
- '@rolldown/binding-android-arm-eabi': 1.2.9
- '@rolldown/binding-android-arm64': 1.2.9
- '@rolldown/binding-darwin-arm64': 1.2.9
- '@rolldown/binding-darwin-x64': 1.2.9
- '@rolldown/binding-freebsd-x64': 1.2.9
- '@rolldown/binding-linux-arm-gnueabihf': 1.2.9
- '@rolldown/binding-linux-arm64-gnu': 1.2.9
- '@rolldown/binding-linux-arm64-musl': 1.2.9
- '@rolldown/binding-linux-ppc64-gnu': 1.2.9
- '@rolldown/binding-linux-s390x-gnu': 1.2.9
- '@rolldown/binding-linux-x64-gnu': 1.2.9
- '@rolldown/binding-linux-x64-musl': 1.2.9
- '@rolldown/binding-openharmony-arm64': 1.2.9
- '@rolldown/binding-win32-arm64-msvc': 1.2.9
- '@rolldown/binding-win32-x64-msvc': 1.2.9
+ '@rolldown/binding-android-arm-eabi': 1.2.10
+ '@rolldown/binding-android-arm64': 1.2.10
+ '@rolldown/binding-darwin-arm64': 1.2.10
+ '@rolldown/binding-darwin-x64': 1.2.10
+ '@rolldown/binding-freebsd-x64': 1.2.10
+ '@rolldown/binding-linux-arm-gnueabihf': 1.2.10
+ '@rolldown/binding-linux-arm64-gnu': 1.2.10
+ '@rolldown/binding-linux-arm64-musl': 1.2.10
+ '@rolldown/binding-linux-ppc64-gnu': 1.2.10
+ '@rolldown/binding-linux-s390x-gnu': 1.2.10
+ '@rolldown/binding-linux-x64-gnu': 1.2.10
+ '@rolldown/binding-linux-x64-musl': 1.2.10
+ '@rolldown/binding-openharmony-arm64': 1.2.10
+ '@rolldown/binding-win32-arm64-msvc': 1.2.10
+ '@rolldown/binding-win32-x64-msvc': 1.2.10
router@2.2.0:
dependencies:
@@ -8828,26 +7236,9 @@ snapshots:
dependencies:
queue-microtask: 1.2.3
- safe-array-concat@1.1.4:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- get-intrinsic: 1.3.0
- has-symbols: 1.1.0
- isarray: 2.0.5
-
- safe-buffer@5.2.1: {}
-
- safe-push-apply@1.0.0:
- dependencies:
- es-errors: 1.3.0
- isarray: 2.0.5
-
- safe-regex-test@1.1.0:
+ rxjs@7.8.2:
dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-regex: 1.2.1
+ tslib: 2.8.1
safe-regex2@5.1.1:
dependencies:
@@ -8888,38 +7279,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
- set-function-length@1.2.2:
- dependencies:
- define-data-property: 1.1.4
- es-errors: 1.3.0
- function-bind: 1.1.2
- get-intrinsic: 1.3.0
- gopd: 1.2.0
- has-property-descriptors: 1.0.2
-
- set-function-name@2.0.2:
- dependencies:
- define-data-property: 1.1.4
- es-errors: 1.3.0
- functions-have-names: 1.2.3
- has-property-descriptors: 1.0.2
-
- set-proto@1.0.0:
- dependencies:
- dunder-proto: 1.0.1
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
+ set-cookie-parser@2.7.2: {}
setprototypeof@1.2.0: {}
- shadcn@4.21.0(@cfworker/json-schema@4.1.1)(typescript@5.9.3):
+ shadcn@4.21.0(typescript@5.9.3):
dependencies:
'@babel/core': 7.29.7
- '@babel/parser': 7.29.8
- '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7)
+ '@babel/parser': 7.29.9
+ '@babel/plugin-transform-typescript': 7.29.9(@babel/core@7.29.7)
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7)
'@dotenvx/dotenvx': 1.75.1
- '@modelcontextprotocol/sdk': 1.30.0(@cfworker/json-schema@4.1.1)(zod@3.25.76)
+ '@modelcontextprotocol/sdk': 1.30.0(zod@3.25.76)
'@types/validate-npm-package-name': 4.0.2
browserslist: 4.29.0
cn: 0.2.6
@@ -8930,7 +7301,7 @@ snapshots:
diff: 8.0.4
execa: 9.6.1
fast-glob: 3.3.3
- fs-extra: 11.4.0
+ fs-extra: 11.4.1
fuzzysort: 3.1.0
kleur: 4.1.5
open: 11.0.4
@@ -8953,46 +7324,14 @@ snapshots:
- supports-color
- typescript
- sharp@0.35.4(@types/node@20.19.43):
- dependencies:
- '@img/colour': 1.1.0
- detect-libc: 2.1.2
- semver: 7.8.5
- optionalDependencies:
- '@img/sharp-darwin-arm64': 0.35.4
- '@img/sharp-darwin-x64': 0.35.4
- '@img/sharp-freebsd-wasm32': 0.35.4
- '@img/sharp-libvips-darwin-arm64': 1.3.3
- '@img/sharp-libvips-darwin-x64': 1.3.3
- '@img/sharp-libvips-linux-arm': 1.3.3
- '@img/sharp-libvips-linux-arm64': 1.3.3
- '@img/sharp-libvips-linux-ppc64': 1.3.3
- '@img/sharp-libvips-linux-riscv64': 1.3.3
- '@img/sharp-libvips-linux-s390x': 1.3.3
- '@img/sharp-libvips-linux-x64': 1.3.3
- '@img/sharp-libvips-linuxmusl-arm64': 1.3.3
- '@img/sharp-libvips-linuxmusl-x64': 1.3.3
- '@img/sharp-linux-arm': 0.35.4
- '@img/sharp-linux-arm64': 0.35.4
- '@img/sharp-linux-ppc64': 0.35.4
- '@img/sharp-linux-riscv64': 0.35.4
- '@img/sharp-linux-s390x': 0.35.4
- '@img/sharp-linux-x64': 0.35.4
- '@img/sharp-linuxmusl-arm64': 0.35.4
- '@img/sharp-linuxmusl-x64': 0.35.4
- '@img/sharp-webcontainers-wasm32': 0.35.4
- '@img/sharp-win32-arm64': 0.35.4
- '@img/sharp-win32-ia32': 0.35.4
- '@img/sharp-win32-x64': 0.35.4
- '@types/node': 20.19.43
- optional: true
-
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
shebang-regex@3.0.0: {}
+ shell-quote@1.9.0: {}
+
side-channel-list@1.0.1:
dependencies:
es-errors: 1.3.0
@@ -9027,14 +7366,6 @@ snapshots:
signal-exit@4.1.0: {}
- simple-concat@1.0.1: {}
-
- simple-get@4.0.1:
- dependencies:
- decompress-response: 6.0.0
- once: 1.4.0
- simple-concat: 1.0.1
-
sisteransi@1.0.5: {}
smart-buffer@4.2.0: {}
@@ -9050,8 +7381,6 @@ snapshots:
sqlstring@2.3.3: {}
- stable-hash@0.0.5: {}
-
stackback@0.0.2: {}
statuses@2.0.2: {}
@@ -9062,72 +7391,18 @@ snapshots:
stdin-discarder@0.2.2: {}
- stop-iteration-iterator@1.1.0:
+ string-width@4.2.3:
dependencies:
- es-errors: 1.3.0
- internal-slot: 1.1.0
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
- get-east-asian-width: 1.6.0
+ get-east-asian-width: 1.7.0
strip-ansi: 7.2.0
- string.prototype.includes@2.0.1:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-abstract: 1.24.2
-
- string.prototype.matchall@4.1.0:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-errors: 1.3.0
- es-object-atoms: 1.1.2
- get-intrinsic: 1.3.0
- gopd: 1.2.0
- has-symbols: 1.1.0
- internal-slot: 1.1.0
- regexp.prototype.flags: 1.5.4
- set-function-name: 2.0.2
- side-channel: 1.1.1
-
- string.prototype.repeat@1.0.0:
- dependencies:
- define-properties: 1.2.1
- es-abstract: 1.24.2
-
- string.prototype.trim@1.2.11:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-data-property: 1.1.4
- define-properties: 1.2.1
- es-abstract: 1.24.2
- es-object-atoms: 1.1.2
- has-property-descriptors: 1.0.2
- safe-regex-test: 1.1.0
-
- string.prototype.trimend@1.0.10:
- dependencies:
- call-bind: 1.0.9
- call-bound: 1.0.4
- define-properties: 1.2.1
- es-object-atoms: 1.1.2
-
- string.prototype.trimstart@1.0.8:
- dependencies:
- call-bind: 1.0.9
- define-properties: 1.2.1
- es-object-atoms: 1.1.2
-
- string_decoder@1.3.0:
- dependencies:
- safe-buffer: 5.2.1
-
stringify-object@5.0.0:
dependencies:
get-own-enumerable-keys: 1.0.0
@@ -9148,44 +7423,22 @@ snapshots:
strip-final-newline@4.0.0: {}
- strip-json-comments@2.0.1: {}
-
strip-json-comments@3.1.1: {}
- styled-jsx@5.1.6(@babel/core@7.29.7)(react@19.2.8):
- dependencies:
- client-only: 0.0.1
- react: 19.2.8
- optionalDependencies:
- '@babel/core': 7.29.7
-
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
- supports-preserve-symlinks-flag@1.0.0: {}
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
- systeminformation@5.33.10: {}
+ systeminformation@5.33.13: {}
tailwindcss@4.3.3: {}
tapable@2.3.3: {}
- tar-fs@2.1.5:
- dependencies:
- chownr: 1.1.4
- mkdirp-classic: 0.5.3
- pump: 3.0.4
- tar-stream: 2.2.0
-
- tar-stream@2.2.0:
- dependencies:
- bl: 4.1.0
- end-of-stream: 1.4.5
- fs-constants: 1.0.0
- inherits: 2.0.4
- readable-stream: 3.6.2
-
tiny-invariant@1.3.3: {}
tinybench@6.1.4: {}
@@ -9203,6 +7456,8 @@ snapshots:
toidentifier@1.0.1: {}
+ tree-kill@1.2.2: {}
+
ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -9212,13 +7467,6 @@ snapshots:
'@ts-morph/common': 0.27.0
code-block-writer: 13.0.3
- tsconfig-paths@3.15.0:
- dependencies:
- '@types/json5': 0.0.29
- json5: 1.0.2
- minimist: 1.2.8
- strip-bom: 3.0.0
-
tsconfig-paths@4.2.0:
dependencies:
json5: 2.2.3
@@ -9227,10 +7475,6 @@ snapshots:
tslib@2.8.1: {}
- tunnel-agent@0.6.0:
- dependencies:
- safe-buffer: 5.2.1
-
tw-animate-css@1.4.0: {}
type-check@0.4.0:
@@ -9243,45 +7487,12 @@ snapshots:
media-typer: 1.1.1
mime-types: 3.0.2
- typed-array-buffer@1.0.3:
- dependencies:
- call-bound: 1.0.4
- es-errors: 1.3.0
- is-typed-array: 1.1.15
-
- typed-array-byte-length@1.0.3:
- dependencies:
- call-bind: 1.0.9
- for-each: 0.3.5
- gopd: 1.2.0
- has-proto: 1.2.0
- is-typed-array: 1.1.15
-
- typed-array-byte-offset@1.0.4:
- dependencies:
- available-typed-arrays: 1.0.7
- call-bind: 1.0.9
- for-each: 0.3.5
- gopd: 1.2.0
- has-proto: 1.2.0
- is-typed-array: 1.1.15
- reflect.getprototypeof: 1.0.10
-
- typed-array-length@1.0.8:
- dependencies:
- call-bind: 1.0.9
- for-each: 0.3.5
- gopd: 1.2.0
- is-typed-array: 1.1.15
- possible-typed-array-names: 1.1.0
- reflect.getprototypeof: 1.0.10
-
- typescript-eslint@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3):
+ typescript-eslint@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.70.0(@typescript-eslint/parser@8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- '@typescript-eslint/parser': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
- '@typescript-eslint/typescript-estree': 8.70.0(typescript@5.9.3)
- '@typescript-eslint/utils': 8.70.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/eslint-plugin': 8.70.1(@typescript-eslint/parser@8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.70.1(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.70.1(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)
eslint: 9.39.5(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
@@ -9289,15 +7500,10 @@ snapshots:
typescript@5.9.3: {}
- unbox-primitive@1.1.0:
- dependencies:
- call-bound: 1.0.4
- has-bigints: 1.1.0
- has-symbols: 1.1.0
- which-boxed-primitive: 1.1.1
-
undici-types@6.21.0: {}
+ undici-types@8.9.0: {}
+
undici@7.29.1: {}
unicorn-magic@0.3.0: {}
@@ -9306,33 +7512,6 @@ snapshots:
unpipe@1.0.0: {}
- unrs-resolver@1.12.2:
- dependencies:
- napi-postinstall: 0.3.4
- optionalDependencies:
- '@unrs/resolver-binding-android-arm-eabi': 1.12.2
- '@unrs/resolver-binding-android-arm64': 1.12.2
- '@unrs/resolver-binding-darwin-arm64': 1.12.2
- '@unrs/resolver-binding-darwin-x64': 1.12.2
- '@unrs/resolver-binding-freebsd-x64': 1.12.2
- '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2
- '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2
- '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2
- '@unrs/resolver-binding-linux-arm64-musl': 1.12.2
- '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2
- '@unrs/resolver-binding-linux-loong64-musl': 1.12.2
- '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2
- '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2
- '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2
- '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2
- '@unrs/resolver-binding-linux-x64-gnu': 1.12.2
- '@unrs/resolver-binding-linux-x64-musl': 1.12.2
- '@unrs/resolver-binding-openharmony-arm64': 1.12.2
- '@unrs/resolver-binding-wasm32-wasi': 1.12.2
- '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2
- '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2
- '@unrs/resolver-binding-win32-x64-msvc': 1.12.2
-
update-browserslist-db@1.3.3(browserslist@4.29.0):
dependencies:
browserslist: 4.29.0
@@ -9377,80 +7556,39 @@ snapshots:
- '@types/react'
- '@types/react-dom'
- vite@8.3.0(@types/node@20.19.43)(jiti@2.7.0):
+ vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0):
dependencies:
lightningcss: 1.33.0
picomatch: 4.0.7
postcss: 8.5.28
- rolldown: 1.2.9
+ rolldown: 1.2.10
tinyglobby: 0.2.17
optionalDependencies:
- '@types/node': 20.19.43
+ '@types/node': 26.6.2
fsevents: 2.3.3
jiti: 2.7.0
- vitest@5.0.1(@types/node@20.19.43)(vite@8.3.0(@types/node@20.19.43)(jiti@2.7.0)):
+ vitest@5.0.1(@types/node@26.6.2)(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0)):
dependencies:
'@types/chai': 5.2.3
- '@vitest/mocker': 5.0.1(vite@8.3.0(@types/node@20.19.43)(jiti@2.7.0))
+ '@vitest/mocker': 5.0.1(vite@8.3.0(@types/node@26.6.2)(jiti@2.7.0))
chai: 6.2.2
es-module-lexer: 2.3.2
expect-type: 1.4.0
- magic-string: 1.4.1
+ magic-string: 1.4.2
obug: 2.2.1
picomatch: 4.0.7
std-env: 4.2.0
tinybench: 6.1.4
tinyexec: 1.3.0
tinyglobby: 0.2.17
- vite: 8.3.0(@types/node@20.19.43)(jiti@2.7.0)
+ vite: 8.3.0(@types/node@26.6.2)(jiti@2.7.0)
why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 20.19.43
+ '@types/node': 26.6.2
transitivePeerDependencies:
- msw
- which-boxed-primitive@1.1.1:
- dependencies:
- is-bigint: 1.1.0
- is-boolean-object: 1.2.2
- is-number-object: 1.1.1
- is-string: 1.1.1
- is-symbol: 1.1.1
-
- which-builtin-type@1.2.1:
- dependencies:
- call-bound: 1.0.4
- function.prototype.name: 1.2.0
- has-tostringtag: 1.0.2
- is-async-function: 2.1.1
- is-date-object: 1.1.0
- is-finalizationregistry: 1.1.1
- is-generator-function: 1.1.2
- is-regex: 1.2.1
- is-weakref: 1.1.1
- isarray: 2.0.5
- which-boxed-primitive: 1.1.1
- which-collection: 1.0.2
- which-typed-array: 1.1.22
-
- which-collection@1.0.2:
- dependencies:
- is-map: 2.0.3
- is-set: 2.0.3
- is-weakmap: 2.0.2
- is-weakset: 2.0.4
-
- which-typed-array@1.1.22:
- dependencies:
- available-typed-arrays: 1.0.7
- call-bind: 1.0.9
- call-bound: 1.0.4
- for-each: 0.3.5
- get-proto: 1.0.1
- gopd: 1.2.0
- has-tostringtag: 1.0.2
-
which@2.0.2:
dependencies:
isexe: 2.0.0
@@ -9466,15 +7604,37 @@ snapshots:
word-wrap@1.2.5: {}
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
wrappy@1.0.2: {}
+ ws@8.21.3: {}
+
wsl-utils@1.0.0:
dependencies:
is-wsl: 3.1.1
powershell-utils: 0.1.0
+ y18n@5.0.8: {}
+
yallist@3.1.1: {}
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yocto-queue@0.1.0: {}
yocto-spinner@1.2.2:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..3e82e7c
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+packages:
+ - "client"
+ - "server"
diff --git a/scripts/prepare-server-bundle.sh b/scripts/prepare-server-bundle.sh
new file mode 100755
index 0000000..cc22ada
--- /dev/null
+++ b/scripts/prepare-server-bundle.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+#
+# Assembles a self-contained copy of the backend at repo-root
+# server-bundle/, suitable for running with `bun run src/index.ts` from any
+# working directory -- not just this git checkout. Used by the Tauri
+# packaging build (client/src-tauri/tauri.conf.json's beforeBuildCommand),
+# and independently verifiable via the Phase 0 risk-gate check described in
+# the packaging plan.
+#
+# Not run automatically by `pnpm run dev` or CI -- only by the Tauri build.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
+SERVER_DIR="$REPO_ROOT/server"
+CLIENT_DIR="$REPO_ROOT/client"
+OUT_DIR="$REPO_ROOT/server-bundle"
+
+step() { printf '\n==> %s\n' "$1"; }
+
+step "Cleaning previous server-bundle"
+rm -rf "$OUT_DIR"
+mkdir -p "$OUT_DIR"
+
+# node_modules here has to satisfy two things at once, and getting there
+# took three attempts:
+#
+# 1. It must be genuinely self-contained (no symlinks pointing back into
+# this git checkout's central pnpm store) -- a plain `rsync -aL
+# server/node_modules` fails this: pnpm resolves a package's transitive
+# deps through a *private* per-package node_modules slot living inside
+# the workspace root's .pnpm store as a *sibling* of the requiring
+# package (e.g. @prisma/adapter-libsql's own @libsql/client dependency
+# lives at node_modules/.pnpm/@prisma+adapter-libsql@.../node_modules/,
+# not anywhere reachable by walking server/node_modules's own tree), so
+# a naive copy silently drops @libsql/client.
+#
+# 2. It must contain zero symlinks at all -- Tauri's `bundle.resources`
+# copy step does not handle pnpm's nested private-slot symlink
+# structure correctly (confirmed: a packaged app failed with "Cannot
+# find module '@prisma/adapter-libsql'"; comparing file counts showed
+# Tauri's copy landed the same file count but zero of the source's 395
+# symlinks, silently dropping directories only reachable via one).
+# `pnpm deploy` alone satisfies (1) but not (2) -- its own node_modules
+# is self-contained but still internally symlinked. Naively
+# dereferencing those symlinks after the fact (rsync -aL) breaks (1)
+# again: each symlink gets flattened independently, losing the sibling
+# relationship @prisma/adapter-libsql depends on to find @libsql/client
+# once it's no longer nested inside its own private slot.
+#
+# pnpm's "hoisted" node-linker satisfies both at once -- it's a
+# classic/npm-style flat layout (every package, including transitive
+# deps, sits directly at node_modules/) with no private per-package
+# slots and no .pnpm store to symlink into. Verified empirically before
+# relying on it: a hoisted install of just server's prod dependencies
+# landed @libsql/client and @libsql/darwin-arm64 directly under
+# node_modules/@libsql/, and @prisma/adapter-libsql resolves them via a
+# normal upward directory walk, same as npm would produce.
+step "Installing production dependencies with pnpm's hoisted node-linker"
+cp "$SERVER_DIR"/package.json "$OUT_DIR"/package.json
+(cd "$OUT_DIR" && pnpm install --config.node-linker=hoisted --prod --ignore-workspace)
+
+step "Copying server source, Prisma schema, and config"
+cp -R "$SERVER_DIR"/src "$OUT_DIR"/src
+cp -R "$SERVER_DIR"/prisma "$OUT_DIR"/prisma
+cp "$SERVER_DIR"/tsconfig.json "$OUT_DIR"/tsconfig.json
+cp "$SERVER_DIR"/prisma7.config.ts "$OUT_DIR"/prisma7.config.ts
+
+step "Generating the Prisma client (from server/, which has the prisma CLI)"
+(cd "$SERVER_DIR" && bunx prisma generate)
+rm -rf "$OUT_DIR"/src/generated
+mkdir -p "$OUT_DIR"/src/generated
+cp -R "$SERVER_DIR"/src/generated/prisma "$OUT_DIR"/src/generated/
+
+step "Copying built client (client/dist) as static assets"
+if [ ! -d "$CLIENT_DIR/dist" ]; then
+ echo "client/dist is missing -- run 'pnpm --filter client build' first." >&2
+ exit 1
+fi
+cp -R "$CLIENT_DIR"/dist "$OUT_DIR"/client-dist
+
+# Deliberately not copying server/.env into the bundle: OPENAI_API_KEY
+# now comes from settings.json (written by the app's own first-run setup
+# screen, see server/src/lib/settings.ts), not a build-time file -- a
+# real distributable build must never ship with the developer's own key
+# baked in. Every other env var the server needs (DATABASE_URL, DATA_DIR,
+# PORT, CORS_ORIGIN, FFMPEG_PATH) is already set explicitly by
+# client/src-tauri/src/lib.rs's spawn call, which overrides whatever a
+# bundled .env would have provided anyway -- so there's nothing left for
+# a bundled .env to actually do.
+
+step "Bootstrapping a pre-migrated empty app.db template"
+TMP_DIR="$(mktemp -d)"
+TMP_DB="$TMP_DIR/app.db"
+(cd "$SERVER_DIR" && DATABASE_URL="file:$TMP_DB" bunx prisma migrate deploy)
+cp "$TMP_DB" "$OUT_DIR"/app.db.template
+rm -rf "$TMP_DIR"
+
+step "Done: $OUT_DIR (run with: cd $OUT_DIR && bun run src/index.ts)"
diff --git a/scripts/setup-mac.sh b/scripts/setup-mac.sh
index 03fe8ff..8a7b66a 100755
--- a/scripts/setup-mac.sh
+++ b/scripts/setup-mac.sh
@@ -1,18 +1,20 @@
#!/usr/bin/env bash
#
# One-shot setup for running this project on macOS: installs Node/pnpm/
-# ffmpeg-full via Homebrew if missing, installs JS deps, generates the
-# Prisma client, and creates client/.env (with FFMPEG_PATH pre-filled and
-# an interactive OPENAI_API_KEY prompt) if one doesn't already exist.
+# Bun/ffmpeg-full via Homebrew (Bun via its own official installer, see
+# below) if missing, installs JS deps for the whole workspace, generates
+# the server's Prisma client, and creates server/.env (with FFMPEG_PATH
+# pre-filled and an interactive OPENAI_API_KEY prompt) if one doesn't
+# already exist.
#
# Safe to re-run -- every step checks before it acts, and an existing
-# client/.env is never overwritten.
+# server/.env is never overwritten.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
-CLIENT_DIR="$REPO_ROOT/client"
+SERVER_DIR="$REPO_ROOT/server"
step() { printf '\n==> %s\n' "$1"; }
@@ -72,6 +74,23 @@ else
brew install pnpm
fi
+step "Checking for Bun"
+if command -v bun &>/dev/null; then
+ echo "Found Bun $(bun -v)."
+else
+ # Bun's own installer, not Homebrew: the Homebrew formula requires
+ # compiling a native dependency (better-sqlite3, transitively) against
+ # Xcode Command Line Tools, and fails on a CLT version that's gone
+ # stale relative to the current macOS/Xcode release -- a common state
+ # on a machine that hasn't run a manual CLT update in a while. Bun's
+ # installer just downloads a prebuilt binary, sidestepping that
+ # entirely (see https://bun.sh).
+ echo "Installing Bun via its official installer..."
+ curl -fsSL https://bun.sh/install | bash
+ export PATH="$HOME/.bun/bin:$PATH"
+ echo "Installed Bun $(bun -v). Open a new terminal (or re-source your shell rc) to get 'bun' on PATH permanently."
+fi
+
step "Checking ffmpeg has subtitle burn-in (libass) support"
ffmpeg_path_override=""
if command -v ffmpeg &>/dev/null && ffmpeg -filters 2>/dev/null | grep -qw subtitles; then
@@ -88,19 +107,19 @@ else
ffmpeg_path_override="$(brew --prefix ffmpeg-full)/bin/ffmpeg"
fi
-step "Installing JS dependencies"
-(cd "$CLIENT_DIR" && pnpm install)
+step "Installing JS dependencies (client + server workspace)"
+(cd "$REPO_ROOT" && pnpm install)
step "Generating the Prisma client"
-(cd "$CLIENT_DIR" && pnpm exec prisma generate)
+(cd "$SERVER_DIR" && bunx prisma generate)
-step "Setting up client/.env"
-env_file="$CLIENT_DIR/.env"
+step "Setting up server/.env"
+env_file="$SERVER_DIR/.env"
if [ -f "$env_file" ]; then
- echo "client/.env already exists -- leaving it as-is."
+ echo "server/.env already exists -- leaving it as-is."
else
- cp "$CLIENT_DIR/.env.example" "$env_file"
- echo "Created client/.env from .env.example."
+ cp "$SERVER_DIR/.env.example" "$env_file"
+ echo "Created server/.env from .env.example."
if [ -n "$ffmpeg_path_override" ]; then
set_env_var "$env_file" \
@@ -110,13 +129,13 @@ else
fi
openai_key_input=""
- read -rsp "Paste your OpenAI API key (used server-side only; Enter to skip and edit client/.env by hand later): " openai_key_input || true
+ read -rsp "Paste your OpenAI API key (used server-side only; Enter to skip and edit server/.env by hand later): " openai_key_input || true
echo
if [ -n "$openai_key_input" ]; then
set_env_var "$env_file" "OPENAI_API_KEY=sk-..." "OPENAI_API_KEY=$openai_key_input"
- echo "Set OPENAI_API_KEY in client/.env."
+ echo "Set OPENAI_API_KEY in server/.env."
else
- echo "Skipped -- transcription and AI features won't work until you set OPENAI_API_KEY in client/.env."
+ echo "Skipped -- transcription and AI features won't work until you set OPENAI_API_KEY in server/.env."
fi
fi
@@ -124,8 +143,8 @@ step "Done"
cat < {
+ it("produces one cue per sentence with source-time timestamps when there are no cuts", () => {
+ const words = [word("1", "Hello", 0, 0.5), word("2", "there", 0.5, 1)];
+ const cues = generateCaptions(transcript(words), [], 10);
+ expect(cues).toHaveLength(1);
+ expect(cues[0]).toMatchObject({ start: 0, end: 1, text: "Hello there" });
+ });
+
+ it("drops a sentence entirely inside a cut", () => {
+ const words = [word("1", "Hello", 0, 0.5), word("2", "there", 0.5, 1)];
+ const cues = generateCaptions(transcript(words), [cut(0, 1)], 10);
+ expect(cues).toEqual([]);
+ });
+
+ it("keeps only the surviving words of a partially cut sentence", () => {
+ const words = [word("1", "Hello", 0, 0.5), word("2", "there", 0.5, 1)];
+ const cues = generateCaptions(transcript(words), [cut(0, 0.5)], 10);
+ expect(cues).toHaveLength(1);
+ expect(cues[0].text).toBe("there");
+ });
+
+ it("remaps timestamps onto the edited (post-cut) timeline, not source time", () => {
+ // A cut before the sentence should shift its start/end back by the cut's length.
+ const words = [word("1", "Hello", 5, 5.5), word("2", "there", 5.5, 6)];
+ const cues = generateCaptions(transcript(words), [cut(0, 2)], 10);
+ expect(cues[0]).toMatchObject({ start: 3, end: 4 });
+ });
+
+ it("includes per-word timing remapped the same way", () => {
+ const words = [word("1", "Hello", 5, 5.5), word("2", "there", 5.5, 6)];
+ const cues = generateCaptions(transcript(words), [cut(0, 2)], 10);
+ expect(cues[0].words).toEqual([
+ { start: 3, end: 3.5, text: "Hello" },
+ { start: 3.5, end: 4, text: "there" },
+ ]);
+ });
+
+ it("sorts cues by start time", () => {
+ const t: Transcript = {
+ id: "t1",
+ segments: [
+ { id: "s1", start: 5, end: 6, text: "Second.", words: [word("2", "Second", 5, 6)] },
+ { id: "s2", start: 0, end: 1, text: "First.", words: [word("1", "First", 0, 1)] },
+ ],
+ };
+ const cues = generateCaptions(t, [], 10);
+ expect(cues.map((c) => c.text)).toEqual(["First", "Second"]);
+ });
+});
diff --git a/server/src/lib/captions/generate.ts b/server/src/lib/captions/generate.ts
new file mode 100644
index 0000000..ca31021
--- /dev/null
+++ b/server/src/lib/captions/generate.ts
@@ -0,0 +1,62 @@
+import type { Transcript } from "@/types/transcript";
+import type { CutOperation } from "@/types/edit-operation";
+import { splitIntoSentences } from "@/lib/timeline/sentences";
+import { computePlayableRanges, isWordCut, sourceTimeToEditedTime } from "@/lib/timeline/cuts";
+
+export type CaptionCueWord = {
+ /** Seconds, on the *edited* timeline (post-cuts). */
+ start: number;
+ end: number;
+ text: string;
+};
+
+export type CaptionCue = {
+ /** Seconds, on the *edited* timeline (post-cuts) -- matches the rendered video, not the raw source. */
+ start: number;
+ end: number;
+ text: string;
+ /** Per-word timing within this cue, for word-by-word highlight styles (claude.md issue #15). */
+ words: CaptionCueWord[];
+};
+
+/**
+ * Generates one caption cue per sentence, from word-level transcript
+ * timestamps. Must respect edit operations (claude.md section 10, issue
+ * requirement): a sentence entirely inside a cut is dropped, a partially
+ * cut sentence keeps only its surviving words, and every timestamp is
+ * remapped from source time to edited-timeline time via the same
+ * computePlayableRanges/sourceTimeToEditedTime the preview and the render
+ * pipeline use -- captions must land on the rendered video's actual
+ * timeline, not the pre-cut source video's.
+ */
+export function generateCaptions(
+ transcript: Transcript,
+ cuts: CutOperation[],
+ duration: number
+): CaptionCue[] {
+ const playableRanges = computePlayableRanges(duration, cuts);
+ const cues: CaptionCue[] = [];
+
+ for (const segment of transcript.segments) {
+ for (const sentence of splitIntoSentences(segment)) {
+ const survivingWords = sentence.words.filter((w) => !isWordCut(w.start, w.end, cuts));
+ if (survivingWords.length === 0) continue;
+
+ const sourceStart = survivingWords[0].start;
+ const sourceEnd = survivingWords[survivingWords.length - 1].end;
+
+ cues.push({
+ start: sourceTimeToEditedTime(sourceStart, playableRanges),
+ end: sourceTimeToEditedTime(sourceEnd, playableRanges),
+ text: survivingWords.map((w) => w.text).join(" "),
+ words: survivingWords.map((w) => ({
+ start: sourceTimeToEditedTime(w.start, playableRanges),
+ end: sourceTimeToEditedTime(w.end, playableRanges),
+ text: w.text,
+ })),
+ });
+ }
+ }
+
+ return cues.filter((c) => c.end > c.start).sort((a, b) => a.start - b.start);
+}
diff --git a/server/src/lib/captions/style.test.ts b/server/src/lib/captions/style.test.ts
new file mode 100644
index 0000000..46c0268
--- /dev/null
+++ b/server/src/lib/captions/style.test.ts
@@ -0,0 +1,59 @@
+import { describe, expect, it } from "vitest";
+import {
+ buildAssStyleFields,
+ buildForceStyle,
+ CAPTION_MARGIN_V_PERCENT,
+ CAPTION_REFERENCE_HEIGHT,
+ DEFAULT_CAPTION_STYLE,
+ hexToAssColor,
+} from "@/lib/captions/style";
+
+describe("hexToAssColor", () => {
+ it("converts #RRGGBB to ASS's &HAABBGGRR (byte-swapped, opaque)", () => {
+ expect(hexToAssColor("#FF0000")).toBe("&H000000FF");
+ expect(hexToAssColor("#00FF00")).toBe("&H0000FF00");
+ expect(hexToAssColor("#0000FF")).toBe("&H00FF0000");
+ });
+});
+
+describe("buildAssStyleFields", () => {
+ it("computes FontSize as a percent of the given reference height", () => {
+ const fields = buildAssStyleFields({ ...DEFAULT_CAPTION_STYLE, fontSize: 2.2 }, 1080);
+ expect(fields.fontSize).toBe(Math.round((2.2 / 100) * 1080));
+ });
+
+ it("computes MarginV from CAPTION_MARGIN_V_PERCENT of the reference height", () => {
+ const fields = buildAssStyleFields(DEFAULT_CAPTION_STYLE, 1080);
+ expect(fields.marginV).toBe(Math.round((CAPTION_MARGIN_V_PERCENT / 100) * 1080));
+ });
+
+ it("scales with a different reference height (e.g. the render's actual probed output height)", () => {
+ const fields576 = buildAssStyleFields(DEFAULT_CAPTION_STYLE, 576);
+ const fields1080 = buildAssStyleFields(DEFAULT_CAPTION_STYLE, CAPTION_REFERENCE_HEIGHT);
+ expect(fields576.fontSize).toBeLessThan(fields1080.fontSize);
+ });
+
+ it("computes a background alpha corresponding to 70% opacity", () => {
+ const fields = buildAssStyleFields(DEFAULT_CAPTION_STYLE, 1080);
+ // ASS alpha is inverted (00 = opaque); 70% opaque -> 30% transparent -> ~0x4D.
+ expect(fields.backColour).toBe("&H4D000000");
+ });
+});
+
+describe("buildForceStyle", () => {
+ it("includes FontName, computed FontSize, colors, and alignment", () => {
+ const style = buildForceStyle({ ...DEFAULT_CAPTION_STYLE, font: "Georgia", fontSize: 2.2 }, 1080);
+ expect(style).toContain("FontName=Georgia");
+ expect(style).toContain(`FontSize=${Math.round((2.2 / 100) * 1080)}`);
+ expect(style).toContain("Alignment=2"); // "bottom" per ALIGNMENT_BY_POSITION
+ });
+
+ it("only includes BackColour when background is enabled", () => {
+ const withoutBg = buildForceStyle({ ...DEFAULT_CAPTION_STYLE, background: false }, 1080);
+ const withBg = buildForceStyle({ ...DEFAULT_CAPTION_STYLE, background: true }, 1080);
+ expect(withoutBg).not.toContain("BackColour");
+ expect(withBg).toContain("BackColour");
+ expect(withBg).toContain("BorderStyle=3");
+ expect(withoutBg).toContain("BorderStyle=1");
+ });
+});
diff --git a/server/src/lib/captions/style.ts b/server/src/lib/captions/style.ts
new file mode 100644
index 0000000..a9ba54d
--- /dev/null
+++ b/server/src/lib/captions/style.ts
@@ -0,0 +1,261 @@
+/**
+ * Burned-in caption styling, applied via libass's `force_style` filter
+ * option (see lib/ffmpeg/plan.ts). Fonts are a fixed whitelist rather than
+ * free text -- besides matching the "small preset list" scope of the
+ * feature, it also means FontName can never carry characters that would
+ * need escaping in the ffmpeg filtergraph (spec section 18: sanitize and
+ * validate all FFmpeg parameters).
+ */
+export const CAPTION_FONTS = [
+ "Arial",
+ "Helvetica",
+ "Verdana",
+ "Georgia",
+ "Courier New",
+ "Impact",
+] as const;
+
+export type CaptionFont = (typeof CAPTION_FONTS)[number];
+
+export const CAPTION_POSITIONS = ["top", "middle", "bottom"] as const;
+export type CaptionPosition = (typeof CAPTION_POSITIONS)[number];
+
+export type CaptionStyle = {
+ font: CaptionFont;
+ /**
+ * Percent of frame HEIGHT, not raw points -- a raw point value meant
+ * something different in the CSS preview (px against the on-screen,
+ * often scaled-down player) than in the export (ASS/libass units
+ * against an undeclared script resolution), so the same stored number
+ * rendered at very different relative sizes (GitHub issue #22). Both
+ * renderers now derive an actual size from this one percent: the CSS
+ * preview via `cqh` container query units (VideoPlayer.tsx), the export
+ * via `buildAssStyleFields` below against the fixed
+ * CAPTION_REFERENCE_WIDTH/HEIGHT, which libass then auto-scales to
+ * whatever the real output resolution is (PlayResX/Y in
+ * lib/captions/format.ts#toAssKaraoke, `original_size` in
+ * lib/ffmpeg/plan.ts for the plain-SRT path).
+ */
+ fontSize: number;
+ /** '#RRGGBB' */
+ textColor: string;
+ /** '#RRGGBB' */
+ outlineColor: string;
+ position: CaptionPosition;
+ /** Draw an opaque box behind the text instead of just an outline. */
+ background: boolean;
+ /**
+ * Word-by-word "typing" highlight (claude.md issue #15): the word
+ * currently being spoken renders in highlightColor, the rest of the cue
+ * in textColor. Applied both to the live preview overlay and to the
+ * export (as an .ass file with libass karaoke tags -- see
+ * lib/captions/format.ts#toAssKaraoke).
+ */
+ wordHighlight: boolean;
+ /** '#RRGGBB' -- color of the word currently being spoken when wordHighlight is on. */
+ highlightColor: string;
+};
+
+/**
+ * Fixed reference frame size that caption font/margin percentages are
+ * computed against for export (ASS PlayResX/Y, ffmpeg `original_size`).
+ * Arbitrary -- doesn't need to match the actual video's resolution, since
+ * libass rescales a PlayRes-authored layout to the real output frame size.
+ * 16:9 keeps the math simple; only the height matters for vertical sizing.
+ */
+export const CAPTION_REFERENCE_WIDTH = 1920;
+export const CAPTION_REFERENCE_HEIGHT = 1080;
+
+/** Vertical inset from the top/bottom edge, percent of frame height. */
+export const CAPTION_MARGIN_V_PERCENT = 3;
+
+/** Opacity of the background box, shared by the CSS preview and both export paths. */
+export const CAPTION_BACKGROUND_OPACITY = 0.7;
+
+export const DEFAULT_CAPTION_STYLE: CaptionStyle = {
+ font: "Arial",
+ fontSize: 2.2,
+ textColor: "#FFFFFF",
+ outlineColor: "#000000",
+ position: "bottom",
+ background: false,
+ wordHighlight: false,
+ highlightColor: "#FF0000",
+};
+
+/** One-click starting points for the manual style controls -- pick a theme, then fine-tune from there. */
+export const CAPTION_THEMES: { id: string; label: string; style: CaptionStyle }[] = [
+ {
+ id: "classic",
+ label: "Classic",
+ style: DEFAULT_CAPTION_STYLE,
+ },
+ {
+ id: "bold-yellow",
+ label: "Bold Yellow",
+ style: {
+ font: "Impact",
+ fontSize: 3.0,
+ textColor: "#FFEE00",
+ outlineColor: "#000000",
+ position: "bottom",
+ background: false,
+ wordHighlight: false,
+ highlightColor: "#FF0000",
+ },
+ },
+ {
+ id: "boxed",
+ label: "Boxed",
+ style: {
+ font: "Arial",
+ fontSize: 2.2,
+ textColor: "#FFFFFF",
+ outlineColor: "#000000",
+ position: "bottom",
+ background: true,
+ wordHighlight: false,
+ highlightColor: "#FF0000",
+ },
+ },
+ {
+ id: "minimal-top",
+ label: "Minimal Top",
+ style: {
+ font: "Verdana",
+ fontSize: 1.9,
+ textColor: "#FFFFFF",
+ outlineColor: "#000000",
+ position: "top",
+ background: false,
+ wordHighlight: false,
+ highlightColor: "#FF0000",
+ },
+ },
+ {
+ id: "high-contrast",
+ label: "High Contrast",
+ style: {
+ font: "Georgia",
+ fontSize: 2.6,
+ textColor: "#000000",
+ outlineColor: "#FFFFFF",
+ position: "bottom",
+ background: true,
+ wordHighlight: false,
+ highlightColor: "#FF0000",
+ },
+ },
+ {
+ id: "word-highlight",
+ label: "Word Highlight",
+ style: {
+ font: "Arial",
+ fontSize: 2.2,
+ textColor: "#000000",
+ outlineColor: "#FFFFFF",
+ position: "bottom",
+ background: false,
+ wordHighlight: true,
+ highlightColor: "#FF0000",
+ },
+ },
+];
+
+/** ASS/libass alignment values (numpad layout, center column only). */
+export const ALIGNMENT_BY_POSITION: Record = {
+ bottom: 2,
+ middle: 5,
+ top: 8,
+};
+
+/** '#RRGGBB' -> ASS '&HAABBGGRR' (opaque, so alpha is always 00). */
+export function hexToAssColor(hex: string): string {
+ const r = hex.slice(1, 3);
+ const g = hex.slice(3, 5);
+ const b = hex.slice(5, 7);
+ return `&H00${b}${g}${r}`.toUpperCase();
+}
+
+/**
+ * '#RRGGBB' + a 0-1 opacity fraction -> ASS '&HAABBGGRR'. ASS alpha is
+ * inverted from the usual convention (00 = fully opaque, FF = fully
+ * transparent), which is easy to get backwards -- this is the one place
+ * that conversion happens, so both export paths (force_style and the ASS
+ * karaoke file) agree with each other and with the CSS preview's opacity.
+ */
+function hexToAssColorWithOpacity(hex: string, opacity: number): string {
+ const r = hex.slice(1, 3);
+ const g = hex.slice(3, 5);
+ const b = hex.slice(5, 7);
+ const alpha = Math.round((1 - opacity) * 255)
+ .toString(16)
+ .padStart(2, "0");
+ return `&H${alpha}${b}${g}${r}`.toUpperCase();
+}
+
+/**
+ * Computes the actual ASS/libass field values shared by both export paths
+ * -- buildForceStyle below (plain SRT + force_style) and toAssKaraoke
+ * (lib/captions/format.ts, word-highlight) -- so there's exactly one place
+ * that turns a CaptionStyle into ASS numbers, not two hand-authored copies
+ * that can disagree with each other (GitHub issue #22).
+ *
+ * `referenceHeight` is the frame height (in pixels) that `style.fontSize`'s
+ * percent is computed against, and callers must pick the right one for
+ * their rendering path -- they are NOT interchangeable:
+ * - toAssKaraoke's .ass file declares PlayResX/Y (a real ASS/libass
+ * feature, verified: the same FontSize renders ~4x smaller with
+ * PlayResY=1080 declared than with no PlayRes at all), so libass itself
+ * rescales a CAPTION_REFERENCE_HEIGHT-relative value to the real output
+ * size -- pass CAPTION_REFERENCE_HEIGHT.
+ * - force_style has no such scaling: ffmpeg's `subtitles` filter's
+ * `original_size` option does NOT rescale force_style's FontSize/Margin
+ * (verified empirically -- byte-identical output with and without it);
+ * FontSize there is always literal, un-scaled output pixels. So
+ * buildForceStyle needs the render's ACTUAL output video height, probed
+ * via ffmpeg/probe.ts#probeVideoDimensions the same way the logo-size
+ * fix does -- pass that.
+ */
+export function buildAssStyleFields(style: CaptionStyle, referenceHeight: number) {
+ return {
+ fontSize: Math.round((style.fontSize / 100) * referenceHeight),
+ marginV: Math.round((CAPTION_MARGIN_V_PERCENT / 100) * referenceHeight),
+ // A fixed, modest outline -- CSS approximates the same idea with a 1px
+ // text-stroke + soft glow, not a pixel-identical match, but this at
+ // least keeps the two export paths consistent with each other.
+ outline: 2,
+ shadow: 0,
+ backColour: hexToAssColorWithOpacity("#000000", CAPTION_BACKGROUND_OPACITY),
+ };
+}
+
+/**
+ * Builds the comma-separated ASS style string for libass's `force_style`
+ * filter option. Every input here is expected to have already been
+ * validated (lib/validation/caption-style.ts) -- this function assumes it,
+ * it does not re-check.
+ *
+ * @param videoHeight The render's actual output video height in pixels
+ * (probed -- see buildAssStyleFields's doc comment for why, unlike
+ * toAssKaraoke, this can't use a fixed reference height).
+ */
+export function buildForceStyle(style: CaptionStyle, videoHeight: number): string {
+ const fields = buildAssStyleFields(style, videoHeight);
+ const parts = [
+ `FontName=${style.font}`,
+ `FontSize=${fields.fontSize}`,
+ `PrimaryColour=${hexToAssColor(style.textColor)}`,
+ `OutlineColour=${hexToAssColor(style.outlineColor)}`,
+ `Alignment=${ALIGNMENT_BY_POSITION[style.position]}`,
+ `Outline=${fields.outline}`,
+ `Shadow=${fields.shadow}`,
+ `MarginV=${fields.marginV}`,
+ // BorderStyle 3 = opaque box, 1 = outline only (libass default).
+ `BorderStyle=${style.background ? 3 : 1}`,
+ ];
+ if (style.background) {
+ parts.push(`BackColour=${fields.backColour}`);
+ }
+ return parts.join(",");
+}
diff --git a/client/src/lib/db/client.ts b/server/src/lib/db/client.ts
similarity index 67%
rename from client/src/lib/db/client.ts
rename to server/src/lib/db/client.ts
index 97ffa2d..8e2c383 100644
--- a/client/src/lib/db/client.ts
+++ b/server/src/lib/db/client.ts
@@ -1,10 +1,10 @@
-import { PrismaBetterSqlite3 } from "@prisma/adapter-better-sqlite3";
-import { PrismaClient } from "@/generated/prisma/client";
+import { PrismaLibSql } from "@prisma/adapter-libsql";
+import { PrismaClient } from "../../generated/prisma/client";
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClient };
function createClient() {
- const adapter = new PrismaBetterSqlite3({
+ const adapter = new PrismaLibSql({
url: process.env.DATABASE_URL ?? "file:./data/app.db",
});
return new PrismaClient({ adapter });
diff --git a/client/src/lib/ffmpeg/filters.test.ts b/server/src/lib/ffmpeg/filters.test.ts
similarity index 100%
rename from client/src/lib/ffmpeg/filters.test.ts
rename to server/src/lib/ffmpeg/filters.test.ts
diff --git a/client/src/lib/ffmpeg/filters.ts b/server/src/lib/ffmpeg/filters.ts
similarity index 100%
rename from client/src/lib/ffmpeg/filters.ts
rename to server/src/lib/ffmpeg/filters.ts
diff --git a/client/src/lib/ffmpeg/plan.test.ts b/server/src/lib/ffmpeg/plan.test.ts
similarity index 100%
rename from client/src/lib/ffmpeg/plan.test.ts
rename to server/src/lib/ffmpeg/plan.test.ts
diff --git a/client/src/lib/ffmpeg/plan.ts b/server/src/lib/ffmpeg/plan.ts
similarity index 100%
rename from client/src/lib/ffmpeg/plan.ts
rename to server/src/lib/ffmpeg/plan.ts
diff --git a/client/src/lib/ffmpeg/probe.ts b/server/src/lib/ffmpeg/probe.ts
similarity index 100%
rename from client/src/lib/ffmpeg/probe.ts
rename to server/src/lib/ffmpeg/probe.ts
diff --git a/client/src/lib/ffmpeg/properties.test.ts b/server/src/lib/ffmpeg/properties.test.ts
similarity index 100%
rename from client/src/lib/ffmpeg/properties.test.ts
rename to server/src/lib/ffmpeg/properties.test.ts
diff --git a/client/src/lib/ffmpeg/properties.ts b/server/src/lib/ffmpeg/properties.ts
similarity index 100%
rename from client/src/lib/ffmpeg/properties.ts
rename to server/src/lib/ffmpeg/properties.ts
diff --git a/client/src/lib/ffmpeg/render-job.ts b/server/src/lib/ffmpeg/render-job.ts
similarity index 98%
rename from client/src/lib/ffmpeg/render-job.ts
rename to server/src/lib/ffmpeg/render-job.ts
index 4850dfc..802c6c1 100644
--- a/client/src/lib/ffmpeg/render-job.ts
+++ b/server/src/lib/ffmpeg/render-job.ts
@@ -1,5 +1,6 @@
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
+import { logger } from "@/lib/logger";
import { prisma } from "@/lib/db/client";
import { resolveInDataDir, statAsset } from "@/lib/storage/local";
import { computePlayableRanges } from "@/lib/timeline/cuts";
@@ -114,7 +115,7 @@ export async function runRenderJob(
data: { status: "completed", outputPath: outputRelativePath, error: null },
});
} catch (err) {
- console.error(`Render job ${jobId} failed:`, err);
+ logger.error(`Render job ${jobId} failed:`, err);
await prisma.renderJob.update({
where: { id: jobId },
data: {
diff --git a/client/src/lib/ffmpeg/run.ts b/server/src/lib/ffmpeg/run.ts
similarity index 100%
rename from client/src/lib/ffmpeg/run.ts
rename to server/src/lib/ffmpeg/run.ts
diff --git a/server/src/lib/http.ts b/server/src/lib/http.ts
new file mode 100644
index 0000000..640dd6c
--- /dev/null
+++ b/server/src/lib/http.ts
@@ -0,0 +1,6 @@
+import type { Context } from "hono";
+import type { ContentfulStatusCode } from "hono/utils/http-status";
+
+export function errorResponse(c: Context, code: string, message: string, status: ContentfulStatusCode) {
+ return c.json({ success: false, error: { code, message } }, status);
+}
diff --git a/server/src/lib/logger.ts b/server/src/lib/logger.ts
new file mode 100644
index 0000000..0055e44
--- /dev/null
+++ b/server/src/lib/logger.ts
@@ -0,0 +1,26 @@
+/**
+ * Minimal shared logging template so every server log line -- request
+ * logging, startup, and error logs -- looks the same in the terminal
+ * instead of each call site inventing its own ad-hoc console.log format.
+ * `[HH:MM:SS] LEVEL message`, level padded to line up in a column.
+ */
+
+type Level = "INFO" | "WARN" | "ERROR";
+
+function timestamp(): string {
+ return new Date().toTimeString().slice(0, 8);
+}
+
+function write(level: Level, message: string, ...args: unknown[]): void {
+ const line = `[${timestamp()}] ${level.padEnd(5)} ${message}`;
+ const out = level === "ERROR" ? console.error : level === "WARN" ? console.warn : console.log;
+ out(line, ...args);
+}
+
+export const logger = {
+ info: (message: string, ...args: unknown[]) => write("INFO", message, ...args),
+ warn: (message: string, ...args: unknown[]) => write("WARN", message, ...args),
+ error: (message: string, ...args: unknown[]) => write("ERROR", message, ...args),
+ /** Raw line writer, for handing to Hono's `logger()` request middleware so its output matches this same template. */
+ raw: (line: string) => write("INFO", line),
+};
diff --git a/server/src/lib/settings.ts b/server/src/lib/settings.ts
new file mode 100644
index 0000000..7ee275b
--- /dev/null
+++ b/server/src/lib/settings.ts
@@ -0,0 +1,42 @@
+import { existsSync, readFileSync, writeFileSync } from "node:fs";
+import path from "node:path";
+import { getDataDir } from "@/lib/storage/local";
+
+/**
+ * Local app settings (currently just the OpenAI API key), stored as a
+ * plain JSON file under DATA_DIR rather than a DB table -- this is the
+ * one piece of config a user supplies through the app itself (via the
+ * setup screen) rather than an env var, so it needs to survive without a
+ * server restart and without touching the project schema.
+ */
+type Settings = {
+ openaiApiKey?: string;
+};
+
+function settingsPath(): string {
+ return path.join(getDataDir(), "settings.json");
+}
+
+export function readSettings(): Settings {
+ const file = settingsPath();
+ if (!existsSync(file)) return {};
+ try {
+ return JSON.parse(readFileSync(file, "utf-8"));
+ } catch {
+ return {};
+ }
+}
+
+export function writeSettings(settings: Settings): void {
+ writeFileSync(settingsPath(), JSON.stringify(settings, null, 2));
+}
+
+/**
+ * A key entered through the setup screen (stored in settings.json) takes
+ * priority over server/.env's OPENAI_API_KEY -- but local dev keeps
+ * working unchanged via .env, since nothing writes settings.json unless
+ * someone actually submits the setup form.
+ */
+export function getOpenAiApiKey(): string | undefined {
+ return readSettings().openaiApiKey ?? process.env.OPENAI_API_KEY;
+}
diff --git a/client/src/lib/storage/local.test.ts b/server/src/lib/storage/local.test.ts
similarity index 100%
rename from client/src/lib/storage/local.test.ts
rename to server/src/lib/storage/local.test.ts
diff --git a/client/src/lib/storage/local.ts b/server/src/lib/storage/local.ts
similarity index 55%
rename from client/src/lib/storage/local.ts
rename to server/src/lib/storage/local.ts
index e7826e9..31d7092 100644
--- a/client/src/lib/storage/local.ts
+++ b/server/src/lib/storage/local.ts
@@ -1,11 +1,10 @@
import { mkdir, rm, stat, writeFile } from "node:fs/promises";
-import { createReadStream, type ReadStream } from "node:fs";
import path from "node:path";
/**
* Local filesystem storage for project media, per claude.md section 15.
* Everything lives under DATA_DIR (default ./data), resolved relative to
- * the process working directory (client/ when running `next dev`/`next start`).
+ * the process working directory (server/ when running `bun src/index.ts`).
*
* Every path that touches the filesystem is validated to stay inside
* DATA_DIR — never trust a client-supplied path directly (spec section 18).
@@ -19,15 +18,13 @@ export class InvalidPathError extends Error {
}
export function getDataDir(): string {
- // Deliberately dynamic (runtime-configurable data directory) — never meant
- // to be statically traced/bundled by the build.
- return path.resolve(/*turbopackIgnore: true*/ process.cwd(), process.env.DATA_DIR ?? "./data");
+ return path.resolve(process.cwd(), process.env.DATA_DIR ?? "./data");
}
/** Resolve a relative path against DATA_DIR, rejecting anything that escapes it. */
export function resolveInDataDir(relativePath: string): string {
const dataDir = getDataDir();
- const resolved = path.resolve(/*turbopackIgnore: true*/ dataDir, relativePath);
+ const resolved = path.resolve(dataDir, relativePath);
if (resolved !== dataDir && !resolved.startsWith(dataDir + path.sep)) {
throw new InvalidPathError(`Path escapes DATA_DIR: ${relativePath}`);
}
@@ -83,63 +80,28 @@ export async function statAsset(relativePath: string) {
return stat(resolveInDataDir(relativePath));
}
-/** Open a read stream for an asset, optionally for a byte range (HTTP Range support). */
-export function readAssetStream(relativePath: string, range?: { start: number; end: number }) {
- const absolutePath = resolveInDataDir(relativePath);
- return range
- ? createReadStream(absolutePath, { start: range.start, end: range.end })
- : createReadStream(absolutePath);
-}
-
/**
- * Wraps a Node fs read stream as a Web ReadableStream for use as a
- * NextResponse body.
- *
- * Not just `Readable.toWeb(stream)`: that throws an uncaught
- * `ERR_INVALID_STATE: Invalid state: Controller is already closed` when the
- * client aborts mid-stream (e.g. a element cancelling a Range
- * request while seeking) races with the fs stream's own 'end'/'close', and
- * both paths try to close the same controller. Every controller call here
- * is guarded so that race can't produce an unhandled exception, and
- * `cancel()` explicitly destroys the underlying fd on client abort instead
- * of leaving that to `Readable.toWeb`'s internal handling.
+ * Streams a request body directly to disk without buffering the whole file
+ * in memory first -- used by the upload route, where source videos can be
+ * hundreds of MB. `Bun.write` accepts a `ReadableStream` directly and
+ * streams it to the filesystem.
*/
-export function streamToWebReadable(stream: ReadStream): ReadableStream {
- let closed = false;
- return new ReadableStream({
- start(controller) {
- stream.on("data", (chunk) => {
- if (closed) return;
- try {
- controller.enqueue(chunk as Uint8Array);
- } catch {
- // Controller already closed/errored elsewhere -- stream is being torn down.
- }
- });
- stream.on("end", () => {
- if (closed) return;
- closed = true;
- try {
- controller.close();
- } catch {
- // Already closed via the error path below -- nothing to do.
- }
- });
- stream.on("error", (err) => {
- if (closed) return;
- closed = true;
- try {
- controller.error(err);
- } catch {
- // Already closed via the end path above -- nothing to do.
- }
- });
- },
- cancel() {
- closed = true;
- stream.destroy();
- },
- });
+export async function writeAssetStream(
+ projectId: string,
+ kind: AssetKind,
+ filename: string,
+ body: ReadableStream
+): Promise<{ relativePath: string; absolutePath: string; sizeBytes: number }> {
+ const dir = projectRelativeDir(projectId, kind);
+ const absDir = resolveInDataDir(dir);
+ await mkdir(absDir, { recursive: true });
+
+ const safeName = sanitizeFilename(filename);
+ const relativePath = path.posix.join(dir, safeName);
+ const absolutePath = resolveInDataDir(relativePath);
+
+ const sizeBytes = await Bun.write(absolutePath, body);
+ return { relativePath, absolutePath, sizeBytes };
}
export async function deleteProjectDir(projectId: string): Promise {
diff --git a/server/src/lib/timeline/cuts.test.ts b/server/src/lib/timeline/cuts.test.ts
new file mode 100644
index 0000000..aee3da2
--- /dev/null
+++ b/server/src/lib/timeline/cuts.test.ts
@@ -0,0 +1,316 @@
+import { describe, expect, it } from "vitest";
+import {
+ computePlayableRanges,
+ cutsOverlapping,
+ editedTimeToSourceTime,
+ getEditedDuration,
+ isCut,
+ isFullyCut,
+ isWordCut,
+ nextPlayableTime,
+ padCutStart,
+ sourceTimeToEditedTime,
+ wordSpanBounds,
+} from "@/lib/timeline/cuts";
+import type { CutOperation } from "@/types/edit-operation";
+import type { PlayableRange } from "@/types/timeline";
+
+function cut(start: number, end: number): CutOperation {
+ return { id: `${start}-${end}`, type: "cut", start, end, createdAt: 0 };
+}
+
+describe("computePlayableRanges", () => {
+ it("returns the whole duration with no cuts", () => {
+ expect(computePlayableRanges(10, [])).toEqual([{ start: 0, end: 10 }]);
+ });
+
+ it("returns nothing for a non-positive duration", () => {
+ expect(computePlayableRanges(0, [])).toEqual([]);
+ expect(computePlayableRanges(-5, [])).toEqual([]);
+ });
+
+ it("subtracts a single interior cut", () => {
+ expect(computePlayableRanges(10, [cut(3, 5)])).toEqual([
+ { start: 0, end: 3 },
+ { start: 5, end: 10 },
+ ]);
+ });
+
+ it("subtracts a cut touching the start", () => {
+ expect(computePlayableRanges(10, [cut(0, 3)])).toEqual([{ start: 3, end: 10 }]);
+ });
+
+ it("subtracts a cut touching the end", () => {
+ expect(computePlayableRanges(10, [cut(7, 10)])).toEqual([{ start: 0, end: 7 }]);
+ });
+
+ it("returns nothing when the whole video is cut", () => {
+ expect(computePlayableRanges(10, [cut(0, 10)])).toEqual([]);
+ });
+
+ it("returns nothing when a cut overruns the duration", () => {
+ expect(computePlayableRanges(10, [cut(0, 999)])).toEqual([]);
+ });
+
+ it("merges overlapping cuts", () => {
+ expect(computePlayableRanges(10, [cut(2, 5), cut(4, 7)])).toEqual([
+ { start: 0, end: 2 },
+ { start: 7, end: 10 },
+ ]);
+ });
+
+ it("merges adjacent (touching) cuts", () => {
+ expect(computePlayableRanges(10, [cut(2, 5), cut(5, 7)])).toEqual([
+ { start: 0, end: 2 },
+ { start: 7, end: 10 },
+ ]);
+ });
+
+ it("handles cuts given out of order", () => {
+ expect(computePlayableRanges(10, [cut(7, 9), cut(1, 2)])).toEqual([
+ { start: 0, end: 1 },
+ { start: 2, end: 7 },
+ { start: 9, end: 10 },
+ ]);
+ });
+
+ it("drops a zero-length surviving range between back-to-back cuts", () => {
+ // Cuts [1,3) and [3,3.0000001) leave essentially nothing between them once
+ // merged/clamped -- computePlayableRanges must not emit a degenerate range.
+ expect(computePlayableRanges(10, [cut(0, 3), cut(3, 10)])).toEqual([]);
+ });
+});
+
+describe("getEditedDuration", () => {
+ it("sums range lengths", () => {
+ const ranges: PlayableRange[] = [
+ { start: 0, end: 3 },
+ { start: 5, end: 10 },
+ ];
+ expect(getEditedDuration(ranges)).toBe(8);
+ });
+
+ it("is zero for no ranges", () => {
+ expect(getEditedDuration([])).toBe(0);
+ });
+});
+
+describe("isCut", () => {
+ const ranges: PlayableRange[] = [
+ { start: 0, end: 3 },
+ { start: 5, end: 10 },
+ ];
+
+ it("is false inside a playable range", () => {
+ expect(isCut(1, ranges)).toBe(false);
+ });
+
+ it("is true inside the gap between ranges", () => {
+ expect(isCut(4, ranges)).toBe(true);
+ });
+
+ it("treats a range's start as playable and its end as not (half-open)", () => {
+ expect(isCut(0, ranges)).toBe(false);
+ expect(isCut(3, ranges)).toBe(true);
+ });
+});
+
+describe("isWordCut / isFullyCut", () => {
+ const cuts = [cut(2, 5)];
+
+ it("a word fully inside a cut is cut", () => {
+ expect(isWordCut(2.5, 3, cuts)).toBe(true);
+ });
+
+ it("a word entirely outside any cut is not cut", () => {
+ expect(isWordCut(6, 7, cuts)).toBe(false);
+ });
+
+ it("a word straddling the cut boundary is not (fully) cut", () => {
+ expect(isWordCut(4, 6, cuts)).toBe(false);
+ });
+
+ it("isFullyCut requires every word to be individually cut", () => {
+ const words = [
+ { id: "a", text: "a", start: 2.1, end: 2.5 },
+ { id: "b", text: "b", start: 2.6, end: 3 },
+ ];
+ expect(isFullyCut(words, cuts)).toBe(true);
+ });
+
+ it("isFullyCut is false if any single word survives", () => {
+ const words = [
+ { id: "a", text: "a", start: 2.1, end: 2.5 },
+ { id: "b", text: "b", start: 6, end: 7 },
+ ];
+ expect(isFullyCut(words, cuts)).toBe(false);
+ });
+
+ it("isFullyCut is false for an empty word list", () => {
+ expect(isFullyCut([], cuts)).toBe(false);
+ });
+});
+
+describe("nextPlayableTime", () => {
+ const ranges: PlayableRange[] = [
+ { start: 0, end: 3 },
+ { start: 5, end: 10 },
+ ];
+
+ it("returns the same time if already playable", () => {
+ expect(nextPlayableTime(1, ranges)).toBe(1);
+ });
+
+ it("jumps forward to the next range's start when inside a cut", () => {
+ expect(nextPlayableTime(4, ranges)).toBe(5);
+ });
+
+ it("returns null at/after the end of the last range", () => {
+ expect(nextPlayableTime(10, ranges)).toBeNull();
+ expect(nextPlayableTime(50, ranges)).toBeNull();
+ });
+});
+
+describe("sourceTimeToEditedTime / editedTimeToSourceTime round-trip", () => {
+ const ranges: PlayableRange[] = [
+ { start: 0, end: 3 },
+ { start: 5, end: 10 },
+ ];
+
+ it("maps source time in the first range unchanged", () => {
+ expect(sourceTimeToEditedTime(1, ranges)).toBe(1);
+ });
+
+ it("maps source time in the second range, collapsing the gap", () => {
+ // 5s of first range consumed (0-3), then 2s into the second range (5-7) -> edited time 3+2=5
+ expect(sourceTimeToEditedTime(7, ranges)).toBe(5);
+ });
+
+ it("clamps time inside a cut gap to the elapsed edited time so far", () => {
+ expect(sourceTimeToEditedTime(4, ranges)).toBe(3);
+ });
+
+ it("is the exact inverse of editedTimeToSourceTime for playable instants", () => {
+ // Excludes exact range-boundary instants (e.g. 5, the start of the
+ // second range) on purpose: at a boundary shared between two adjacent
+ // ranges, multiple source times legitimately map to the same edited
+ // time (see the dedicated boundary test below), so round-tripping
+ // isn't well-defined there.
+ for (const sourceTime of [0, 1, 2.5, 5.001, 7, 9.999]) {
+ const edited = sourceTimeToEditedTime(sourceTime, ranges);
+ expect(editedTimeToSourceTime(edited, ranges)).toBeCloseTo(sourceTime, 10);
+ }
+ });
+
+ it("resolves the range-boundary ambiguity toward the earlier range's end", () => {
+ // Edited time 3 sits exactly between range 1 ending (source 3) and
+ // range 2 starting (source 5) -- both are "correct" since the cut
+ // between them is invisible on the edited timeline. editedTimeToSourceTime
+ // deterministically picks the earlier range's end.
+ expect(sourceTimeToEditedTime(5, ranges)).toBe(3);
+ expect(editedTimeToSourceTime(3, ranges)).toBe(3);
+ });
+
+ it("editedTimeToSourceTime clamps past-the-end time to the last range's end", () => {
+ const editedDuration = getEditedDuration(ranges);
+ expect(editedTimeToSourceTime(editedDuration + 100, ranges)).toBe(10);
+ });
+
+ it("editedTimeToSourceTime returns 0 for an empty range list", () => {
+ expect(editedTimeToSourceTime(5, [])).toBe(0);
+ });
+});
+
+describe("wordSpanBounds", () => {
+ it("spans the first word's start to the last word's end", () => {
+ const words = [
+ { start: 1.0, end: 1.5 },
+ { start: 1.5, end: 2.0 },
+ { start: 2.0, end: 2.4 },
+ ];
+ expect(wordSpanBounds(words, { start: 0, end: 99 })).toEqual({ start: 1.0, end: 2.4 });
+ });
+
+ it("uses the fallback for an empty word list", () => {
+ expect(wordSpanBounds([], { start: 5, end: 10 })).toEqual({ start: 5, end: 10 });
+ });
+
+ it("regression: a word extending past a container's own reported end is not clipped", () => {
+ // Mirrors transcribe.ts tolerating a word up to 50ms past segment.end
+ // when assigning words to a segment -- deleting "the segment" must use
+ // the word's real end (2.55), not the segment's looser reported end
+ // (2.50), or a sliver of that word's footage survives the cut.
+ const segmentReportedBounds = { start: 1.0, end: 2.5 };
+ const words = [
+ { start: 1.0, end: 1.5 },
+ { start: 1.5, end: 2.55 }, // 50ms past the segment's own reported end
+ ];
+ expect(wordSpanBounds(words, segmentReportedBounds)).toEqual({ start: 1.0, end: 2.55 });
+ });
+
+ it("a single-word list spans just that word", () => {
+ expect(wordSpanBounds([{ start: 3, end: 3.2 }], { start: 0, end: 0 })).toEqual({ start: 3, end: 3.2 });
+ });
+});
+
+describe("padCutStart", () => {
+ it("pulls the cut start earlier by the default pad when there's room", () => {
+ // Mirrors the real transcript that surfaced this: a natural pause (word
+ // ending at 3.28, next word reported starting at 4.08) between two
+ // sentences -- plenty of silence to safely absorb the pad into.
+ const allWords = [{ start: 2.8, end: 3.28 }];
+ expect(padCutStart(4.08, allWords)).toBeCloseTo(4.08 - 0.15, 10);
+ });
+
+ it("clamps to the end of the immediately preceding word instead of overrunning it", () => {
+ // Only 40ms of gap before the cut -- less than the default 0.15s pad,
+ // so the pad must not reach back into the previous word's own audio.
+ const allWords = [{ start: 2.0, end: 3.0 }];
+ expect(padCutStart(3.04, allWords)).toBe(3.0);
+ });
+
+ it("clamps to 0 when the cut is near the very start of the video (no preceding word)", () => {
+ expect(padCutStart(0.05, [])).toBe(0);
+ });
+
+ it("picks the correct preceding word out of several, not just the first/last in the list", () => {
+ const allWords = [
+ { start: 0, end: 1 },
+ { start: 1.2, end: 2 },
+ { start: 2.1, end: 2.9 },
+ ];
+ // Preceding word is [2.1, 2.9], not [0,1] or [1.2,2] -- pad reaches back
+ // to 2.9 at most (here the default 0.15s pad fits before that).
+ expect(padCutStart(3.2, allWords)).toBeCloseTo(3.2 - 0.15, 10);
+ });
+
+ it("respects a custom pad duration", () => {
+ const allWords = [{ start: 0, end: 1 }];
+ expect(padCutStart(2, allWords, 0.5)).toBe(1.5);
+ });
+});
+
+describe("cutsOverlapping", () => {
+ it("finds the single cut covering a word inside a bigger range", () => {
+ const cuts = [cut(2, 5), cut(8, 10)];
+ expect(cutsOverlapping(3, 3.5, cuts)).toEqual([cuts[0]]);
+ });
+
+ it("returns every cut that overlaps at all, not just ones fully containing the range", () => {
+ // A word-select delete's own cut partially overlaps a neighboring
+ // filler-word cut -- restoring either one's word should surface both.
+ const cuts = [cut(2, 4), cut(3.9, 6)];
+ expect(cutsOverlapping(3.5, 4.5, cuts)).toEqual(cuts);
+ });
+
+ it("is empty when the range isn't cut at all", () => {
+ expect(cutsOverlapping(10, 11, [cut(2, 5)])).toEqual([]);
+ });
+
+ it("excludes a cut that only touches the range's edge, not its interior", () => {
+ // [start, end) semantics, same as isCut/isWordCut: touching at a single
+ // instant isn't an overlap.
+ expect(cutsOverlapping(5, 6, [cut(2, 5)])).toEqual([]);
+ expect(cutsOverlapping(0, 2, [cut(2, 5)])).toEqual([]);
+ });
+});
diff --git a/server/src/lib/timeline/cuts.ts b/server/src/lib/timeline/cuts.ts
new file mode 100644
index 0000000..1419199
--- /dev/null
+++ b/server/src/lib/timeline/cuts.ts
@@ -0,0 +1,178 @@
+import type { CutOperation } from "@/types/edit-operation";
+import type { PlayableRange } from "@/types/timeline";
+import type { TranscriptWord } from "@/types/transcript";
+
+/**
+ * How much earlier than a word's reported `start` a cut is allowed to
+ * reach, to absorb speech-to-text timestamp imprecision. Confirmed
+ * empirically on a real transcript: Whisper reported a word's start as
+ * 4.08s, but the word was already clearly audible in the source audio's
+ * waveform from ~3.96s -- so a cut starting exactly at the reported
+ * timestamp left ~120ms of the real word audible right after the cut. This
+ * pad is deliberately larger than that one measurement to leave margin for
+ * worse cases, but is always clamped (see padCutStart) to the end of
+ * whatever word precedes the cut, so it can only reach into silence, never
+ * into that previous word's own real audio.
+ */
+export const CUT_START_PAD_SECONDS = 0.15;
+
+/**
+ * Extends a cut's start earlier, into the silence before it, by up to
+ * CUT_START_PAD_SECONDS -- see that constant's doc comment for why. Clamped
+ * to the end of the last word (from `allWords`, in chronological order)
+ * that ends at or before `start`, so the pad can never eat into that
+ * word's own audio, even for a cut with very little room before it (e.g. a
+ * filler word with almost no gap before the previous word).
+ */
+export function padCutStart(
+ start: number,
+ allWords: { start: number; end: number }[],
+ padSeconds: number = CUT_START_PAD_SECONDS
+): number {
+ let previousWordEnd = 0;
+ for (const w of allWords) {
+ if (w.end <= start && w.end > previousWordEnd) previousWordEnd = w.end;
+ }
+ return Math.max(previousWordEnd, start - padSeconds);
+}
+
+/**
+ * [start, end] spanning every word in the list -- first word's start, last
+ * word's end -- falling back to `fallback` if the list is empty. Use this
+ * (not a container's own reported start/end, e.g. a Whisper
+ * TranscriptSegment's `start`/`end`) whenever a cut needs to span "all of
+ * these words": a container's own boundary can be looser than its actual
+ * words (e.g. transcribe.ts tolerates a word ending up to 50ms past its
+ * segment's `end`), so cutting the container's boundary directly can leave
+ * a sliver of the last word's footage surviving past the cut -- visible as
+ * a brief flash in both the preview and the export, since both read the
+ * same stored cut boundary.
+ */
+export function wordSpanBounds(
+ words: { start: number; end: number }[],
+ fallback: { start: number; end: number }
+): { start: number; end: number } {
+ if (words.length === 0) return fallback;
+ return { start: words[0].start, end: words[words.length - 1].end };
+}
+
+/** Merge overlapping/adjacent cut ranges and sort by start time. */
+function mergeCuts(cuts: CutOperation[]): { start: number; end: number }[] {
+ const sorted = [...cuts]
+ .map((c) => ({ start: c.start, end: c.end }))
+ .sort((a, b) => a.start - b.start);
+
+ const merged: { start: number; end: number }[] = [];
+ for (const cut of sorted) {
+ const last = merged[merged.length - 1];
+ if (last && cut.start <= last.end) {
+ last.end = Math.max(last.end, cut.end);
+ } else {
+ merged.push({ ...cut });
+ }
+ }
+ return merged;
+}
+
+/** Subtract merged cut ranges from [0, duration] to get the surviving playable ranges. */
+export function computePlayableRanges(
+ duration: number,
+ cuts: CutOperation[]
+): PlayableRange[] {
+ if (duration <= 0) return [];
+ const merged = mergeCuts(cuts);
+
+ const ranges: PlayableRange[] = [];
+ let cursor = 0;
+ for (const cut of merged) {
+ if (cut.start > cursor) {
+ ranges.push({ start: cursor, end: Math.min(cut.start, duration) });
+ }
+ cursor = Math.max(cursor, cut.end);
+ if (cursor >= duration) break;
+ }
+ if (cursor < duration) {
+ ranges.push({ start: cursor, end: duration });
+ }
+ return ranges.filter((r) => r.end > r.start);
+}
+
+export function getEditedDuration(ranges: PlayableRange[]): number {
+ return ranges.reduce((sum, r) => sum + (r.end - r.start), 0);
+}
+
+/** Is this source-time instant inside a cut (i.e. not in any playable range)? */
+export function isCut(sourceTime: number, ranges: PlayableRange[]): boolean {
+ return !ranges.some((r) => sourceTime >= r.start && sourceTime < r.end);
+}
+
+/** Is this [start, end) word/sentence/segment range entirely covered by a cut? */
+export function isWordCut(start: number, end: number, cuts: CutOperation[]): boolean {
+ return cuts.some((c) => start >= c.start && end <= c.end + 0.001);
+}
+
+/** Are every one of these words individually cut (e.g. a whole sentence/segment)? */
+export function isFullyCut(words: TranscriptWord[], cuts: CutOperation[]): boolean {
+ return words.length > 0 && words.every((w) => isWordCut(w.start, w.end, cuts));
+}
+
+/**
+ * Every cut operation whose range overlaps [start, end) at all -- used to
+ * find which operation(s) to remove when restoring a specific cut word or
+ * segment, rather than only being able to undo the single most recent edit.
+ * If [start, end) was covered by one bigger cut (e.g. a whole segment
+ * delete), restoring any one word inside it removes that whole operation --
+ * there's no way to carve a single word back out of an operation that
+ * removed more than it, since a CutOperation is just one [start, end)
+ * range, not a set of words.
+ */
+export function cutsOverlapping(start: number, end: number, cuts: CutOperation[]): CutOperation[] {
+ return cuts.filter((c) => c.start < end && c.end > start);
+}
+
+/**
+ * If sourceTime falls inside a cut, return the start of the next playable
+ * range (so playback can jump forward over it). Returns null if sourceTime
+ * is at/after the end of the last playable range (end of edited video).
+ */
+export function nextPlayableTime(
+ sourceTime: number,
+ ranges: PlayableRange[]
+): number | null {
+ for (const r of ranges) {
+ if (sourceTime >= r.start && sourceTime < r.end) return sourceTime;
+ if (sourceTime < r.start) return r.start;
+ }
+ return null;
+}
+
+/** Map a source-video timestamp to its position on the edited (cuts-removed) timeline. */
+export function sourceTimeToEditedTime(
+ sourceTime: number,
+ ranges: PlayableRange[]
+): number {
+ let elapsed = 0;
+ for (const r of ranges) {
+ if (sourceTime < r.start) return elapsed;
+ if (sourceTime <= r.end) return elapsed + (sourceTime - r.start);
+ elapsed += r.end - r.start;
+ }
+ return elapsed;
+}
+
+/** Map a position on the edited timeline back to the corresponding source-video timestamp. */
+export function editedTimeToSourceTime(
+ editedTime: number,
+ ranges: PlayableRange[]
+): number {
+ let elapsed = 0;
+ for (const r of ranges) {
+ const len = r.end - r.start;
+ if (editedTime <= elapsed + len) {
+ return r.start + (editedTime - elapsed);
+ }
+ elapsed += len;
+ }
+ const last = ranges[ranges.length - 1];
+ return last ? last.end : 0;
+}
diff --git a/server/src/lib/timeline/sentences.test.ts b/server/src/lib/timeline/sentences.test.ts
new file mode 100644
index 0000000..c2006dd
--- /dev/null
+++ b/server/src/lib/timeline/sentences.test.ts
@@ -0,0 +1,50 @@
+import { describe, expect, it } from "vitest";
+import { splitIntoSentences } from "@/lib/timeline/sentences";
+import type { TranscriptSegment, TranscriptWord } from "@/types/transcript";
+
+function word(id: string, text: string, start: number, end: number): TranscriptWord {
+ return { id, text, start, end };
+}
+
+function segment(text: string, words: TranscriptWord[]): TranscriptSegment {
+ return { id: "s1", start: words[0]?.start ?? 0, end: words[words.length - 1]?.end ?? 0, text, words };
+}
+
+describe("splitIntoSentences", () => {
+ it("returns nothing for a segment with no words", () => {
+ expect(splitIntoSentences(segment("", []))).toEqual([]);
+ });
+
+ it("keeps a single-sentence segment as one sentence", () => {
+ const words = [word("1", "hello", 0, 0.5), word("2", "world", 0.5, 1)];
+ const result = splitIntoSentences(segment("hello world", words));
+ expect(result).toHaveLength(1);
+ expect(result[0]).toEqual({ words, start: 0, end: 1 });
+ });
+
+ it("splits a multi-sentence segment by terminal punctuation, mapped back onto word timestamps", () => {
+ const words = [
+ word("1", "Hi", 0, 0.3),
+ word("2", "there", 0.3, 0.6),
+ word("3", "How", 1, 1.3),
+ word("4", "are", 1.3, 1.6),
+ word("5", "you", 1.6, 2),
+ ];
+ const result = splitIntoSentences(segment("Hi there. How are you?", words));
+ expect(result).toHaveLength(2);
+ expect(result[0].words.map((w) => w.text)).toEqual(["Hi", "there"]);
+ expect(result[0]).toMatchObject({ start: 0, end: 0.6 });
+ expect(result[1].words.map((w) => w.text)).toEqual(["How", "are", "you"]);
+ expect(result[1]).toMatchObject({ start: 1, end: 2 });
+ });
+
+ it("appends leftover words as a trailing group instead of dropping them", () => {
+ // The text's word-count-by-whitespace can undercount segment.words
+ // (contractions, punctuation quirks per the function's own doc comment)
+ // -- simulate that by having more real words than the text accounts for.
+ const words = [word("1", "Hi", 0, 0.3), word("2", "there", 0.3, 0.6), word("3", "extra", 0.6, 0.9)];
+ const result = splitIntoSentences(segment("Hi. there.", words));
+ const allWords = result.flatMap((s) => s.words);
+ expect(allWords.map((w) => w.text)).toEqual(["Hi", "there", "extra"]);
+ });
+});
diff --git a/server/src/lib/timeline/sentences.ts b/server/src/lib/timeline/sentences.ts
new file mode 100644
index 0000000..d45912a
--- /dev/null
+++ b/server/src/lib/timeline/sentences.ts
@@ -0,0 +1,57 @@
+import type { TranscriptSegment, TranscriptWord } from "@/types/transcript";
+
+export type Sentence = {
+ words: TranscriptWord[];
+ start: number;
+ end: number;
+};
+
+/**
+ * Splits a transcript segment into sentences by terminal punctuation. A
+ * Whisper segment is a pause-based grouping and often spans several
+ * sentences (claude.md section 2's "delete a sentence" vs "delete a
+ * transcript segment" are genuinely different units, not aliases of the
+ * same thing).
+ *
+ * Punctuation only lives on segment.text, never on individual word tokens
+ * (OpenAI's word-timestamps API returns bare words) -- confirmed by
+ * inspecting real transcribe output, where every TranscriptWord.text was
+ * punctuation-free even though segment.text had periods. So sentence
+ * boundaries are found in segment.text and mapped back onto word
+ * timestamps by word count, not by testing word.text directly (that
+ * regex would never match anything, silently making every "sentence"
+ * span the whole segment).
+ */
+export function splitIntoSentences(segment: TranscriptSegment): Sentence[] {
+ if (segment.words.length === 0) return [];
+
+ const sentenceTexts = segment.text
+ .split(/(?<=[.!?])\s+/)
+ .map((s) => s.trim())
+ .filter(Boolean);
+
+ if (sentenceTexts.length <= 1) {
+ return [wordsToSentence(segment.words)];
+ }
+
+ const sentences: Sentence[] = [];
+ let wordIndex = 0;
+ for (const sentenceText of sentenceTexts) {
+ const wordCount = sentenceText.split(/\s+/).filter(Boolean).length;
+ const words = segment.words.slice(wordIndex, wordIndex + wordCount);
+ if (words.length > 0) sentences.push(wordsToSentence(words));
+ wordIndex += wordCount;
+ }
+
+ // Word-count mismatches (contractions, punctuation quirks) shouldn't
+ // silently drop words -- any leftover becomes its own trailing group.
+ if (wordIndex < segment.words.length) {
+ sentences.push(wordsToSentence(segment.words.slice(wordIndex)));
+ }
+
+ return sentences;
+}
+
+function wordsToSentence(words: TranscriptWord[]): Sentence {
+ return { words, start: words[0].start, end: words[words.length - 1].end };
+}
diff --git a/client/src/lib/validation/caption-style.ts b/server/src/lib/validation/caption-style.ts
similarity index 100%
rename from client/src/lib/validation/caption-style.ts
rename to server/src/lib/validation/caption-style.ts
diff --git a/client/src/lib/validation/edit-operation.test.ts b/server/src/lib/validation/edit-operation.test.ts
similarity index 100%
rename from client/src/lib/validation/edit-operation.test.ts
rename to server/src/lib/validation/edit-operation.test.ts
diff --git a/client/src/lib/validation/edit-operation.ts b/server/src/lib/validation/edit-operation.ts
similarity index 100%
rename from client/src/lib/validation/edit-operation.ts
rename to server/src/lib/validation/edit-operation.ts
diff --git a/client/src/lib/validation/logo.ts b/server/src/lib/validation/logo.ts
similarity index 100%
rename from client/src/lib/validation/logo.ts
rename to server/src/lib/validation/logo.ts
diff --git a/client/src/lib/validation/project.ts b/server/src/lib/validation/project.ts
similarity index 100%
rename from client/src/lib/validation/project.ts
rename to server/src/lib/validation/project.ts
diff --git a/client/src/lib/validation/transcript.ts b/server/src/lib/validation/transcript.ts
similarity index 100%
rename from client/src/lib/validation/transcript.ts
rename to server/src/lib/validation/transcript.ts
diff --git a/client/src/lib/validation/video-properties.ts b/server/src/lib/validation/video-properties.ts
similarity index 100%
rename from client/src/lib/validation/video-properties.ts
rename to server/src/lib/validation/video-properties.ts
diff --git a/server/src/lib/video/filters.test.ts b/server/src/lib/video/filters.test.ts
new file mode 100644
index 0000000..b56be05
--- /dev/null
+++ b/server/src/lib/video/filters.test.ts
@@ -0,0 +1,19 @@
+import { describe, expect, it } from "vitest";
+import { FILTER_PRESETS, getFilterPreset } from "@/lib/video/filters";
+
+describe("getFilterPreset", () => {
+ it("finds a preset by id", () => {
+ expect(getFilterPreset("bw").label).toBe("Black & white");
+ });
+
+ it("falls back to the first preset (none) for an unknown id", () => {
+ expect(getFilterPreset("nonexistent")).toBe(FILTER_PRESETS[0]);
+ expect(getFilterPreset("nonexistent").id).toBe("none");
+ });
+
+ it("every preset has a non-empty css value", () => {
+ for (const preset of FILTER_PRESETS) {
+ expect(preset.css.length).toBeGreaterThan(0);
+ }
+ });
+});
diff --git a/server/src/lib/video/filters.ts b/server/src/lib/video/filters.ts
new file mode 100644
index 0000000..a7bc071
--- /dev/null
+++ b/server/src/lib/video/filters.ts
@@ -0,0 +1,36 @@
+import type { FilterPreset } from "@/types/filter";
+
+/**
+ * Named visual presets, applied as a CSS `filter` on the video element for
+ * the live preview -- never baked into the source file directly. Export
+ * renders the actual pixels through an equivalent FFmpeg filter chain
+ * instead (see ffmpeg/filters.ts#getFfmpegFilter), which is a close but not
+ * always pixel-exact match (some CSS filter functions, like sepia() and
+ * hue-rotate(), have no simple ffmpeg equivalent -- see that file's doc
+ * comment for which terms are verified exact vs. approximate).
+ */
+export const FILTER_PRESETS: FilterPreset[] = [
+ { id: "none", label: "None", css: "none" },
+ { id: "bw", label: "Black & white", css: "grayscale(1) contrast(1.05)" },
+ {
+ id: "vintage",
+ label: "Vintage",
+ css: "sepia(0.35) saturate(1.2) contrast(1.1) brightness(0.95)",
+ },
+ {
+ id: "warm",
+ label: "Warm",
+ css: "sepia(0.18) saturate(1.3) brightness(1.05) contrast(1.05)",
+ },
+ {
+ id: "cool",
+ label: "Cool",
+ css: "hue-rotate(-8deg) saturate(1.15) brightness(1.02) contrast(1.05)",
+ },
+ { id: "contrast", label: "High contrast", css: "contrast(1.4) saturate(1.1)" },
+ { id: "faded", label: "Faded", css: "contrast(0.85) brightness(1.1) saturate(0.7)" },
+];
+
+export function getFilterPreset(id: string): FilterPreset {
+ return FILTER_PRESETS.find((p) => p.id === id) ?? FILTER_PRESETS[0];
+}
diff --git a/server/src/lib/video/logo.test.ts b/server/src/lib/video/logo.test.ts
new file mode 100644
index 0000000..56e2ccf
--- /dev/null
+++ b/server/src/lib/video/logo.test.ts
@@ -0,0 +1,26 @@
+import { describe, expect, it } from "vitest";
+import { DEFAULT_LOGO_POSITION, logoPositionToOverlayXY, toLogoPosition } from "@/lib/video/logo";
+
+describe("toLogoPosition", () => {
+ it("passes through a valid position", () => {
+ expect(toLogoPosition("top-left")).toBe("top-left");
+ });
+
+ it("falls back to the default for an invalid/legacy value", () => {
+ expect(toLogoPosition("nonsense")).toBe(DEFAULT_LOGO_POSITION);
+ });
+});
+
+describe("logoPositionToOverlayXY", () => {
+ it("anchors bottom-right using W/H filtergraph variables, not literal pixels", () => {
+ const { x, y } = logoPositionToOverlayXY("bottom-right", 3, 4);
+ expect(x).toBe("(W-w-W*0.0300)");
+ expect(y).toBe("(H-h-H*0.0400)");
+ });
+
+ it("anchors top-left", () => {
+ const { x, y } = logoPositionToOverlayXY("top-left", 3, 4);
+ expect(x).toBe("(W*0.0300)");
+ expect(y).toBe("(H*0.0400)");
+ });
+});
diff --git a/server/src/lib/video/logo.ts b/server/src/lib/video/logo.ts
new file mode 100644
index 0000000..ea503fe
--- /dev/null
+++ b/server/src/lib/video/logo.ts
@@ -0,0 +1,71 @@
+/**
+ * Server-side subset of client/src/lib/video/logo.ts -- keep the two in
+ * sync by hand when the shared logo-position math changes. Drops
+ * logoPositionToCss (CSS preview positioning, VideoPlayer.tsx only) so
+ * this package has no React dependency; the ffmpeg export path
+ * (logoPositionToOverlayXY, used by lib/ffmpeg/plan.ts) is unaffected.
+ */
+
+/**
+ * Logo/watermark overlay anchor + padding, shared between the live preview
+ * (VideoPlayer.tsx, positioned via CSS) and the export (ffmpeg/plan.ts,
+ * positioned via an overlay filter expression) so the two always agree.
+ */
+export const LOGO_POSITIONS = ["top-left", "top-right", "bottom-left", "bottom-right"] as const;
+export type LogoPosition = (typeof LOGO_POSITIONS)[number];
+
+export const DEFAULT_LOGO_POSITION: LogoPosition = "bottom-right";
+export const DEFAULT_LOGO_OPACITY = 100;
+
+/**
+ * Logo inset from the edge, as a percent of the frame's own width/height
+ * (paddingX relative to frame width, paddingY relative to frame height) --
+ * NOT raw pixels. A pixel value would mean something different in the
+ * preview (CSS px against the on-screen, often-scaled-down player element)
+ * than in the export (ffmpeg px against the video's native resolution), so
+ * both logoPositionToCss and logoPositionToOverlayXY below take this same
+ * frame-relative percent and let their own renderer (CSS %, ffmpeg's W/H
+ * filtergraph variables) do the pixel conversion in its own coordinate
+ * space -- see GitHub issue #21.
+ */
+export const DEFAULT_LOGO_PADDING_PERCENT = 3;
+export const LOGO_PADDING_MIN_PERCENT = 0;
+export const LOGO_PADDING_MAX_PERCENT = 20;
+
+/**
+ * Max logo size relative to the video frame, preserving aspect ratio and
+ * never upscaling past the source image's native size. Keep in sync with
+ * VideoPlayer.tsx's `max-w-[25%] max-h-[15%]` classes -- the ffmpeg export
+ * (ffmpeg/plan.ts) applies the same fractions via a `scale` filter so the
+ * exported logo size matches what was shown in the live preview.
+ */
+export const LOGO_MAX_WIDTH_FRACTION = 0.25;
+export const LOGO_MAX_HEIGHT_FRACTION = 0.15;
+
+/** Narrows the raw `Project.logoPosition` DB string (always app-written, but typed as `string` by Prisma) to `LogoPosition`. */
+export function toLogoPosition(value: string): LogoPosition {
+ return (LOGO_POSITIONS as readonly string[]).includes(value)
+ ? (value as LogoPosition)
+ : DEFAULT_LOGO_POSITION;
+}
+
+/**
+ * ffmpeg `overlay` filter x/y expressions for the same anchor + frame-
+ * relative padding percent, using the standard W/H (base video) and w/h
+ * (overlay) filtergraph variables so the position matches at any
+ * resolution -- W*fraction is ffmpeg's own equivalent of CSS's `%`, so no
+ * probed pixel dimensions are needed here (unlike the logo *size* fix,
+ * where the scale filter has no access to the base video's W/H and needs
+ * real numbers instead).
+ */
+export function logoPositionToOverlayXY(
+ position: LogoPosition,
+ paddingXPercent: number,
+ paddingYPercent: number
+): { x: string; y: string } {
+ const fracX = (paddingXPercent / 100).toFixed(4);
+ const fracY = (paddingYPercent / 100).toFixed(4);
+ const x = position.endsWith("left") ? `(W*${fracX})` : `(W-w-W*${fracX})`;
+ const y = position.startsWith("top") ? `(H*${fracY})` : `(H-h-H*${fracY})`;
+ return { x, y };
+}
diff --git a/server/src/lib/video/properties.test.ts b/server/src/lib/video/properties.test.ts
new file mode 100644
index 0000000..5e6fa04
--- /dev/null
+++ b/server/src/lib/video/properties.test.ts
@@ -0,0 +1,82 @@
+import { describe, expect, it } from "vitest";
+import { buildPropertiesCss, buildPropertiesSvgValues } from "@/lib/video/properties";
+import { DEFAULT_VIDEO_PROPERTIES } from "@/types/video-properties";
+
+describe("buildPropertiesCss", () => {
+ it("is empty at all-neutral defaults", () => {
+ expect(buildPropertiesCss(DEFAULT_VIDEO_PROPERTIES)).toBe("");
+ });
+
+ it("saturate() uses 1 + pct/100, matching ffmpeg's eq=saturation= exactly", () => {
+ const css = buildPropertiesCss({ ...DEFAULT_VIDEO_PROPERTIES, saturation: 20 });
+ expect(css).toBe("saturate(1.200)");
+ });
+
+ it("contrast() uses 1 + pct/100 with no cross-term from shadows", () => {
+ const css = buildPropertiesCss({ ...DEFAULT_VIDEO_PROPERTIES, contrast: 40, shadows: 100 });
+ expect(css).toBe("contrast(1.400)");
+ });
+
+ it("brightness() is 2^(exposure/100) -- exact match to the exposure filter's stops, not linear", () => {
+ const css = buildPropertiesCss({ ...DEFAULT_VIDEO_PROPERTIES, exposure: 100 });
+ expect(css).toBe(`brightness(${Math.pow(2, 1).toFixed(3)})`);
+ });
+
+ it("never produces a negative saturate/contrast multiplier", () => {
+ const css = buildPropertiesCss({ ...DEFAULT_VIDEO_PROPERTIES, saturation: -100, contrast: -100 });
+ expect(css).toBe("saturate(0.000) contrast(0.000)");
+ });
+});
+
+describe("buildPropertiesSvgValues", () => {
+ it("is the identity transform at all-neutral defaults", () => {
+ const v = buildPropertiesSvgValues(DEFAULT_VIDEO_PROPERTIES);
+ expect(v.highlightsSlope).toBe(1);
+ expect(v.shadowsExponent).toBe(1);
+ expect(v.colorMatrix).toBe("1 0 0 0 0.000 0 1 0 0 0.000 0 0 1 0 0.000 0 0 0 1 0");
+ });
+
+ it("highlights slope matches colorlevels' input-white-point remap for highlights > 0", () => {
+ // Verified against ffmpeg's colorlevels filter: output = clamp(input/imax, 0, 1).
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, highlights: 50 });
+ const imax = Math.min(1, Math.max(0.4, 1 - 50 / 250));
+ expect(v.highlightsSlope).toBeCloseTo(1 / imax, 10);
+ });
+
+ it("highlights slope matches colorlevels' output-white-point remap for highlights < 0", () => {
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, highlights: -100 });
+ const omax = Math.min(1, Math.max(0.4, 1 + -100 / 250));
+ expect(v.highlightsSlope).toBeCloseTo(omax, 10);
+ expect(v.highlightsSlope).toBe(0.6);
+ });
+
+ it("shadows exponent is 2^(-shadows/100), positive shadows brightening (exponent < 1)", () => {
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, shadows: 50 });
+ expect(v.shadowsExponent).toBeCloseTo(Math.pow(2, -0.5), 10);
+ expect(v.shadowsExponent).toBeLessThan(1);
+ });
+
+ it("negative shadows darken (exponent > 1)", () => {
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, shadows: -50 });
+ expect(v.shadowsExponent).toBeGreaterThan(1);
+ });
+
+ it("positive temperature shifts red up and blue down (warmer)", () => {
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, temperature: 100 });
+ const [rShift, , bShift] = v.colorMatrix
+ .split(/\s{2}/)
+ .map((row) => Number(row.trim().split(/\s+/).pop()));
+ expect(rShift).toBeGreaterThan(0);
+ expect(bShift).toBeLessThan(0);
+ });
+
+ it("positive tint shifts toward magenta (red and blue up, green down)", () => {
+ const v = buildPropertiesSvgValues({ ...DEFAULT_VIDEO_PROPERTIES, tint: 100 });
+ const [rShift, gShift, bShift] = v.colorMatrix
+ .split(/\s{2}/)
+ .map((row) => Number(row.trim().split(/\s+/).pop()));
+ expect(rShift).toBeGreaterThan(0);
+ expect(gShift).toBeLessThan(0);
+ expect(bShift).toBeGreaterThan(0);
+ });
+});
diff --git a/server/src/lib/video/properties.ts b/server/src/lib/video/properties.ts
new file mode 100644
index 0000000..0a40902
--- /dev/null
+++ b/server/src/lib/video/properties.ts
@@ -0,0 +1,101 @@
+import type { VideoProperties } from "@/types/video-properties";
+
+/**
+ * Builds the browser CSS `filter` string for saturation/contrast/exposure --
+ * for the live preview, combined with the selected preset's own css (see
+ * lib/video/filters.ts) and, for highlights/shadows/temperature/tint, an SVG
+ * filter (buildPropertiesSvgValues below) applied via `url(#id)` in the same
+ * `filter` list.
+ *
+ * These three are verified exact matches to their ffmpeg equivalents
+ * (lib/ffmpeg/properties.ts), confirmed empirically by rendering a test
+ * gradient through each ffmpeg filter and sampling pixel values -- not just
+ * assumed from documentation:
+ * - saturate() <-> eq=saturation= (both `1 + pct/100`, standard formula)
+ * - contrast() <-> eq=contrast= (both `(x-0.5)*c+0.5`, the CSS Filter
+ * Effects spec's own definition of contrast() happens to be the same
+ * formula ffmpeg's eq filter uses)
+ * - brightness() <-> exposure=exposure= (both a pure multiplicative gain,
+ * `x * amount`, when isolated from the `black` shadows term -- so
+ * `2^(exposure/100)` matches ffmpeg's exposure-in-stops semantics exactly
+ * where the old `1 + exposure/100` linear approximation didn't)
+ */
+export function buildPropertiesCss(props: VideoProperties): string {
+ const parts: string[] = [];
+
+ const saturation = 1 + props.saturation / 100;
+ if (saturation !== 1) parts.push(`saturate(${Math.max(0, saturation).toFixed(3)})`);
+
+ const contrast = 1 + props.contrast / 100;
+ if (contrast !== 1) parts.push(`contrast(${Math.max(0, contrast).toFixed(3)})`);
+
+ const brightness = Math.pow(2, props.exposure / 100);
+ if (brightness !== 1) parts.push(`brightness(${brightness.toFixed(3)})`);
+
+ return parts.join(" ");
+}
+
+/** Numeric inputs for the SVG filter primitives VideoPlayer.tsx renders for highlights/shadows/temperature/tint. */
+export type PropertiesSvgValues = {
+ /** feComponentTransfer type="linear" slope (intercept always 0). */
+ highlightsSlope: number;
+ /** feComponentTransfer type="gamma" exponent (amplitude/offset always 1/0). */
+ shadowsExponent: number;
+ /** feColorMatrix type="matrix" values (20 numbers, space-separated). */
+ colorMatrix: string;
+};
+
+/**
+ * Computes the SVG-filter equivalents of highlights/shadows/temperature/tint
+ * -- CSS's `filter` property has no dedicated primitives for these, unlike
+ * saturate/contrast/brightness above, so an inline SVG (rendered by
+ * VideoPlayer.tsx, referenced via `url(#id)`) is needed instead.
+ *
+ * Highlights is a verified EXACT match to ffmpeg's colorlevels filter
+ * (confirmed empirically the same way as buildPropertiesCss's three, above):
+ * colorlevels' imax/omax is a linear input/output-range remap, which is
+ * exactly feComponentTransfer's type="linear" (`slope*x + intercept`).
+ *
+ * Shadows and temperature/tint are NOT exact matches -- ffmpeg's shadows
+ * term (the `exposure` filter's `black` parameter) turned out not to follow
+ * any simple documented formula (verified empirically it's neither a plain
+ * black-point stretch nor a simple gamma curve), and colortemperature/
+ * colorbalance use a proper Planckian-locus white-balance algorithm with no
+ * straightforward CSS/SVG equivalent. These use a well-directioned but
+ * approximate gamma curve (shadows) and per-channel blue-yellow/green-
+ * magenta offset (temperature/tint) instead -- the same kind of fast
+ * approximation many editors use for real-time preview vs. final render,
+ * clearly labeled as such rather than claiming a parity that isn't real.
+ */
+export function buildPropertiesSvgValues(props: VideoProperties): PropertiesSvgValues {
+ let highlightsSlope = 1;
+ if (props.highlights > 0) {
+ const imax = Math.min(1, Math.max(0.4, 1 - props.highlights / 250));
+ highlightsSlope = 1 / imax;
+ } else if (props.highlights < 0) {
+ const omax = Math.min(1, Math.max(0.4, 1 + props.highlights / 250));
+ highlightsSlope = omax;
+ }
+
+ // Positive shadows -> exponent < 1 -> brightens dark values more than
+ // bright ones (a standard gamma-lift technique), matching the "positive
+ // = lift shadows" direction of the ffmpeg side (see the sign-fix comment
+ // in lib/ffmpeg/properties.ts) and of the old CSS approximation.
+ const shadowsExponent = Math.pow(2, -props.shadows / 100);
+
+ const warmth = props.temperature / 100;
+ const magenta = props.tint / 100;
+ const TEMPERATURE_SHIFT = 0.15;
+ const TINT_SHIFT = 0.1;
+ const rShift = (warmth * TEMPERATURE_SHIFT + magenta * TINT_SHIFT).toFixed(3);
+ const gShift = (-magenta * TINT_SHIFT).toFixed(3);
+ const bShift = (-warmth * TEMPERATURE_SHIFT + magenta * TINT_SHIFT).toFixed(3);
+ const colorMatrix = [
+ `1 0 0 0 ${rShift}`,
+ `0 1 0 0 ${gShift}`,
+ `0 0 1 0 ${bShift}`,
+ `0 0 0 1 0`,
+ ].join(" ");
+
+ return { highlightsSlope, shadowsExponent, colorMatrix };
+}
diff --git a/client/src/app/api/projects/[id]/captions/route.ts b/server/src/routes/captions.ts
similarity index 52%
rename from client/src/app/api/projects/[id]/captions/route.ts
rename to server/src/routes/captions.ts
index 7d00573..1ceb689 100644
--- a/client/src/app/api/projects/[id]/captions/route.ts
+++ b/server/src/routes/captions.ts
@@ -1,35 +1,30 @@
-import { NextResponse } from "next/server";
+import { Hono } from "hono";
import { prisma } from "@/lib/db/client";
import { generateCaptions } from "@/lib/captions/generate";
import { toSrt, toVtt } from "@/lib/captions/format";
+import { errorResponse } from "@/lib/http";
import type { Transcript } from "@/types/transcript";
import type { CutOperation, EditOperation } from "@/types/edit-operation";
-export const runtime = "nodejs";
-
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
+export const captionsRoute = new Hono();
/** Sidecar caption download -- GET /api/projects/:id/captions?format=srt|vtt */
-export async function GET(request: Request, { params }: RouteContext) {
- const { id } = await params;
- const format = new URL(request.url).searchParams.get("format") === "vtt" ? "vtt" : "srt";
+captionsRoute.get("/:id/captions", async (c) => {
+ const id = c.req.param("id");
+ const format = c.req.query("format") === "vtt" ? "vtt" : "srt";
const project = await prisma.project.findUnique({
where: { id },
include: { transcript: true, editOperations: true },
});
if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
}
if (!project.transcript) {
- return errorResponse("NO_TRANSCRIPT", "This project has no transcript yet.", 400);
+ return errorResponse(c, "NO_TRANSCRIPT", "This project has no transcript yet.", 400);
}
if (project.duration === null) {
- return errorResponse("NO_DURATION", "This project's video duration isn't known yet.", 400);
+ return errorResponse(c, "NO_DURATION", "This project's video duration isn't known yet.", 400);
}
const transcript: Transcript = {
@@ -44,11 +39,8 @@ export async function GET(request: Request, { params }: RouteContext) {
const body = format === "vtt" ? toVtt(cues) : toSrt(cues);
const filename = `${project.name.replace(/[^a-zA-Z0-9_-]/g, "_")}.${format}`;
- return new NextResponse(body, {
- status: 200,
- headers: {
- "Content-Type": format === "vtt" ? "text/vtt; charset=utf-8" : "application/x-subrip; charset=utf-8",
- "Content-Disposition": `attachment; filename="${filename}"`,
- },
+ return c.body(body, 200, {
+ "Content-Type": format === "vtt" ? "text/vtt; charset=utf-8" : "application/x-subrip; charset=utf-8",
+ "Content-Disposition": `attachment; filename="${filename}"`,
});
-}
+});
diff --git a/server/src/routes/filler-words.ts b/server/src/routes/filler-words.ts
new file mode 100644
index 0000000..1b541f0
--- /dev/null
+++ b/server/src/routes/filler-words.ts
@@ -0,0 +1,27 @@
+import { Hono } from "hono";
+import { logger } from "@/lib/logger";
+import { prisma } from "@/lib/db/client";
+import { detectFillerWords } from "@/lib/ai/filler-words";
+import { errorResponse } from "@/lib/http";
+import type { Transcript } from "@/types/transcript";
+
+export const fillerWordsRoute = new Hono();
+
+fillerWordsRoute.post("/:id/filler-words", async (c) => {
+ const id = c.req.param("id");
+
+ const record = await prisma.transcript.findUnique({ where: { projectId: id } });
+ if (!record) {
+ return errorResponse(c, "NO_TRANSCRIPT", "This project has no transcript yet.", 400);
+ }
+
+ const transcript: Transcript = { id: record.id, segments: JSON.parse(record.segmentsJson) };
+
+ try {
+ const fillerWordIds = await detectFillerWords(transcript);
+ return c.json({ success: true, fillerWordIds });
+ } catch (err) {
+ logger.error("Filler word detection failed:", err);
+ return errorResponse(c, "DETECTION_FAILED", "Could not analyze the transcript for filler words.", 502);
+ }
+});
diff --git a/client/src/app/api/projects/[id]/logo/route.ts b/server/src/routes/logo.ts
similarity index 52%
rename from client/src/app/api/projects/[id]/logo/route.ts
rename to server/src/routes/logo.ts
index 418c880..98a67ec 100644
--- a/client/src/app/api/projects/[id]/logo/route.ts
+++ b/server/src/routes/logo.ts
@@ -1,8 +1,9 @@
-import { NextResponse } from "next/server";
+import { Hono } from "hono";
import { prisma } from "@/lib/db/client";
-import { readAssetStream, statAsset, streamToWebReadable, writeAsset } from "@/lib/storage/local";
+import { resolveInDataDir, writeAsset } from "@/lib/storage/local";
+import { errorResponse } from "@/lib/http";
-export const runtime = "nodejs";
+export const logoRoute = new Hono();
const MAX_FILE_BYTES = 10 * 1024 * 1024; // 10MB local upload cap for a logo/watermark image
@@ -12,55 +13,43 @@ const MAX_FILE_BYTES = 10 * 1024 * 1024; // 10MB local upload cap for a logo/wat
// but fails the export with "no decoder found for: svg".
const SUPPORTED_LOGO_MIME_TYPES = new Set(["image/png", "image/jpeg", "image/webp", "image/gif"]);
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function GET(_request: Request, { params }: RouteContext) {
- const { id } = await params;
+logoRoute.get("/:id/logo", async (c) => {
+ const id = c.req.param("id");
const asset = await prisma.asset.findFirst({ where: { projectId: id, kind: "logo" } });
if (!asset) {
- return errorResponse("NOT_FOUND", "No logo for this project.", 404);
+ return errorResponse(c, "NOT_FOUND", "No logo for this project.", 404);
}
- const stats = await statAsset(asset.filePath);
- const stream = readAssetStream(asset.filePath);
- return new NextResponse(streamToWebReadable(stream), {
- status: 200,
- headers: {
- "Content-Type": asset.mimeType,
- "Content-Length": String(stats.size),
- },
- });
-}
+ const file = Bun.file(resolveInDataDir(asset.filePath));
+ return new Response(file, { headers: { "Content-Type": asset.mimeType } });
+});
-export async function POST(request: Request, { params }: RouteContext) {
- const { id } = await params;
+logoRoute.post("/:id/logo", async (c) => {
+ const id = c.req.param("id");
const project = await prisma.project.findUnique({ where: { id }, select: { id: true } });
if (!project) {
- return errorResponse("NOT_FOUND", "Project not found.", 404);
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
}
- let formData: FormData;
+ let body: Record;
try {
- formData = await request.formData();
+ body = await c.req.parseBody();
} catch {
- return errorResponse("INVALID_REQUEST", "Expected multipart/form-data.", 400);
+ return errorResponse(c, "INVALID_REQUEST", "Expected multipart/form-data.", 400);
}
- const file = formData.get("file");
+ const file = body.file;
if (!(file instanceof File)) {
- return errorResponse("MISSING_FILE", "No file was provided.", 400);
+ return errorResponse(c, "MISSING_FILE", "No file was provided.", 400);
}
if (file.size === 0) {
- return errorResponse("EMPTY_FILE", "The uploaded file is empty.", 400);
+ return errorResponse(c, "EMPTY_FILE", "The uploaded file is empty.", 400);
}
if (file.size > MAX_FILE_BYTES) {
return errorResponse(
+ c,
"FILE_TOO_LARGE",
`File exceeds the ${MAX_FILE_BYTES / (1024 * 1024)}MB upload limit.`,
413
@@ -68,6 +57,7 @@ export async function POST(request: Request, { params }: RouteContext) {
}
if (!SUPPORTED_LOGO_MIME_TYPES.has(file.type)) {
return errorResponse(
+ c,
"INVALID_FILE_TYPE",
"Only PNG, JPG, WEBP, or GIF images are supported (SVG can't be rendered into the export).",
400
@@ -89,22 +79,18 @@ export async function POST(request: Request, { params }: RouteContext) {
},
});
- return NextResponse.json({
- success: true,
- asset,
- logoUrl: `/api/projects/${id}/logo`,
- });
-}
+ return c.json({ success: true, asset, logoUrl: `/api/projects/${id}/logo` });
+});
-export async function DELETE(_request: Request, { params }: RouteContext) {
- const { id } = await params;
+logoRoute.delete("/:id/logo", async (c) => {
+ const id = c.req.param("id");
const asset = await prisma.asset.findFirst({ where: { projectId: id, kind: "logo" } });
if (!asset) {
- return errorResponse("NOT_FOUND", "No logo for this project.", 404);
+ return errorResponse(c, "NOT_FOUND", "No logo for this project.", 404);
}
await prisma.asset.delete({ where: { id: asset.id } });
- return NextResponse.json({ success: true });
-}
+ return c.json({ success: true });
+});
diff --git a/server/src/routes/operations.ts b/server/src/routes/operations.ts
new file mode 100644
index 0000000..ed7002b
--- /dev/null
+++ b/server/src/routes/operations.ts
@@ -0,0 +1,55 @@
+import { Hono } from "hono";
+import { prisma } from "@/lib/db/client";
+import { editOperationSchema } from "@/lib/validation/edit-operation";
+import { errorResponse } from "@/lib/http";
+
+export const operationsRoute = new Hono();
+
+operationsRoute.post("/:id/operations", async (c) => {
+ const projectId = c.req.param("id");
+
+ let body: unknown;
+ try {
+ body = await c.req.json();
+ } catch {
+ return errorResponse(c, "INVALID_REQUEST", "Expected a JSON body.", 400);
+ }
+
+ const parsed = editOperationSchema.safeParse(body);
+ if (!parsed.success) {
+ return errorResponse(c, "INVALID_OPERATION", parsed.error.issues[0]?.message ?? "Invalid edit operation.", 400);
+ }
+
+ const op = parsed.data;
+ if ((op.type === "cut" || op.type === "trim") && op.end <= op.start) {
+ return errorResponse(c, "INVALID_OPERATION", "end must be after start.", 400);
+ }
+
+ const project = await prisma.project.findUnique({ where: { id: projectId }, select: { id: true } });
+ if (!project) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+
+ // Client generates the id (needed so undo/redo can address the exact row);
+ // upsert so a redo that restores a previously-undone op is idempotent.
+ const saved = await prisma.editOperation.upsert({
+ where: { id: op.id },
+ create: { id: op.id, projectId, type: op.type, dataJson: JSON.stringify(op) },
+ update: { dataJson: JSON.stringify(op) },
+ });
+
+ return c.json({ success: true, operation: JSON.parse(saved.dataJson) }, 201);
+});
+
+operationsRoute.delete("/:id/operations/:operationId", async (c) => {
+ const projectId = c.req.param("id");
+ const operationId = c.req.param("operationId");
+
+ const op = await prisma.editOperation.findUnique({ where: { id: operationId } });
+ if (!op || op.projectId !== projectId) {
+ return errorResponse(c, "NOT_FOUND", "Operation not found.", 404);
+ }
+
+ await prisma.editOperation.delete({ where: { id: operationId } });
+ return c.json({ success: true });
+});
diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts
new file mode 100644
index 0000000..85d9f6a
--- /dev/null
+++ b/server/src/routes/projects.ts
@@ -0,0 +1,148 @@
+import { Hono } from "hono";
+import { prisma } from "@/lib/db/client";
+import { deleteProjectDir, ensureProjectDirs } from "@/lib/storage/local";
+import { createProjectSchema, updateProjectSchema } from "@/lib/validation/project";
+import { errorResponse } from "@/lib/http";
+import type { Project } from "@/types/project";
+import type { Transcript } from "@/types/transcript";
+import type { EditOperation } from "@/types/edit-operation";
+
+export const projectsRoute = new Hono();
+
+// GET /api/projects -- list, with the derived fields the dashboard needs
+// (hasVideo/hasTranscript/cutCount). The old Next app computed these only
+// inside its Server Component page, not this API route -- ported here now
+// that there's no server-rendered page to do it instead.
+projectsRoute.get("/", async (c) => {
+ const rows = await prisma.project.findMany({
+ orderBy: { updatedAt: "desc" },
+ include: {
+ assets: { select: { kind: true } },
+ transcript: { select: { id: true } },
+ _count: { select: { editOperations: true } },
+ },
+ });
+
+ const projects: Project[] = rows.map((p) => ({
+ id: p.id,
+ name: p.name,
+ duration: p.duration,
+ filterId: p.filterId,
+ createdAt: p.createdAt.toISOString(),
+ updatedAt: p.updatedAt.toISOString(),
+ hasVideo: p.assets.some((a) => a.kind === "original"),
+ hasTranscript: p.transcript !== null,
+ cutCount: p._count.editOperations,
+ }));
+
+ return c.json({ success: true, projects });
+});
+
+projectsRoute.post("/", async (c) => {
+ let body: unknown;
+ try {
+ body = await c.req.json();
+ } catch {
+ return errorResponse(c, "INVALID_REQUEST", "Expected a JSON body.", 400);
+ }
+
+ const parsed = createProjectSchema.safeParse(body);
+ if (!parsed.success) {
+ return errorResponse(c, "INVALID_PROJECT", parsed.error.issues[0]?.message ?? "Invalid project.", 400);
+ }
+
+ const project = await prisma.project.create({ data: { name: parsed.data.name } });
+ await ensureProjectDirs(project.id);
+
+ return c.json({ success: true, project }, 201);
+});
+
+projectsRoute.get("/:id", async (c) => {
+ const id = c.req.param("id");
+
+ const project = await prisma.project.findUnique({
+ where: { id },
+ include: {
+ assets: true,
+ transcript: true,
+ editOperations: { orderBy: { createdAt: "asc" } },
+ },
+ });
+
+ if (!project) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+
+ const transcript: Transcript | null = project.transcript
+ ? { id: project.transcript.id, segments: JSON.parse(project.transcript.segmentsJson) }
+ : null;
+
+ const operations: EditOperation[] = project.editOperations.map(
+ (op) => JSON.parse(op.dataJson) as EditOperation
+ );
+
+ const originalAsset = project.assets.find((a) => a.kind === "original") ?? null;
+ const logoAsset = project.assets.find((a) => a.kind === "logo") ?? null;
+
+ return c.json({
+ success: true,
+ project: {
+ id: project.id,
+ name: project.name,
+ duration: project.duration,
+ filterId: project.filterId,
+ burnInCaptions: project.burnInCaptions,
+ captionStyle: project.captionStyleJson ? JSON.parse(project.captionStyleJson) : null,
+ properties: project.propertiesJson ? JSON.parse(project.propertiesJson) : null,
+ logoPosition: project.logoPosition,
+ logoPaddingX: project.logoPaddingX,
+ logoPaddingY: project.logoPaddingY,
+ logoOpacity: project.logoOpacity,
+ createdAt: project.createdAt,
+ updatedAt: project.updatedAt,
+ },
+ transcript,
+ operations,
+ videoUrl: originalAsset ? `/api/projects/${project.id}/video` : null,
+ logoUrl: logoAsset ? `/api/projects/${project.id}/logo` : null,
+ });
+});
+
+projectsRoute.patch("/:id", async (c) => {
+ const id = c.req.param("id");
+
+ let body: unknown;
+ try {
+ body = await c.req.json();
+ } catch {
+ return errorResponse(c, "INVALID_REQUEST", "Expected a JSON body.", 400);
+ }
+
+ const parsed = updateProjectSchema.safeParse(body);
+ if (!parsed.success) {
+ return errorResponse(c, "INVALID_REQUEST", parsed.error.issues[0]?.message ?? "Invalid update.", 400);
+ }
+
+ const exists = await prisma.project.findUnique({ where: { id }, select: { id: true } });
+ if (!exists) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+
+ const project = await prisma.project.update({ where: { id }, data: parsed.data });
+
+ return c.json({ success: true, project });
+});
+
+projectsRoute.delete("/:id", async (c) => {
+ const id = c.req.param("id");
+
+ const exists = await prisma.project.findUnique({ where: { id }, select: { id: true } });
+ if (!exists) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+
+ await prisma.project.delete({ where: { id } });
+ await deleteProjectDir(id);
+
+ return c.json({ success: true });
+});
diff --git a/server/src/routes/render.ts b/server/src/routes/render.ts
new file mode 100644
index 0000000..f7919ac
--- /dev/null
+++ b/server/src/routes/render.ts
@@ -0,0 +1,90 @@
+import { Hono } from "hono";
+import { prisma } from "@/lib/db/client";
+import { runRenderJob } from "@/lib/ffmpeg/render-job";
+import { captionStyleSchema } from "@/lib/validation/caption-style";
+import { resolveInDataDir } from "@/lib/storage/local";
+import { errorResponse } from "@/lib/http";
+import type { CaptionStyle } from "@/lib/captions/style";
+
+export const renderRoute = new Hono();
+
+renderRoute.post("/:id/render", async (c) => {
+ const id = c.req.param("id");
+
+ let burnInCaptions = false;
+ let captionStyle: CaptionStyle | undefined;
+ try {
+ const body = await c.req.json();
+ burnInCaptions = body?.burnInCaptions === true;
+ if (burnInCaptions && body?.captionStyle !== undefined) {
+ const parsed = captionStyleSchema.safeParse(body.captionStyle);
+ if (!parsed.success) {
+ return errorResponse(c, "INVALID_CAPTION_STYLE", "Invalid caption style.", 400);
+ }
+ captionStyle = parsed.data;
+ }
+ } catch {
+ // No body (or invalid JSON) just means "no captions" -- not an error.
+ }
+
+ const project = await prisma.project.findUnique({ where: { id }, include: { assets: true } });
+ if (!project) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+ if (!project.assets.some((a) => a.kind === "original")) {
+ return errorResponse(c, "NO_VIDEO", "This project has no video to render.", 400);
+ }
+
+ const job = await prisma.renderJob.create({ data: { projectId: id, status: "queued" } });
+
+ // Fire-and-forget: never block the request on FFmpeg (spec section 14).
+ // No `waitUntil` needed here -- Bun.serve() is one long-lived process, not
+ // a request-scoped function, so this promise keeps running on the event
+ // loop after the response flushes. runRenderJob's own try/catch already
+ // swallows every error and writes status "failed", so an unhandled
+ // rejection here can't take the process down.
+ void runRenderJob(job.id, { burnInCaptions, captionStyle });
+
+ return c.json({ success: true, jobId: job.id }, 202);
+});
+
+renderRoute.get("/:id/render/:jobId", async (c) => {
+ const id = c.req.param("id");
+ const jobId = c.req.param("jobId");
+
+ const job = await prisma.renderJob.findUnique({ where: { id: jobId } });
+ if (!job || job.projectId !== id) {
+ return errorResponse(c, "NOT_FOUND", "Render job not found.", 404);
+ }
+
+ return c.json({
+ success: true,
+ job: {
+ id: job.id,
+ status: job.status,
+ error: job.error,
+ downloadUrl: job.status === "completed" ? `/api/projects/${id}/render/${jobId}/download` : null,
+ },
+ });
+});
+
+renderRoute.get("/:id/render/:jobId/download", async (c) => {
+ const id = c.req.param("id");
+ const jobId = c.req.param("jobId");
+
+ const job = await prisma.renderJob.findUnique({ where: { id: jobId } });
+ if (!job || job.projectId !== id || job.status !== "completed" || !job.outputPath) {
+ return errorResponse(c, "NOT_READY", "This render isn't ready to download.", 404);
+ }
+
+ const project = await prisma.project.findUnique({ where: { id }, select: { name: true } });
+ const filename = `${(project?.name ?? "video").replace(/[^a-zA-Z0-9_-]/g, "_")}.mp4`;
+
+ const file = Bun.file(resolveInDataDir(job.outputPath));
+ return new Response(file, {
+ headers: {
+ "Content-Type": "video/mp4",
+ "Content-Disposition": `attachment; filename="${filename}"`,
+ },
+ });
+});
diff --git a/server/src/routes/settings.ts b/server/src/routes/settings.ts
new file mode 100644
index 0000000..e00cca0
--- /dev/null
+++ b/server/src/routes/settings.ts
@@ -0,0 +1,41 @@
+import { Hono } from "hono";
+import OpenAI from "openai";
+import { getOpenAiApiKey, writeSettings } from "@/lib/settings";
+import { errorResponse } from "@/lib/http";
+
+export const settingsRoute = new Hono();
+
+// Never returns the key itself -- only whether one is configured. Checks
+// the same source getOpenAiApiKey() does (settings.json, falling back to
+// server/.env's OPENAI_API_KEY) -- not just settings.json alone -- so
+// local dev (where the key lives in .env, and no one has ever hit the
+// setup screen) correctly reports "configured" instead of being wrongly
+// redirected to setup.
+settingsRoute.get("/", async (c) => {
+ return c.json({ success: true, hasApiKey: Boolean(getOpenAiApiKey()) });
+});
+
+settingsRoute.post("/", async (c) => {
+ const body = await c.req.json().catch(() => null);
+ const key = body?.openaiApiKey;
+ if (typeof key !== "string" || !key.trim()) {
+ return errorResponse(c, "INVALID_KEY", "Enter an OpenAI API key.", 400);
+ }
+
+ // Validate against the real API before persisting -- a typo'd key
+ // should fail here, immediately and clearly, not surface later as an
+ // opaque transcription failure with no indication it's a key problem.
+ try {
+ await new OpenAI({ apiKey: key }).models.list();
+ } catch {
+ return errorResponse(
+ c,
+ "INVALID_KEY",
+ "That key doesn't seem to work. Double-check it and try again.",
+ 400
+ );
+ }
+
+ writeSettings({ openaiApiKey: key });
+ return c.json({ success: true });
+});
diff --git a/client/src/app/api/projects/[id]/transcribe/route.ts b/server/src/routes/transcribe.ts
similarity index 60%
rename from client/src/app/api/projects/[id]/transcribe/route.ts
rename to server/src/routes/transcribe.ts
index c1835f4..69e582a 100644
--- a/client/src/app/api/projects/[id]/transcribe/route.ts
+++ b/server/src/routes/transcribe.ts
@@ -1,29 +1,26 @@
-import { NextResponse } from "next/server";
+import { Hono } from "hono";
import { readFile } from "node:fs/promises";
import path from "node:path";
+import { logger } from "@/lib/logger";
import { prisma } from "@/lib/db/client";
import { resolveInDataDir } from "@/lib/storage/local";
import { transcribeFile } from "@/lib/ai/transcribe";
+import { errorResponse } from "@/lib/http";
-export const runtime = "nodejs";
+export const transcribeRoute = new Hono();
const MAX_TRANSCRIBE_BYTES = 25 * 1024 * 1024; // Whisper API limit
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function POST(_request: Request, { params }: RouteContext) {
- const { id } = await params;
+transcribeRoute.post("/:id/transcribe", async (c) => {
+ const id = c.req.param("id");
const asset = await prisma.asset.findFirst({ where: { projectId: id, kind: "original" } });
if (!asset) {
- return errorResponse("NO_VIDEO", "Upload a video before transcribing.", 400);
+ return errorResponse(c, "NO_VIDEO", "Upload a video before transcribing.", 400);
}
if (asset.sizeBytes > MAX_TRANSCRIBE_BYTES) {
return errorResponse(
+ c,
"FILE_TOO_LARGE",
`Video exceeds the ${MAX_TRANSCRIBE_BYTES / (1024 * 1024)}MB transcription limit.`,
413
@@ -44,13 +41,9 @@ export async function POST(_request: Request, { params }: RouteContext) {
update: { segmentsJson: JSON.stringify(transcript.segments) },
});
- return NextResponse.json({ success: true, transcript });
+ return c.json({ success: true, transcript });
} catch (err) {
- console.error("Transcription failed:", err);
- return errorResponse(
- "TRANSCRIPTION_FAILED",
- "The video could not be transcribed. Please try again.",
- 502
- );
+ logger.error("Transcription failed:", err);
+ return errorResponse(c, "TRANSCRIPTION_FAILED", "The video could not be transcribed. Please try again.", 502);
}
-}
+});
diff --git a/client/src/app/api/projects/[id]/transcript/route.ts b/server/src/routes/transcript.ts
similarity index 50%
rename from client/src/app/api/projects/[id]/transcript/route.ts
rename to server/src/routes/transcript.ts
index dae8f25..1693fc2 100644
--- a/client/src/app/api/projects/[id]/transcript/route.ts
+++ b/server/src/routes/transcript.ts
@@ -1,53 +1,48 @@
-import { NextResponse } from "next/server";
+import { Hono } from "hono";
import { prisma } from "@/lib/db/client";
import { updateSpeakerSchema } from "@/lib/validation/transcript";
+import { errorResponse } from "@/lib/http";
import type { Transcript } from "@/types/transcript";
-export const runtime = "nodejs";
+export const transcriptRoute = new Hono();
-function errorResponse(code: string, message: string, status: number) {
- return NextResponse.json({ success: false, error: { code, message } }, { status });
-}
-
-type RouteContext = { params: Promise<{ id: string }> };
-
-export async function GET(_request: Request, { params }: RouteContext) {
- const { id } = await params;
+transcriptRoute.get("/:id/transcript", async (c) => {
+ const id = c.req.param("id");
const record = await prisma.transcript.findUnique({ where: { projectId: id } });
if (!record) {
- return errorResponse("NOT_FOUND", "This project has no transcript yet.", 404);
+ return errorResponse(c, "NOT_FOUND", "This project has no transcript yet.", 404);
}
const transcript: Transcript = { id: record.id, segments: JSON.parse(record.segmentsJson) };
- return NextResponse.json({ success: true, transcript });
-}
+ return c.json({ success: true, transcript });
+});
/** Assigns or clears the speaker label on one transcript segment. */
-export async function PATCH(request: Request, { params }: RouteContext) {
- const { id } = await params;
+transcriptRoute.patch("/:id/transcript", async (c) => {
+ const id = c.req.param("id");
let body: unknown;
try {
- body = await request.json();
+ body = await c.req.json();
} catch {
- return errorResponse("INVALID_REQUEST", "Expected a JSON body.", 400);
+ return errorResponse(c, "INVALID_REQUEST", "Expected a JSON body.", 400);
}
const parsed = updateSpeakerSchema.safeParse(body);
if (!parsed.success) {
- return errorResponse("INVALID_REQUEST", parsed.error.issues[0]?.message ?? "Invalid request.", 400);
+ return errorResponse(c, "INVALID_REQUEST", parsed.error.issues[0]?.message ?? "Invalid request.", 400);
}
const record = await prisma.transcript.findUnique({ where: { projectId: id } });
if (!record) {
- return errorResponse("NOT_FOUND", "This project has no transcript yet.", 404);
+ return errorResponse(c, "NOT_FOUND", "This project has no transcript yet.", 404);
}
const transcript: Transcript = { id: record.id, segments: JSON.parse(record.segmentsJson) };
const segment = transcript.segments.find((s) => s.id === parsed.data.segmentId);
if (!segment) {
- return errorResponse("SEGMENT_NOT_FOUND", "That transcript segment doesn't exist.", 404);
+ return errorResponse(c, "SEGMENT_NOT_FOUND", "That transcript segment doesn't exist.", 404);
}
if (parsed.data.speaker === null) {
@@ -61,5 +56,5 @@ export async function PATCH(request: Request, { params }: RouteContext) {
data: { segmentsJson: JSON.stringify(transcript.segments) },
});
- return NextResponse.json({ success: true, transcript });
-}
+ return c.json({ success: true, transcript });
+});
diff --git a/server/src/routes/upload.ts b/server/src/routes/upload.ts
new file mode 100644
index 0000000..05b2d7e
--- /dev/null
+++ b/server/src/routes/upload.ts
@@ -0,0 +1,72 @@
+import { Hono } from "hono";
+import { prisma } from "@/lib/db/client";
+import { writeAssetStream } from "@/lib/storage/local";
+import { errorResponse } from "@/lib/http";
+
+export const uploadRoute = new Hono();
+
+const MAX_FILE_BYTES = 500 * 1024 * 1024; // 500MB local upload cap
+
+/**
+ * POST /api/projects/:id/upload?filename=video.mp4
+ *
+ * Deliberate wire-contract change from the old Next route: the request
+ * body is the raw video bytes (Content-Type: video/*), not
+ * multipart/form-data. This lets the server stream straight to disk via
+ * Bun.write(path, request.body) without ever buffering the whole file in
+ * memory -- the old route's request.formData()/file.arrayBuffer() approach
+ * held the entire upload in RAM, a real problem for large videos.
+ */
+uploadRoute.post("/:id/upload", async (c) => {
+ const id = c.req.param("id");
+
+ const project = await prisma.project.findUnique({ where: { id }, select: { id: true } });
+ if (!project) {
+ return errorResponse(c, "NOT_FOUND", "Project not found.", 404);
+ }
+
+ const filename = c.req.query("filename");
+ if (!filename) {
+ return errorResponse(c, "MISSING_FILENAME", "A ?filename= query param is required.", 400);
+ }
+
+ const contentType = c.req.header("content-type") ?? "";
+ if (!contentType.startsWith("video/")) {
+ return errorResponse(c, "INVALID_FILE_TYPE", "Only video files are supported.", 400);
+ }
+
+ const contentLengthHeader = c.req.header("content-length");
+ const contentLength = contentLengthHeader ? Number(contentLengthHeader) : NaN;
+ if (!Number.isFinite(contentLength) || contentLength <= 0) {
+ return errorResponse(c, "MISSING_CONTENT_LENGTH", "A Content-Length header is required.", 400);
+ }
+ if (contentLength > MAX_FILE_BYTES) {
+ return errorResponse(
+ c,
+ "FILE_TOO_LARGE",
+ `File exceeds the ${MAX_FILE_BYTES / (1024 * 1024)}MB upload limit.`,
+ 413
+ );
+ }
+
+ const body = c.req.raw.body;
+ if (!body) {
+ return errorResponse(c, "EMPTY_FILE", "The uploaded file is empty.", 400);
+ }
+
+ const { relativePath, sizeBytes } = await writeAssetStream(id, "original", filename, body);
+
+ // Only one "original" asset per project for v1 -- replace any previous one.
+ await prisma.asset.deleteMany({ where: { projectId: id, kind: "original" } });
+ const asset = await prisma.asset.create({
+ data: {
+ projectId: id,
+ kind: "original",
+ filePath: relativePath,
+ mimeType: contentType,
+ sizeBytes,
+ },
+ });
+
+ return c.json({ success: true, asset, videoUrl: `/api/projects/${id}/video` });
+});
diff --git a/server/src/routes/video.ts b/server/src/routes/video.ts
new file mode 100644
index 0000000..28682ac
--- /dev/null
+++ b/server/src/routes/video.ts
@@ -0,0 +1,24 @@
+import { Hono } from "hono";
+import { prisma } from "@/lib/db/client";
+import { resolveInDataDir } from "@/lib/storage/local";
+import { errorResponse } from "@/lib/http";
+
+export const videoRoute = new Hono();
+
+// Bun.file() returned directly as a Response body natively handles the
+// incoming Range header (206 + Content-Range/Accept-Ranges/Content-Length,
+// or a full 200 with no Range header) -- verified empirically against Bun
+// v1.4.2. No manual range-regex parsing needed, unlike the old Next route.
+videoRoute.get("/:id/video", async (c) => {
+ const id = c.req.param("id");
+
+ const asset = await prisma.asset.findFirst({ where: { projectId: id, kind: "original" } });
+ if (!asset) {
+ return errorResponse(c, "NOT_FOUND", "No video for this project.", 404);
+ }
+
+ const file = Bun.file(resolveInDataDir(asset.filePath));
+ return new Response(file, {
+ headers: { "Content-Type": asset.mimeType, "Accept-Ranges": "bytes" },
+ });
+});
diff --git a/server/src/types/edit-operation.ts b/server/src/types/edit-operation.ts
new file mode 100644
index 0000000..ecdfa4c
--- /dev/null
+++ b/server/src/types/edit-operation.ts
@@ -0,0 +1,32 @@
+export type EditOperation =
+ | {
+ id: string;
+ type: "cut";
+ start: number;
+ end: number;
+ reason?: string;
+ createdAt: number;
+ }
+ | {
+ id: string;
+ type: "trim";
+ start: number;
+ end: number;
+ createdAt: number;
+ }
+ | {
+ id: string;
+ type: "split";
+ timestamp: number;
+ createdAt: number;
+ }
+ | {
+ id: string;
+ type: "caption";
+ text: string;
+ start: number;
+ end: number;
+ createdAt: number;
+ };
+
+export type CutOperation = Extract;
diff --git a/server/src/types/filter.ts b/server/src/types/filter.ts
new file mode 100644
index 0000000..354292d
--- /dev/null
+++ b/server/src/types/filter.ts
@@ -0,0 +1,6 @@
+export type FilterPreset = {
+ id: string;
+ label: string;
+ /** CSS `filter` value applied to the video element for preview. */
+ css: string;
+};
diff --git a/server/src/types/project.ts b/server/src/types/project.ts
new file mode 100644
index 0000000..91394ac
--- /dev/null
+++ b/server/src/types/project.ts
@@ -0,0 +1,11 @@
+export type Project = {
+ id: string;
+ name: string;
+ duration: number | null;
+ filterId: string;
+ createdAt: string;
+ updatedAt: string;
+ hasVideo: boolean;
+ hasTranscript: boolean;
+ cutCount: number;
+};
diff --git a/server/src/types/timeline.ts b/server/src/types/timeline.ts
new file mode 100644
index 0000000..749066f
--- /dev/null
+++ b/server/src/types/timeline.ts
@@ -0,0 +1,5 @@
+/** A contiguous range of the *source* video, in seconds, that survives all cuts. */
+export type PlayableRange = {
+ start: number;
+ end: number;
+};
diff --git a/server/src/types/transcript.ts b/server/src/types/transcript.ts
new file mode 100644
index 0000000..3a46293
--- /dev/null
+++ b/server/src/types/transcript.ts
@@ -0,0 +1,21 @@
+export type TranscriptWord = {
+ id: string;
+ text: string;
+ start: number;
+ end: number;
+};
+
+export type TranscriptSegment = {
+ id: string;
+ start: number;
+ end: number;
+ text: string;
+ words: TranscriptWord[];
+ /** Manually assigned speaker name (claude.md section 10). Unset until the user labels it. */
+ speaker?: string;
+};
+
+export type Transcript = {
+ id: string;
+ segments: TranscriptSegment[];
+};
diff --git a/server/src/types/video-properties.ts b/server/src/types/video-properties.ts
new file mode 100644
index 0000000..3b5b38d
--- /dev/null
+++ b/server/src/types/video-properties.ts
@@ -0,0 +1,34 @@
+/**
+ * Manual color-adjustment sliders, layered on top of the selected filter
+ * preset (lib/video/filters.ts). Every value is a -100..100 percentage
+ * around a neutral 0 (no change) -- matches how the sliders read on screen.
+ */
+export type VideoProperties = {
+ saturation: number;
+ temperature: number;
+ tint: number;
+ exposure: number;
+ contrast: number;
+ highlights: number;
+ shadows: number;
+};
+
+export const DEFAULT_VIDEO_PROPERTIES: VideoProperties = {
+ saturation: 0,
+ temperature: 0,
+ tint: 0,
+ exposure: 0,
+ contrast: 0,
+ highlights: 0,
+ shadows: 0,
+};
+
+export const VIDEO_PROPERTY_FIELDS: { key: keyof VideoProperties; label: string; section: "Color" | "Light" }[] = [
+ { key: "saturation", label: "Saturation", section: "Color" },
+ { key: "temperature", label: "Temperature", section: "Color" },
+ { key: "tint", label: "Tint", section: "Color" },
+ { key: "exposure", label: "Exposure", section: "Light" },
+ { key: "contrast", label: "Contrast", section: "Light" },
+ { key: "highlights", label: "Highlights", section: "Light" },
+ { key: "shadows", label: "Shadows", section: "Light" },
+];
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..1dcde84
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "lib": ["ESNext"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "types": ["bun"],
+ "strict": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": ["src/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/server/vitest.config.mts b/server/vitest.config.mts
new file mode 100644
index 0000000..105083c
--- /dev/null
+++ b/server/vitest.config.mts
@@ -0,0 +1,18 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ resolve: {
+ tsconfigPaths: true,
+ },
+ test: {
+ environment: "node",
+ include: ["src/**/*.test.ts"],
+ env: {
+ // lib/ai/filler-words.ts constructs its OpenAI client at module load
+ // time, which throws without a key -- this dummy value only needs to
+ // exist, never a real one, since the tests that import it only
+ // exercise the deterministic path that returns before any API call.
+ OPENAI_API_KEY: "test-key-not-used",
+ },
+ },
+});