diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..9e8b671
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+.git
+node_modules
+**/node_modules
+**/dist
+data
+docs
+mockups
+tools
+.github
+*.md
+!README.md
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..1b20c6c
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,35 @@
+name: ci
+
+on:
+ push:
+ branches: [main]
+ pull_request:
+
+jobs:
+ backend:
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: apps/backend
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
+ with:
+ go-version-file: apps/backend/go.mod
+ cache-dependency-path: apps/backend/go.sum
+ - run: go build ./...
+ - run: go vet ./...
+ - run: go test ./...
+
+ frontend:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 24
+ cache: npm
+ - run: npm ci
+ - run: npm run typecheck
+ - run: npm run lint
+ - run: npm run build
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b833715
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# deps / builds
+node_modules/
+apps/frontend/dist/
+packages/api-client/dist/
+packages/api-client/src/schema.d.ts
+apps/backend/internal/web/dist/*
+!apps/backend/internal/web/dist/.keep
+!apps/backend/internal/web/dist/index.html
+
+# data / runtime
+data/*
+!data/.keep
+*.db
+*.db-*
+
+# env / secrets
+.env
+.env.*
+
+# misc
+.DS_Store
+coverage/
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..2904e03
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,37 @@
+# Dash
+
+Self-hosted homelab dashboard. Single Go binary serving an embedded React UI.
+No auth, no YAML. See `ROADMAP.md` (canonical) and `docs/specs/` for design.
+
+## Layout
+
+- `apps/frontend` — React 18 + Vite + TS strict + Tailwind v4 + shadcn/ui
+- `apps/backend` — Go + Gin + zap, CGO-free, SQLite via `modernc.org/sqlite`
+- `packages/api-client` — generated TS client; `openapi.yaml` at repo root is the contract
+- `infra/` — Dockerfile (multi-stage, one image)
+- `tools/oxlint/anti-slop` — vendored lint plugin (do not extend casually)
+- `data/` — runtime SQLite + uploaded icons (gitignored)
+
+## Commands
+
+```sh
+npm install
+npm run generate # regenerate TS client from openapi.yaml
+npm run dev # vite dev server (proxies /api -> :8080)
+npm run typecheck # tsc --noEmit
+npm run lint # oxlint + anti-slop rules
+npm run build # api-client types + frontend bundle
+
+cd apps/backend
+go build ./... # build backend
+go test ./... # tests
+DASH_DEV=1 go run ./cmd/dash # dev server on :8080, no embedded UI needed
+```
+
+## Rules
+
+- OpenAPI is the API contract. Never hand-write API types in the frontend.
+- All schema changes go through goose migrations in `apps/backend/internal/db/migrations`.
+- Board renderers share state/dnd/popover primitives; never reimplement them per renderer.
+- No emojis in code or UI.
+- Before pushing: `npm run typecheck`, `npm run lint`, `npm run build`, `go build/vet/test ./...` all green.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..2c19f5c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 TDvorak
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..6aefdcb
--- /dev/null
+++ b/README.md
@@ -0,0 +1,46 @@
+# Dash
+
+A self-hosted homelab dashboard. CasaOS-grade ease of use, Vercel-grade design.
+Single container, UI-managed, no YAML, no auth.
+
+**Status:** pre-release. See [ROADMAP.md](ROADMAP.md).
+
+## Run
+
+```sh
+docker run -v ./data:/data -p 3000:3000 dash
+```
+
+or
+
+```sh
+docker compose up
+```
+
+Then open http://localhost:3000.
+
+## Develop
+
+Requires Go 1.24+ and Node 20+.
+
+```sh
+npm install # workspaces: frontend + api-client
+npm run generate # regenerate TS client from openapi.yaml
+npm run dev # vite dev on :5173, proxies /api to :8080
+```
+
+In another shell:
+
+```sh
+cd apps/backend
+DASH_DEV=1 go run ./cmd/dash # API on :8080 (DASH_DEV skips embedded UI)
+```
+
+## Stack
+
+Go + Gin + SQLite (single static binary, embeds the frontend) · React 18 +
+Vite + TS strict + Tailwind + shadcn/ui + dnd-kit · OpenAPI-generated client.
+
+## License
+
+[MIT](LICENSE)
diff --git a/ROADMAP.md b/ROADMAP.md
index fbd4e27..eede592 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -39,20 +39,20 @@ or popover logic.
---
-## Phase 0 — Foundation → part of v0.1.0
+## Phase 0 — Foundation → part of v0.1.0 ✅ done 2026-09-17
Repo hygiene and pipelines before features.
-- [ ] Monorepo scaffold (`apps/frontend`, `apps/backend`, `packages/api-client`, `infra/`, `data/`)
-- [ ] MIT `LICENSE`, `README.md`, `.gitignore`, `AGENTS.md`
-- [ ] `openapi.yaml` stub → generated TS client (`packages/api-client`)
-- [ ] Backend skeleton: Gin router, `zap` logging, `/api/healthz`, SQLite open + goose migrations runner
-- [ ] Frontend skeleton: Vite + TS strict + Tailwind + shadcn/ui, theme tokens matching `d-bento-mono.html` CSS vars
-- [ ] CI (GitHub Actions): `go build ./...`, `go vet ./...`, `go test ./...`, `tsc --noEmit`, `oxlint` + vendored anti-slop rules, `npm run build`
-- [ ] Multi-stage `Dockerfile` (frontend build → Go build → `FROM scratch`/distroless, `embed.FS` serves UI)
-- [ ] `docker-compose.yml` — one service, `./data:/data` volume
+- [x] Monorepo scaffold (`apps/frontend`, `apps/backend`, `packages/api-client`, `infra/`, `data/`)
+- [x] MIT `LICENSE`, `README.md`, `.gitignore`, `AGENTS.md`
+- [x] `openapi.yaml` stub → generated TS client (`packages/api-client`)
+- [x] Backend skeleton: Gin router, `zap` logging, `/api/healthz`, SQLite open + goose migrations runner
+- [x] Frontend skeleton: Vite + TS strict + Tailwind + shadcn/ui, theme tokens matching `d-bento-mono.html` CSS vars
+- [x] CI (GitHub Actions): `go build ./...`, `go vet ./...`, `go test ./...`, `tsc --noEmit`, `oxlint` + vendored anti-slop rules, `npm run build`
+- [x] Multi-stage `Dockerfile` (frontend build → Go build → distroless, `embed.FS` serves UI)
+- [x] `docker-compose.yml` — one service, `./data:/data` volume
-**Exit:** `docker compose up` serves a blank board. CI green on `main`.
+**Exit:** `docker compose up` serves a blank board — verified locally (33.6 MB image). CI green on `main` — pending first push.
## Phase 1 — MVP → v0.1.0
diff --git a/apps/backend/cmd/dash/main.go b/apps/backend/cmd/dash/main.go
new file mode 100644
index 0000000..68e1c15
--- /dev/null
+++ b/apps/backend/cmd/dash/main.go
@@ -0,0 +1,76 @@
+// Command dash is the single-binary entrypoint: API + embedded UI.
+package main
+
+import (
+ "context"
+ "errors"
+ "net/http"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "syscall"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+
+ "github.com/tdvorak/dash/internal/api"
+ "github.com/tdvorak/dash/internal/db"
+ "github.com/tdvorak/dash/internal/web"
+)
+
+func main() {
+ dev := os.Getenv("DASH_DEV") == "1"
+
+ var logger *zap.Logger
+ if dev {
+ logger = zap.Must(zap.NewDevelopment())
+ } else {
+ gin.SetMode(gin.ReleaseMode)
+ logger = zap.Must(zap.NewProduction())
+ }
+ defer func() { _ = logger.Sync() }()
+
+ dataDir := envOr("DASH_DATA_DIR", "./data")
+ if err := os.MkdirAll(filepath.Join(dataDir, "icons"), 0o755); err != nil {
+ logger.Fatal("create data dir", zap.Error(err))
+ }
+
+ sqlDB, err := db.Open(filepath.Join(dataDir, "dash.db"))
+ if err != nil {
+ logger.Fatal("open database", zap.Error(err))
+ }
+ defer func() { _ = sqlDB.Close() }()
+
+ router := api.NewRouter(logger, sqlDB)
+ if err := web.Register(router); err != nil {
+ logger.Fatal("register embedded UI", zap.Error(err))
+ }
+
+ addr := ":" + envOr("DASH_PORT", "8080")
+ srv := &http.Server{Addr: addr, Handler: router, ReadHeaderTimeout: 10 * time.Second}
+
+ ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+ defer stop()
+
+ go func() {
+ logger.Info("listening", zap.String("addr", addr), zap.String("data", dataDir))
+ if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
+ logger.Fatal("serve", zap.Error(err))
+ }
+ }()
+
+ <-ctx.Done()
+ shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ if err := srv.Shutdown(shutdownCtx); err != nil {
+ logger.Error("shutdown", zap.Error(err))
+ }
+}
+
+func envOr(key, fallback string) string {
+ if v := os.Getenv(key); v != "" {
+ return v
+ }
+ return fallback
+}
diff --git a/apps/backend/go.mod b/apps/backend/go.mod
new file mode 100644
index 0000000..8c8078a
--- /dev/null
+++ b/apps/backend/go.mod
@@ -0,0 +1,53 @@
+module github.com/tdvorak/dash
+
+go 1.27
+
+require (
+ github.com/gin-gonic/gin v1.12.0
+ github.com/pressly/goose/v3 v3.28.0
+ go.uber.org/zap v1.28.0
+ modernc.org/sqlite v1.59.0
+)
+
+require (
+ github.com/bytedance/gopkg v0.1.3 // indirect
+ github.com/bytedance/sonic v1.15.0 // indirect
+ github.com/bytedance/sonic/loader v0.5.0 // indirect
+ github.com/cloudwego/base64x v0.1.6 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.12 // indirect
+ github.com/gin-contrib/sse v1.1.0 // indirect
+ github.com/go-playground/locales v0.14.1 // indirect
+ github.com/go-playground/universal-translator v0.18.1 // indirect
+ github.com/go-playground/validator/v10 v10.30.1 // indirect
+ github.com/goccy/go-json v0.10.5 // indirect
+ github.com/goccy/go-yaml v1.19.2 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/klauspost/cpuid/v2 v2.3.0 // indirect
+ github.com/leodido/go-urn v1.4.0 // indirect
+ github.com/mattn/go-isatty v0.0.24 // indirect
+ github.com/mfridman/interpolate v0.0.2 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/ncruces/go-strftime v1.0.0 // indirect
+ github.com/pelletier/go-toml/v2 v2.2.4 // indirect
+ github.com/quic-go/qpack v0.6.0 // indirect
+ github.com/quic-go/quic-go v0.59.0 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/sethvargo/go-retry v0.4.0 // indirect
+ github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+ github.com/ugorji/go/codec v1.3.1 // indirect
+ go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ golang.org/x/arch v0.22.0 // indirect
+ golang.org/x/crypto v0.55.0 // indirect
+ golang.org/x/net v0.58.0 // indirect
+ golang.org/x/sync v0.22.0 // indirect
+ golang.org/x/sys v0.47.0 // indirect
+ golang.org/x/text v0.41.0 // indirect
+ google.golang.org/protobuf v1.36.12 // indirect
+ modernc.org/libc v1.75.7 // indirect
+ modernc.org/mathutil v1.7.1 // indirect
+ modernc.org/memory v1.12.1 // indirect
+)
diff --git a/apps/backend/go.sum b/apps/backend/go.sum
new file mode 100644
index 0000000..11d520f
--- /dev/null
+++ b/apps/backend/go.sum
@@ -0,0 +1,146 @@
+github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
+github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
+github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
+github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k=
+github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE=
+github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
+github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
+github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
+github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
+github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
+github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
+github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
+github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
+github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
+github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
+github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
+github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
+github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
+github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
+github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
+github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
+github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo=
+github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
+github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
+github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
+github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
+github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
+github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI=
+github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
+github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
+github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
+github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
+github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/pressly/goose/v3 v3.28.0 h1:D2M+iL31GmpZxSHOhX8mqyqAT3CXnokUmm0eKoSP+Vc=
+github.com/pressly/goose/v3 v3.28.0/go.mod h1:v26MOuB8bL3kzzrt3Vqhb3R0PRVsl8hFQKdrht/L6Rk=
+github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
+github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
+github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
+github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/sethvargo/go-retry v0.4.0 h1:9qy1OoIAxBL+gBYnkTnTnWle5wlfsXQlwRzIbbpdqPw=
+github.com/sethvargo/go-retry v0.4.0/go.mod h1:tvsjdKG6xfiCx4LSiUZ06kcv38xvdVQwv8R6/VnnVWg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
+github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
+github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
+github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
+github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
+github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
+github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
+go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE=
+go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
+go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
+go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
+go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
+go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
+go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
+go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
+go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
+golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI=
+golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
+golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
+golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
+golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
+golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
+golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
+golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
+golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
+golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
+golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
+golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
+golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
+google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
+google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+modernc.org/cc/v4 v4.29.2 h1:h6+9ciCnPKutf4I03CvheAvDLX7+IHlqR6Iy6J+cgd8=
+modernc.org/cc/v4 v4.29.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
+modernc.org/ccgo/v4 v4.35.0 h1:F+TUsmw09QxLzmi3aeYYGxjAXarmZaKgj3mKQHNaA8w=
+modernc.org/ccgo/v4 v4.35.0/go.mod h1:qrVGs9S3Sr2Ztcg9ve+kTAYMp5a3YvWjo+SoN06kJ5I=
+modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
+modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
+modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
+modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
+modernc.org/gc/v3 v3.1.5 h1:21ldfPfRYE31Tb7B3mwAK8gy1AxP4+dKjrOQPfqakoc=
+modernc.org/gc/v3 v3.1.5/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
+modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
+modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
+modernc.org/libc v1.75.7 h1:o3DTP9/0p9pKmY2WCKQaySW6wIiZhNM7wc2lUoyhfew=
+modernc.org/libc v1.75.7/go.mod h1:bO5o2ztHxBb2rjz0PgdHN0sSMw57CgxGFLZ3Qd/QpVQ=
+modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
+modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
+modernc.org/memory v1.12.1 h1:nFMiWrpStgZczNl6XI9GnIk/rWhYIyHGUaR04pGbp9g=
+modernc.org/memory v1.12.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
+modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
+modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
+modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
+modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
+modernc.org/sqlite v1.59.0 h1:X1es1GpqBlS/5T+vbM4HLUdaa8OtQx468DF2vrx+38A=
+modernc.org/sqlite v1.59.0/go.mod h1:+paeT2A3iPRHkQDwG7oA6Tk0zQd5woMEI8q7orfry8k=
+modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
+modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
+modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
+modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
diff --git a/apps/backend/internal/api/api.go b/apps/backend/internal/api/api.go
new file mode 100644
index 0000000..3913a08
--- /dev/null
+++ b/apps/backend/internal/api/api.go
@@ -0,0 +1,42 @@
+// Package api wires the Gin router and /api handlers.
+package api
+
+import (
+ "database/sql"
+ "net/http"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "go.uber.org/zap"
+)
+
+// NewRouter builds the HTTP handler: zap access log, recovery, /api routes.
+func NewRouter(logger *zap.Logger, db *sql.DB) *gin.Engine {
+ r := gin.New()
+ r.Use(accessLog(logger), gin.Recovery())
+
+ v1 := r.Group("/api")
+ v1.GET("/healthz", func(c *gin.Context) {
+ if err := db.PingContext(c.Request.Context()); err != nil {
+ c.JSON(http.StatusServiceUnavailable, gin.H{"status": "db unreachable"})
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{"status": "ok"})
+ })
+
+ return r
+}
+
+// accessLog is gin.Logger backed by zap: one structured line per request.
+func accessLog(logger *zap.Logger) gin.HandlerFunc {
+ return func(c *gin.Context) {
+ start := time.Now()
+ c.Next()
+ logger.Info("request",
+ zap.String("method", c.Request.Method),
+ zap.String("path", c.Request.URL.Path),
+ zap.Int("status", c.Writer.Status()),
+ zap.Duration("latency", time.Since(start)),
+ )
+ }
+}
diff --git a/apps/backend/internal/db/db.go b/apps/backend/internal/db/db.go
new file mode 100644
index 0000000..ab2c143
--- /dev/null
+++ b/apps/backend/internal/db/db.go
@@ -0,0 +1,33 @@
+// Package db opens the SQLite database and applies goose migrations.
+package db
+
+import (
+ "database/sql"
+ "embed"
+ "fmt"
+
+ "github.com/pressly/goose/v3"
+ _ "modernc.org/sqlite"
+)
+
+//go:embed migrations/*.sql
+var migrations embed.FS
+
+// Open opens (creating if needed) the SQLite file at path and migrates it
+// to the latest schema. WAL + busy_timeout keep reads snappy under writes.
+func Open(path string) (*sql.DB, error) {
+ dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)", path)
+ sqlDB, err := sql.Open("sqlite", dsn)
+ if err != nil {
+ return nil, fmt.Errorf("open sqlite: %w", err)
+ }
+
+ goose.SetBaseFS(migrations)
+ if err := goose.SetDialect("sqlite3"); err != nil {
+ return nil, fmt.Errorf("goose dialect: %w", err)
+ }
+ if err := goose.Up(sqlDB, "migrations"); err != nil {
+ return nil, fmt.Errorf("migrate: %w", err)
+ }
+ return sqlDB, nil
+}
diff --git a/apps/backend/internal/db/migrations/0001_init.sql b/apps/backend/internal/db/migrations/0001_init.sql
new file mode 100644
index 0000000..0775a5f
--- /dev/null
+++ b/apps/backend/internal/db/migrations/0001_init.sql
@@ -0,0 +1,9 @@
+-- +goose Up
+-- Key/value store for user prefs (theme, renderer, ...). Values are JSON.
+CREATE TABLE settings (
+ key TEXT PRIMARY KEY,
+ value JSON NOT NULL
+);
+
+-- +goose Down
+DROP TABLE settings;
diff --git a/apps/backend/internal/web/dist/.keep b/apps/backend/internal/web/dist/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/apps/backend/internal/web/dist/index.html b/apps/backend/internal/web/dist/index.html
new file mode 100644
index 0000000..eaf7eb0
--- /dev/null
+++ b/apps/backend/internal/web/dist/index.html
@@ -0,0 +1 @@
+
DashDash API is running. Frontend build is not embedded in this binary.
diff --git a/apps/backend/internal/web/web.go b/apps/backend/internal/web/web.go
new file mode 100644
index 0000000..78bbccb
--- /dev/null
+++ b/apps/backend/internal/web/web.go
@@ -0,0 +1,41 @@
+// Package web serves the embedded frontend build (SPA with index.html
+// fallback). dist/ is populated by the frontend build in Docker/CI; the
+// committed .keep keeps //go:embed satisfied on a bare clone.
+package web
+
+import (
+ "embed"
+ "io/fs"
+ "net/http"
+
+ "github.com/gin-gonic/gin"
+)
+
+//go:embed all:dist
+var dist embed.FS
+
+// Register mounts the SPA on the router. Non-/api paths serve static files;
+// unknown paths fall back to index.html for client-side routing.
+func Register(r *gin.Engine) error {
+ sub, err := fs.Sub(dist, "dist")
+ if err != nil {
+ return err
+ }
+ fileServer := http.FileServer(http.FS(sub))
+
+ r.NoRoute(func(c *gin.Context) {
+ path := c.Request.URL.Path
+ if len(path) >= 4 && path[:4] == "/api" {
+ c.JSON(http.StatusNotFound, gin.H{"error": "not found"})
+ return
+ }
+ if f, err := sub.Open(path[1:]); err == nil {
+ _ = f.Close()
+ fileServer.ServeHTTP(c.Writer, c.Request)
+ return
+ }
+ c.Request.URL.Path = "/"
+ fileServer.ServeHTTP(c.Writer, c.Request)
+ })
+ return nil
+}
diff --git a/apps/frontend/components.json b/apps/frontend/components.json
new file mode 100644
index 0000000..8bfc737
--- /dev/null
+++ b/apps/frontend/components.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": false,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/index.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "iconLibrary": "lucide"
+}
diff --git a/apps/frontend/index.html b/apps/frontend/index.html
new file mode 100644
index 0000000..bdc5652
--- /dev/null
+++ b/apps/frontend/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ Dash
+
+
+
+
+
+
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
new file mode 100644
index 0000000..3e8b330
--- /dev/null
+++ b/apps/frontend/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@dash/frontend",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "typecheck": "tsc --noEmit",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@dash/api-client": "*",
+ "@fontsource-variable/geist": "^5.2.5",
+ "@fontsource-variable/geist-mono": "^5.2.5",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.545.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^3.3.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.13",
+ "@types/node": "^24.5.2",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^5.0.4",
+ "tailwindcss": "^4.1.13",
+ "typescript": "^5.9.2",
+ "vite": "^7.1.7"
+ }
+}
diff --git a/apps/frontend/src/App.tsx b/apps/frontend/src/App.tsx
new file mode 100644
index 0000000..212b02d
--- /dev/null
+++ b/apps/frontend/src/App.tsx
@@ -0,0 +1,54 @@
+import { useEffect, useState } from "react";
+import { Moon, Plus, Sun } from "lucide-react";
+
+type Theme = "dark" | "light";
+
+function initialTheme(): Theme {
+ const saved = localStorage.getItem("dash.theme");
+ return saved === "light" ? "light" : "dark";
+}
+
+export default function App() {
+ const [theme, setTheme] = useState(initialTheme);
+
+ useEffect(() => {
+ document.documentElement.dataset.theme = theme;
+ localStorage.setItem("dash.theme", theme);
+ }, [theme]);
+
+ return (
+
+
+
+
+ Dash
+
+
+
+
+
+
+ No sections yet. Add a service to get started.
+
+
+
+ );
+}
diff --git a/apps/frontend/src/index.css b/apps/frontend/src/index.css
new file mode 100644
index 0000000..3c4ea01
--- /dev/null
+++ b/apps/frontend/src/index.css
@@ -0,0 +1,81 @@
+@import "tailwindcss";
+
+/* Design tokens — ported from mockups/d-bento-mono.html.
+ Semantic vars flip on [data-theme]; Tailwind utilities map via @theme. */
+:root {
+ --ease: cubic-bezier(0.2, 0, 0, 1);
+}
+
+[data-theme="dark"] {
+ --bg: #0a0a0a;
+ --surface: #111111;
+ --surface-hover: #161616;
+ --border: rgba(255, 255, 255, 0.08);
+ --border-strong: rgba(255, 255, 255, 0.14);
+ --text: #fafafa;
+ --text-dim: #a1a1aa;
+ --text-faint: #52525b;
+ --accent: #3b82f6;
+ --up: #22c55e;
+ --down: #f59e0b;
+ --icon-bg: #1c1c1e;
+ --popover: #141414;
+}
+
+[data-theme="light"] {
+ --bg: #fafafa;
+ --surface: #ffffff;
+ --surface-hover: #f4f4f5;
+ --border: rgba(0, 0, 0, 0.08);
+ --border-strong: rgba(0, 0, 0, 0.16);
+ --text: #09090b;
+ --text-dim: #52525b;
+ --text-faint: #a1a1aa;
+ --accent: #2563eb;
+ --up: #16a34a;
+ --down: #d97706;
+ --icon-bg: #f4f4f5;
+ --popover: #ffffff;
+}
+
+@theme inline {
+ --color-bg: var(--bg);
+ --color-surface: var(--surface);
+ --color-surface-hover: var(--surface-hover);
+ --color-border: var(--border);
+ --color-border-strong: var(--border-strong);
+ --color-text: var(--text);
+ --color-text-dim: var(--text-dim);
+ --color-text-faint: var(--text-faint);
+ --color-accent: var(--accent);
+ --color-up: var(--up);
+ --color-down: var(--down);
+ --color-icon-bg: var(--icon-bg);
+ --color-popover: var(--popover);
+ --font-sans: "Geist Variable", ui-sans-serif, system-ui, -apple-system, sans-serif;
+ --font-mono: "Geist Mono Variable", ui-monospace, monospace;
+}
+
+html {
+ -webkit-font-smoothing: antialiased;
+}
+
+body {
+ background: var(--bg);
+ color: var(--text);
+ min-height: 100dvh;
+ transition:
+ background 200ms var(--ease),
+ color 200ms var(--ease);
+}
+
+:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ * {
+ transition: none !important;
+ }
+}
diff --git a/apps/frontend/src/lib/utils.ts b/apps/frontend/src/lib/utils.ts
new file mode 100644
index 0000000..3200be2
--- /dev/null
+++ b/apps/frontend/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx";
+import { twMerge } from "tailwind-merge";
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
diff --git a/apps/frontend/src/main.tsx b/apps/frontend/src/main.tsx
new file mode 100644
index 0000000..a1d09f8
--- /dev/null
+++ b/apps/frontend/src/main.tsx
@@ -0,0 +1,16 @@
+import "@fontsource-variable/geist";
+import "@fontsource-variable/geist-mono";
+import "./index.css";
+
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App.tsx";
+
+const root = document.getElementById("root");
+if (!root) throw new Error("#root missing");
+
+createRoot(root).render(
+
+
+ ,
+);
diff --git a/apps/frontend/src/vite-env.d.ts b/apps/frontend/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/apps/frontend/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/apps/frontend/tsconfig.json b/apps/frontend/tsconfig.json
new file mode 100644
index 0000000..053348a
--- /dev/null
+++ b/apps/frontend/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "jsx": "react-jsx",
+ "strict": true,
+ "noUncheckedIndexedAccess": true,
+ "exactOptionalPropertyTypes": true,
+ "noImplicitOverride": true,
+ "noFallthroughCasesInSwitch": true,
+ "verbatimModuleSyntax": true,
+ "isolatedModules": true,
+ "useDefineForClassFields": true,
+ "resolveJsonModule": true,
+ "allowImportingTsExtensions": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "baseUrl": ".",
+ "paths": { "@/*": ["src/*"] }
+ },
+ "include": ["src", "vite.config.ts"]
+}
diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts
new file mode 100644
index 0000000..9f53dca
--- /dev/null
+++ b/apps/frontend/vite.config.ts
@@ -0,0 +1,15 @@
+import path from "node:path";
+import react from "@vitejs/plugin-react";
+import tailwindcss from "@tailwindcss/vite";
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ plugins: [react(), tailwindcss()],
+ resolve: {
+ alias: { "@": path.resolve(__dirname, "src") },
+ },
+ server: {
+ // Backend API during development (DASH_DEV=1 go run ./cmd/dash).
+ proxy: { "/api": "http://localhost:8080" },
+ },
+});
diff --git a/data/.keep b/data/.keep
new file mode 100644
index 0000000..e69de29
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..839b9b1
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,10 @@
+services:
+ dash:
+ build:
+ context: .
+ dockerfile: infra/Dockerfile
+ ports:
+ - "3000:3000"
+ volumes:
+ - ./data:/data
+ restart: unless-stopped
diff --git a/infra/Dockerfile b/infra/Dockerfile
new file mode 100644
index 0000000..22f1ca5
--- /dev/null
+++ b/infra/Dockerfile
@@ -0,0 +1,31 @@
+# syntax=docker/dockerfile:1
+
+# ---- frontend ----
+FROM node:24-alpine AS frontend
+WORKDIR /src
+COPY package.json package-lock.json openapi.yaml ./
+COPY apps/frontend/package.json apps/frontend/package.json
+COPY packages/api-client/package.json packages/api-client/package.json
+RUN npm ci
+COPY apps/frontend apps/frontend
+COPY packages/api-client packages/api-client
+RUN npm run build -w @dash/frontend
+
+# ---- backend ----
+FROM golang:1.27-alpine AS backend
+WORKDIR /src
+COPY apps/backend/go.mod apps/backend/go.sum ./
+RUN go mod download
+COPY apps/backend ./
+COPY --from=frontend /src/apps/frontend/dist ./internal/web/dist
+RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o /out/dash ./cmd/dash
+
+# ---- final: static binary on distroless ----
+# Runs as root so the ./data bind mount stays writable without PUID plumbing.
+# Attack surface is a single static binary; no shell, no package manager.
+FROM gcr.io/distroless/static-debian12
+COPY --from=backend /out/dash /dash
+ENV DASH_PORT=3000 DASH_DATA_DIR=/data
+EXPOSE 3000
+VOLUME ["/data"]
+ENTRYPOINT ["/dash"]
diff --git a/openapi.yaml b/openapi.yaml
new file mode 100644
index 0000000..309abfa
--- /dev/null
+++ b/openapi.yaml
@@ -0,0 +1,38 @@
+openapi: 3.1.0
+info:
+ title: Dash API
+ version: 0.1.0
+ description: >
+ Single source of truth for the Dash API. The TypeScript client in
+ packages/api-client is generated from this file — do not hand-write
+ API types in the frontend. Phase 1 extends this contract with
+ sections/items/urls CRUD (see docs/specs).
+servers:
+ - url: /
+paths:
+ /api/healthz:
+ get:
+ operationId: healthz
+ summary: Liveness + database reachability probe
+ responses:
+ "200":
+ description: Service healthy
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Health"
+ "503":
+ description: Database unreachable
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Health"
+components:
+ schemas:
+ Health:
+ type: object
+ required: [status]
+ properties:
+ status:
+ type: string
+ example: ok
diff --git a/oxlint.config.ts b/oxlint.config.ts
new file mode 100644
index 0000000..d9c2933
--- /dev/null
+++ b/oxlint.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "oxlint";
+
+export default defineConfig({
+ ignorePatterns: [
+ ".agents/**",
+ ".claude/**",
+ "tools/oxlint/anti-slop/**",
+ "**/dist/**",
+ "**/node_modules/**",
+ ],
+ jsPlugins: [
+ { name: "anti-slop", specifier: "./tools/oxlint/anti-slop/index.ts" },
+ ],
+ rules: {
+ "anti-slop/no-chained-type-assertions": "error",
+ "anti-slop/no-widen-then-assert": "error",
+ "anti-slop/no-unsafe-dictionary-type": "error",
+ "anti-slop/require-safety-comment-for-type-assertion": "error",
+ "anti-slop/no-known-value-widening": "error",
+ },
+});
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..43ea7d4
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3363 @@
+{
+ "name": "dash",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "dash",
+ "hasInstallScript": true,
+ "workspaces": [
+ "apps/frontend",
+ "packages/api-client"
+ ],
+ "devDependencies": {
+ "@oxlint/plugins": "1.78.0",
+ "oxlint": "^1.78.0"
+ }
+ },
+ "apps/frontend": {
+ "name": "@dash/frontend",
+ "version": "0.0.0",
+ "dependencies": {
+ "@dash/api-client": "*",
+ "@fontsource-variable/geist": "^5.2.5",
+ "@fontsource-variable/geist-mono": "^5.2.5",
+ "clsx": "^2.1.1",
+ "lucide-react": "^0.545.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "tailwind-merge": "^3.3.1"
+ },
+ "devDependencies": {
+ "@tailwindcss/vite": "^4.1.13",
+ "@types/node": "^24.5.2",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "@vitejs/plugin-react": "^5.0.4",
+ "tailwindcss": "^4.1.13",
+ "typescript": "^5.9.2",
+ "vite": "^7.1.7"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
+ "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
+ "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@dash/api-client": {
+ "resolved": "packages/api-client",
+ "link": true
+ },
+ "node_modules/@dash/frontend": {
+ "resolved": "apps/frontend",
+ "link": true
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@fontsource-variable/geist": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/geist/-/geist-5.3.0.tgz",
+ "integrity": "sha512-j0m+vLQuG5XAYoHtGCVu0spvlGreR3EzpECUVzkFmI1mTVnAO38l/NEPDCFgZ177JxzYJCLSmTQibIiYPilGrA==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@fontsource-variable/geist-mono": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/@fontsource-variable/geist-mono/-/geist-mono-5.3.0.tgz",
+ "integrity": "sha512-vBbuwDEo9AkrqADMXOrlAR3DFcJi4/JxeuU43FoiQERnNwsfXNnvxvReZG02cQKmyk4DZkZdBZX3oTDvy2zBAw==",
+ "license": "OFL-1.1",
+ "funding": {
+ "url": "https://github.com/sponsors/ayuhito"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm-eabi": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.83.0.tgz",
+ "integrity": "sha512-0yGY24EwsLk5YDe6F+VkmZyRHSwJDALa3nIrPpq7FXmp2lV2d0TzvBCGeZk+wgiULRGr5blhyr4QMp5KCXJUqA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-android-arm64": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.83.0.tgz",
+ "integrity": "sha512-hHfJ0vc17A4iUjH5p9BsTUPYbYRNxGpvD2lbu1aBRk54bzNIx9o5TtYF39QPZcV95DagZd+4DEAw2RH3G2ZsMg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-arm64": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.83.0.tgz",
+ "integrity": "sha512-hsOjYjszLb/3zym/TkzUMPAoQlTJcuzSyEPOAyA+skXJIX9M0o+4JfOtqopX/Vf4hSLrJ98j0nvFo23gzk8auQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-darwin-x64": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.83.0.tgz",
+ "integrity": "sha512-mjh5oH2EA+wl5yRJYT9K9G61O2zFlpuv+yf2JwZOi0+dq2FnTUtm1h8i+5Ik0fXPWIu/k84I1psZR9aQsLAnyA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-freebsd-x64": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.83.0.tgz",
+ "integrity": "sha512-fNHr64/YaO8YssuoDVC8+F4Uk5enR86q5uxfHkQrjAPs1dbAILOrD2uaud+J7MO8Fx774g44ERLD0IGIvZE48w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.83.0.tgz",
+ "integrity": "sha512-Qpwy3zzAwMj+8/lyYItHmkSMwbkprFNWTK7jPYDOxSyxEhaSLOWYUTCMkjF334J8/WD0nznCCsoBbIH6hpsuIw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.83.0.tgz",
+ "integrity": "sha512-s+BirYLFq7JL2k9sP0XI3ZXJ9dYvJ8sX3jLCLoag7tt+zrSHpZxP0jqznfL+Gdgwu7ay0dYgGYJXrQvq3iWloA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-gnu": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.83.0.tgz",
+ "integrity": "sha512-7lihXt3vKr+GIyapNbHrnFHm/biiW30le6Zv/DExbAFPF6YwCQXVFlONPFehxs0CpGO4CBfYPM9rdDT+XMoIlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-arm64-musl": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.83.0.tgz",
+ "integrity": "sha512-q63JalLYVkZiZvls1z3PPUnpmQluOMXp0khqQMznCeAPLGydfNY8JhvuA4WlK57JfrvikU8wB5lPVveqpIXvew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.83.0.tgz",
+ "integrity": "sha512-krQmDF+dRbxvdqVPV88ZuOoPPu8X5BuqDA8Hd+qcS4YMRQCb+nexA57DazgGsc/rGdKBe3QmV0mnv0bdpW/p5g==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.83.0.tgz",
+ "integrity": "sha512-MmOl8Y6txEAXZU1RG8Rr264jQ6D7VPmqFsU/45x/FeWsGe32hklTqGrLE6UxHzp5Rjt0wP+20tY8YXKgSFB3mw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-riscv64-musl": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.83.0.tgz",
+ "integrity": "sha512-u1rMymh0W3JZkq370kzQsYPULGWqhE09pZRqnZvUSoYaI9pVO5yVX+iYIslmWuEgwuzH9YAaOsScJiobWCHoOw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-s390x-gnu": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.83.0.tgz",
+ "integrity": "sha512-y0zK3HNwGysu7rqtE+BQG/d0bx5gh/KwlOtghN8oWeK1KcWzeaLqtZrbm8owqdma1lFyrce/hTO5ismuNu+INQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-gnu": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.83.0.tgz",
+ "integrity": "sha512-rS5gM0NgD7ngmuJmbIehsidtrOwKkLFwCQbKEeb9KuyQrrWNq5Zkn0uV6AYdXOMJ0grrWEiLwBuvMxt8w5vsNw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-linux-x64-musl": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.83.0.tgz",
+ "integrity": "sha512-W2IH4EtpcPaWcvNGCA95YoDg4vxqE/ZiPCi3arrxEEpsK7+JQN9WYwrlYFx9pcdP6KPXqRqkv3zdQPHcx7b6YQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-openharmony-arm64": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.83.0.tgz",
+ "integrity": "sha512-6LyKkUyoajssTPLlZmDbZIbu4IZ5B4bGuRUnBgCGpEvHP3FQMaYITncHA/unPUo7q+Z+pIu2HhdkQ+8d1SG7iA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-arm64-msvc": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.83.0.tgz",
+ "integrity": "sha512-Uz/fObEtF0jmNJQJ8CGRBKfefYstS0/wjD3s6IGzP8nUwsJykHQJBiN3npHwKiGRGn/vvBEgNr4B3cCzmmatvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-ia32-msvc": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.83.0.tgz",
+ "integrity": "sha512-u7XcvPW6Bk58tY5iWs2ESb0vJjoE/kuSpHxopbwp/p3ZtWVQXZ6wor5w3ssVTHOqd/v8b+QdhSFWQ4grEUNWpA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/binding-win32-x64-msvc": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.83.0.tgz",
+ "integrity": "sha512-LZRubd7ph13QmAg4fFecTYVZkiYbROR2Htaxh/ufWRkDhPOm2wrwaEYR89e0YpPFD3dqBrPoxS7myBw5hmYA7Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@oxlint/plugins": {
+ "version": "1.78.0",
+ "resolved": "https://registry.npmjs.org/@oxlint/plugins/-/plugins-1.78.0.tgz",
+ "integrity": "sha512-Ypt8KeRYw+4jUtlPirfcHWMrn5ms12VrrFPD+Mds477/7tJxG1Kcz2Yrg2nVcTQEUx/GdlhS+BUg1kmxNm04Ug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@redocly/ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js-replace": "^1.0.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/@redocly/config": {
+ "version": "0.22.0",
+ "resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.22.0.tgz",
+ "integrity": "sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@redocly/openapi-core": {
+ "version": "1.34.20",
+ "resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-1.34.20.tgz",
+ "integrity": "sha512-ypeBZ/6BKXR9+7/TtbKhbl4UgD7raHhPS12oknlKno2A8+lnFkxIwiE/Aklu6L2cd/ioH+fCWuMxi9/p3EyAPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/ajv": "8.11.2",
+ "@redocly/config": "0.22.0",
+ "colorette": "1.4.0",
+ "https-proxy-agent": "7.0.6",
+ "js-levenshtein": "1.1.6",
+ "js-yaml": "4.3.2",
+ "minimatch": "5.1.9",
+ "pluralize": "8.0.0",
+ "yaml-ast-parser": "0.0.43"
+ },
+ "engines": {
+ "node": ">=18.17.0",
+ "npm": ">=9.5.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz",
+ "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz",
+ "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz",
+ "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz",
+ "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz",
+ "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz",
+ "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz",
+ "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz",
+ "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz",
+ "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz",
+ "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz",
+ "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz",
+ "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz",
+ "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz",
+ "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz",
+ "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz",
+ "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz",
+ "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz",
+ "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz",
+ "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz",
+ "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz",
+ "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz",
+ "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz",
+ "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz",
+ "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz",
+ "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.3.tgz",
+ "integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.24.1",
+ "jiti": "^2.7.0",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
+ "integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-arm64": "4.3.3",
+ "@tailwindcss/oxide-darwin-x64": "4.3.3",
+ "@tailwindcss/oxide-freebsd-x64": "4.3.3",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.3.3",
+ "@tailwindcss/oxide-linux-x64-musl": "4.3.3",
+ "@tailwindcss/oxide-wasm32-wasi": "4.3.3",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.3.3",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.3.3"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.3.tgz",
+ "integrity": "sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.3.tgz",
+ "integrity": "sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.3.tgz",
+ "integrity": "sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.3.tgz",
+ "integrity": "sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.3.tgz",
+ "integrity": "sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.3.tgz",
+ "integrity": "sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.3.tgz",
+ "integrity": "sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.3.tgz",
+ "integrity": "sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.3.tgz",
+ "integrity": "sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.3.tgz",
+ "integrity": "sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.11.1",
+ "@emnapi/runtime": "^1.11.1",
+ "@emnapi/wasi-threads": "^1.2.2",
+ "@napi-rs/wasm-runtime": "^1.1.4",
+ "@tybys/wasm-util": "^0.10.2",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
+ "integrity": "sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.3.tgz",
+ "integrity": "sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/vite": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.3.tgz",
+ "integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@tailwindcss/node": "4.3.3",
+ "@tailwindcss/oxide": "4.3.3",
+ "tailwindcss": "4.3.3"
+ },
+ "peerDependencies": {
+ "vite": "^5.2.0 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "24.13.5",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.5.tgz",
+ "integrity": "sha512-TXyindR+lBr22aJIdMQzCFHPHR6cR4js838mRDCSz5hOKWZvZwsXSSiXDmjRj4iJmgl+sR9O+1mkoVBSMadNug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.31",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz",
+ "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz",
+ "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.24",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz",
+ "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz",
+ "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.29.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.23",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.427",
+ "node-releases": "^2.0.55",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/change-case": {
+ "version": "5.4.4",
+ "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz",
+ "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/colorette": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
+ "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.430",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz",
+ "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.25.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.25.1.tgz",
+ "integrity": "sha512-nGXts5znJzmWPu+mIE9izCOzdg63oJca2mDzGWWTth7sr4aCToKcoyFVBQwN75Ij5Pf6p510EwkTqViTRzDV+w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/index-to-position": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
+ "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz",
+ "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-levenshtein": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
+ "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.545.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.545.0.tgz",
+ "integrity": "sha512-7r1/yUuflQDSt4f1bpn5ZAocyIxcTyVyBBChSVtBKn5M+392cPmI5YJMWOJKk/HUWGm5wg83chlAZtCcGbEZtw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.19",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz",
+ "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.55",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz",
+ "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/openapi-fetch": {
+ "version": "0.15.2",
+ "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.15.2.tgz",
+ "integrity": "sha512-rdYTzUmSsJevmNqg7fwUVGuKc2Gfb9h6ph74EVPkPfIGJaZTfqdIbJahtbJ3qg1LKinln30hqZniLnKpH0RJBg==",
+ "license": "MIT",
+ "dependencies": {
+ "openapi-typescript-helpers": "^0.0.15"
+ }
+ },
+ "node_modules/openapi-typescript": {
+ "version": "7.13.0",
+ "resolved": "https://registry.npmjs.org/openapi-typescript/-/openapi-typescript-7.13.0.tgz",
+ "integrity": "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@redocly/openapi-core": "^1.34.6",
+ "ansi-colors": "^4.1.3",
+ "change-case": "^5.4.4",
+ "parse-json": "^8.3.0",
+ "supports-color": "^10.2.2",
+ "yargs-parser": "^21.1.1"
+ },
+ "bin": {
+ "openapi-typescript": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "typescript": "^5.x"
+ }
+ },
+ "node_modules/openapi-typescript-helpers": {
+ "version": "0.0.15",
+ "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz",
+ "integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==",
+ "license": "MIT"
+ },
+ "node_modules/oxlint": {
+ "version": "1.83.0",
+ "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.83.0.tgz",
+ "integrity": "sha512-cyDzSzaw3uzP0TeCeq3lLRPPoaUxkbB4ZOXj+kn+5r+BX9V+4bNVGk9lxer+WrgcpebH4JxLlJ3KQjveVztOLQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "oxlint": "bin/oxlint"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/oxc-project"
+ },
+ "optionalDependencies": {
+ "@oxlint/binding-android-arm-eabi": "1.83.0",
+ "@oxlint/binding-android-arm64": "1.83.0",
+ "@oxlint/binding-darwin-arm64": "1.83.0",
+ "@oxlint/binding-darwin-x64": "1.83.0",
+ "@oxlint/binding-freebsd-x64": "1.83.0",
+ "@oxlint/binding-linux-arm-gnueabihf": "1.83.0",
+ "@oxlint/binding-linux-arm-musleabihf": "1.83.0",
+ "@oxlint/binding-linux-arm64-gnu": "1.83.0",
+ "@oxlint/binding-linux-arm64-musl": "1.83.0",
+ "@oxlint/binding-linux-ppc64-gnu": "1.83.0",
+ "@oxlint/binding-linux-riscv64-gnu": "1.83.0",
+ "@oxlint/binding-linux-riscv64-musl": "1.83.0",
+ "@oxlint/binding-linux-s390x-gnu": "1.83.0",
+ "@oxlint/binding-linux-x64-gnu": "1.83.0",
+ "@oxlint/binding-linux-x64-musl": "1.83.0",
+ "@oxlint/binding-openharmony-arm64": "1.83.0",
+ "@oxlint/binding-win32-arm64-msvc": "1.83.0",
+ "@oxlint/binding-win32-ia32-msvc": "1.83.0",
+ "@oxlint/binding-win32-x64-msvc": "1.83.0"
+ },
+ "peerDependencies": {
+ "oxlint-tsgolint": ">=7.0.2001",
+ "vite-plus": "*"
+ },
+ "peerDependenciesMeta": {
+ "oxlint-tsgolint": {
+ "optional": true
+ },
+ "vite-plus": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "8.3.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
+ "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.26.2",
+ "index-to-position": "^1.1.0",
+ "type-fest": "^4.39.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
+ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pluralize": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
+ "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.28",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz",
+ "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.18",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz",
+ "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.63.3",
+ "@rollup/rollup-android-arm64": "4.63.3",
+ "@rollup/rollup-darwin-arm64": "4.63.3",
+ "@rollup/rollup-darwin-x64": "4.63.3",
+ "@rollup/rollup-freebsd-arm64": "4.63.3",
+ "@rollup/rollup-freebsd-x64": "4.63.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.3",
+ "@rollup/rollup-linux-arm64-musl": "4.63.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.3",
+ "@rollup/rollup-linux-loong64-musl": "4.63.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.3",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.3",
+ "@rollup/rollup-linux-x64-gnu": "4.63.3",
+ "@rollup/rollup-linux-x64-musl": "4.63.3",
+ "@rollup/rollup-openbsd-x64": "4.63.3",
+ "@rollup/rollup-openharmony-arm64": "4.63.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.3",
+ "@rollup/rollup-win32-x64-gnu": "4.63.3",
+ "@rollup/rollup-win32-x64-msvc": "4.63.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/tailwind-merge": {
+ "version": "3.7.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz",
+ "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.3.tgz",
+ "integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
+ "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "4.41.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
+ "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js-replace": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/uri-js-replace/-/uri-js-replace-1.0.1.tgz",
+ "integrity": "sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yaml-ast-parser": {
+ "version": "0.0.43",
+ "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
+ "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "packages/api-client": {
+ "name": "@dash/api-client",
+ "version": "0.0.0",
+ "dependencies": {
+ "openapi-fetch": "^0.15.0"
+ },
+ "devDependencies": {
+ "openapi-typescript": "^7.10.1"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..124f1f1
--- /dev/null
+++ b/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "dash",
+ "private": true,
+ "type": "module",
+ "workspaces": [
+ "apps/frontend",
+ "packages/api-client"
+ ],
+ "scripts": {
+ "generate": "npm run generate -w @dash/api-client",
+ "dev": "npm run dev -w @dash/frontend",
+ "build": "npm run build -w @dash/api-client --if-present && npm run build -w @dash/frontend",
+ "typecheck": "npm run typecheck -w @dash/frontend",
+ "lint": "oxlint",
+ "postinstall": "npm run generate"
+ },
+ "devDependencies": {
+ "@oxlint/plugins": "1.78.0",
+ "oxlint": "^1.78.0"
+ }
+}
diff --git a/packages/api-client/package.json b/packages/api-client/package.json
new file mode 100644
index 0000000..9fd49ae
--- /dev/null
+++ b/packages/api-client/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@dash/api-client",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "exports": {
+ ".": "./src/index.ts",
+ "./types": "./src/schema.d.ts"
+ },
+ "scripts": {
+ "generate": "openapi-typescript ../../openapi.yaml -o src/schema.d.ts"
+ },
+ "dependencies": {
+ "openapi-fetch": "^0.15.0"
+ },
+ "devDependencies": {
+ "openapi-typescript": "^7.10.1"
+ }
+}
diff --git a/packages/api-client/src/index.ts b/packages/api-client/src/index.ts
new file mode 100644
index 0000000..556cf39
--- /dev/null
+++ b/packages/api-client/src/index.ts
@@ -0,0 +1,11 @@
+// Generated-client entrypoint. Types come from src/schema.d.ts, produced by
+// `npm run generate` from ../../openapi.yaml — the API contract.
+import createFetchClient from "openapi-fetch";
+import type { paths } from "./schema.d.ts";
+
+export type { paths, components } from "./schema.d.ts";
+
+/** API client bound to the OpenAPI contract. Same-origin by default. */
+export function createClient(baseUrl = "") {
+ return createFetchClient({ baseUrl });
+}
diff --git a/tools/oxlint/anti-slop/index.ts b/tools/oxlint/anti-slop/index.ts
new file mode 100644
index 0000000..d750cb5
--- /dev/null
+++ b/tools/oxlint/anti-slop/index.ts
@@ -0,0 +1,23 @@
+// Vendored from github.com/dmmulroy/anti-slop (MIT), trimmed to the adopted
+// rule set in references/architecture.md. Do not add rules here without
+// updating the adopted list.
+import { eslintCompatPlugin } from "@oxlint/plugins";
+
+import { noChainedTypeAssertionsRule } from "./rules/no-chained-type-assertions.ts";
+import { noKnownValueWideningRule } from "./rules/no-known-value-widening.ts";
+import { noUnsafeDictionaryTypeRule } from "./rules/no-unsafe-dictionary-type.ts";
+import { noWidenThenAssertRule } from "./rules/no-widen-then-assert.ts";
+import { requireSafetyCommentForTypeAssertionRule } from "./rules/require-safety-comment-for-type-assertion.ts";
+
+const antiSlopPlugin = eslintCompatPlugin({
+ meta: { name: "anti-slop" },
+ rules: {
+ "no-chained-type-assertions": noChainedTypeAssertionsRule,
+ "no-known-value-widening": noKnownValueWideningRule,
+ "no-unsafe-dictionary-type": noUnsafeDictionaryTypeRule,
+ "no-widen-then-assert": noWidenThenAssertRule,
+ "require-safety-comment-for-type-assertion": requireSafetyCommentForTypeAssertionRule,
+ },
+});
+
+export default antiSlopPlugin;
diff --git a/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts b/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts
new file mode 100644
index 0000000..0d11852
--- /dev/null
+++ b/tools/oxlint/anti-slop/rules/no-chained-type-assertions.ts
@@ -0,0 +1,77 @@
+import { defineRule } from "@oxlint/plugins";
+import type { ESTree } from "@oxlint/plugins";
+
+type TypeAssertionExpression = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
+
+function isTypeAssertionExpression(node: ESTree.Node): node is TypeAssertionExpression {
+ return node.type === "TSAsExpression" || node.type === "TSTypeAssertion";
+}
+
+function unwrapParenthesizedExpression(expression: ESTree.Expression): ESTree.Expression {
+ let current = expression;
+ while (current.type === "ParenthesizedExpression") {
+ current = current.expression;
+ }
+ return current;
+}
+
+function isConstAssertion(node: TypeAssertionExpression): boolean {
+ const { typeAnnotation } = node;
+ return (
+ typeAnnotation.type === "TSTypeReference" &&
+ typeAnnotation.typeName.type === "Identifier" &&
+ typeAnnotation.typeName.name === "const"
+ );
+}
+
+function isOutermostAssertionInChain(node: TypeAssertionExpression): boolean {
+ let current: ESTree.Expression = node;
+ let parent = node.parent;
+
+ while (parent.type === "ParenthesizedExpression" && parent.expression === current) {
+ current = parent;
+ parent = parent.parent;
+ }
+
+ return !isTypeAssertionExpression(parent) || parent.expression !== current;
+}
+
+function isForbiddenAssertionChain(node: TypeAssertionExpression): boolean {
+ let assertionCount = 0;
+ let hasNonConstAssertion = false;
+ let current: ESTree.Expression = node;
+
+ while (isTypeAssertionExpression(current)) {
+ assertionCount += 1;
+ hasNonConstAssertion ||= !isConstAssertion(current);
+ current = unwrapParenthesizedExpression(current.expression);
+ }
+
+ return assertionCount > 1 && hasNonConstAssertion;
+}
+
+/** Disallow nested TypeScript type assertions, while permitting chains made only of const assertions. */
+export const noChainedTypeAssertionsRule = defineRule({
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Disallow chained TypeScript as and angle-bracket assertions, including parenthesized chains.",
+ },
+ messages: {
+ chained:
+ "This assertion chain discards type evidence. Keep the original precise type, or parse untrusted input at its boundary before narrowing it.",
+ },
+ },
+ createOnce(context) {
+ const checkTypeAssertion = (node: TypeAssertionExpression) => {
+ if (!isOutermostAssertionInChain(node) || !isForbiddenAssertionChain(node)) return;
+ context.report({ node, messageId: "chained" });
+ };
+
+ return {
+ TSAsExpression: checkTypeAssertion,
+ TSTypeAssertion: checkTypeAssertion,
+ };
+ },
+});
diff --git a/tools/oxlint/anti-slop/rules/no-known-value-widening.ts b/tools/oxlint/anti-slop/rules/no-known-value-widening.ts
new file mode 100644
index 0000000..0df39ab
--- /dev/null
+++ b/tools/oxlint/anti-slop/rules/no-known-value-widening.ts
@@ -0,0 +1,415 @@
+import { defineRule } from "@oxlint/plugins";
+
+import {
+ classifyUnsafeDictionaryValue,
+ classifyWideningTarget,
+ createTypeEnvironment,
+ isKnownEvidenceExpression,
+ type TypeEnvironment,
+ type WideningTarget,
+} from "../shared/dictionary-types.ts";
+import {
+ containsUnknownType,
+ functionParameterBindingName,
+ functionParameterTypeAnnotation,
+} from "../shared/function-parameters.ts";
+import { resolveVariable } from "../shared/scope.ts";
+
+import type { ESTree, SourceCode, Variable } from "@oxlint/plugins";
+
+type FunctionExpression = ESTree.ArrowFunctionExpression | ESTree.Function;
+
+function unwrapExpression(expression: ESTree.Expression): ESTree.Expression {
+ let current = expression;
+ while (
+ current.type === "ParenthesizedExpression" ||
+ current.type === "TSAsExpression" ||
+ current.type === "TSSatisfiesExpression" ||
+ current.type === "TSTypeAssertion" ||
+ current.type === "TSNonNullExpression"
+ ) {
+ current = current.expression;
+ }
+ return current;
+}
+
+function variableDeclarator(variable: Variable): ESTree.VariableDeclarator | null {
+ if (variable.defs.length !== 1) return null;
+ const [definition] = variable.defs;
+ return definition?.type === "Variable" && definition.node.type === "VariableDeclarator"
+ ? definition.node
+ : null;
+}
+
+function isStableConstVariable(variable: Variable, declarator: ESTree.VariableDeclarator): boolean {
+ return (
+ declarator.parent.type === "VariableDeclaration" &&
+ declarator.parent.kind === "const" &&
+ variable.references.every((reference) => reference.init || !reference.isWrite())
+ );
+}
+
+function hasKnownEvidence(
+ sourceCode: SourceCode,
+ expression: ESTree.Expression,
+ visitedVariables = new Set(),
+): boolean {
+ if (isKnownEvidenceExpression(expression)) return true;
+ const unwrapped = unwrapExpression(expression);
+ if (unwrapped.type !== "Identifier") return false;
+ const variable = resolveVariable(sourceCode, unwrapped);
+ if (variable === null || visitedVariables.has(variable)) return false;
+ const declarator = variableDeclarator(variable);
+ if (
+ declarator === null ||
+ declarator.init === null ||
+ !isStableConstVariable(variable, declarator)
+ ) {
+ return false;
+ }
+ visitedVariables.add(variable);
+ return hasKnownEvidence(sourceCode, declarator.init, visitedVariables);
+}
+
+function isFunctionExpression(node: ESTree.Node): node is FunctionExpression {
+ return (
+ node.type === "ArrowFunctionExpression" ||
+ node.type === "FunctionDeclaration" ||
+ node.type === "FunctionExpression" ||
+ node.type === "TSDeclareFunction" ||
+ node.type === "TSEmptyBodyFunctionExpression"
+ );
+}
+
+function localFunctionForCall(
+ sourceCode: SourceCode,
+ callee: ESTree.Expression,
+): FunctionExpression | null {
+ const unwrapped = unwrapExpression(callee);
+ if (isFunctionExpression(unwrapped)) return unwrapped;
+ if (unwrapped.type !== "Identifier") return null;
+ const variable = resolveVariable(sourceCode, unwrapped);
+ if (variable === null || variable.defs.length !== 1) return null;
+ const [definition] = variable.defs;
+ if (definition === undefined) return null;
+ if (definition.type === "FunctionName" && isFunctionExpression(definition.node)) {
+ return definition.node;
+ }
+ if (definition.type !== "Variable" || definition.node.type !== "VariableDeclarator") {
+ return null;
+ }
+ const initializer = definition.node.init;
+ if (initializer === null) return null;
+ const unwrappedInitializer = unwrapExpression(initializer);
+ return isFunctionExpression(unwrappedInitializer) ? unwrappedInitializer : null;
+}
+
+function variableTypeAnnotation(
+ sourceCode: SourceCode,
+ variable: Variable,
+): ESTree.TSTypeAnnotation | null {
+ if (variable.defs.length !== 1) return null;
+ const [definition] = variable.defs;
+ if (definition === undefined) return null;
+ if (
+ definition.type === "Variable" &&
+ definition.node.type === "VariableDeclarator" &&
+ definition.node.id.type === "Identifier"
+ ) {
+ return definition.node.id.typeAnnotation ?? null;
+ }
+ if (definition.type !== "Parameter" || !isFunctionExpression(definition.node)) {
+ return null;
+ }
+ const parameter = definition.node.params.find(
+ (candidate) =>
+ functionParameterBindingName(candidate, sourceCode) === variable.name,
+ );
+ return parameter === undefined ? null : (functionParameterTypeAnnotation(parameter) ?? null);
+}
+
+function hasInformativeType(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+): boolean {
+ return classifyUnsafeDictionaryValue(type, environment) === null;
+}
+
+function hasKnownCallArgumentEvidence(
+ sourceCode: SourceCode,
+ expression: ESTree.Expression,
+ environment: TypeEnvironment,
+ visitedVariables = new Set(),
+): boolean {
+ if (expression.type === "ParenthesizedExpression" || expression.type === "TSNonNullExpression") {
+ return hasKnownCallArgumentEvidence(
+ sourceCode,
+ expression.expression,
+ environment,
+ visitedVariables,
+ );
+ }
+ if (expression.type === "TSAsExpression" || expression.type === "TSTypeAssertion") {
+ return hasInformativeType(expression.typeAnnotation, environment);
+ }
+ if (expression.type === "TSSatisfiesExpression") {
+ return hasKnownCallArgumentEvidence(
+ sourceCode,
+ expression.expression,
+ environment,
+ visitedVariables,
+ );
+ }
+ if (expression.type === "CallExpression") {
+ const owner = localFunctionForCall(sourceCode, expression.callee);
+ const returnType = owner?.returnType?.typeAnnotation;
+ return returnType !== undefined && hasInformativeType(returnType, environment);
+ }
+ if (expression.type !== "Identifier") return isKnownEvidenceExpression(expression);
+ const variable = resolveVariable(sourceCode, expression);
+ if (variable === null || visitedVariables.has(variable)) return false;
+ const annotation = variableTypeAnnotation(sourceCode, variable);
+ if (annotation !== null) {
+ return hasInformativeType(annotation.typeAnnotation, environment);
+ }
+ const declarator = variableDeclarator(variable);
+ if (
+ declarator === null ||
+ declarator.init === null ||
+ !isStableConstVariable(variable, declarator)
+ ) {
+ return false;
+ }
+ visitedVariables.add(variable);
+ return hasKnownCallArgumentEvidence(
+ sourceCode,
+ declarator.init,
+ environment,
+ visitedVariables,
+ );
+}
+
+function typePredicateSubjectIndex(
+ sourceCode: SourceCode,
+ owner: FunctionExpression,
+): number | null {
+ const predicate = owner.returnType?.typeAnnotation;
+ if (predicate?.type !== "TSTypePredicate" || predicate.parameterName.type !== "Identifier") {
+ return null;
+ }
+ const predicateParameterName = predicate.parameterName.name;
+ const index = owner.params.findIndex(
+ (parameter) =>
+ functionParameterBindingName(parameter, sourceCode) === predicateParameterName,
+ );
+ return index === -1 ? null : index;
+}
+
+function annotationTarget(
+ annotation: ESTree.TSTypeAnnotation | null | undefined,
+ environment: TypeEnvironment,
+): WideningTarget | null {
+ return annotation === null || annotation === undefined
+ ? null
+ : classifyWideningTarget(annotation.typeAnnotation, environment);
+}
+
+function enclosingFunction(node: ESTree.Node): FunctionExpression | null {
+ let current: ESTree.Node | null = node.parent;
+ while (current !== null && current.type !== "Program") {
+ if (
+ current.type === "ArrowFunctionExpression" ||
+ current.type === "FunctionDeclaration" ||
+ current.type === "FunctionExpression"
+ ) {
+ return current;
+ }
+ current = current.parent;
+ }
+ return null;
+}
+
+function sourceKeyName(sourceCode: SourceCode, key: ESTree.PropertyKey): string {
+ if (key.type === "Identifier" || key.type === "PrivateIdentifier") return key.name;
+ if (key.type === "Literal") return String(key.value);
+ return sourceCode.getText(key);
+}
+
+function functionName(sourceCode: SourceCode, owner: FunctionExpression | null): string {
+ if (owner === null) return "anonymous function";
+ if (owner.id !== null) return owner.id.name;
+ const parent = owner.parent;
+ if (parent.type === "VariableDeclarator" && parent.id.type === "Identifier")
+ return parent.id.name;
+ if (parent.type === "MethodDefinition") return sourceKeyName(sourceCode, parent.key);
+ return "anonymous function";
+}
+
+function isEmptyObjectExpression(expression: ESTree.Expression): boolean {
+ const unwrapped = unwrapExpression(expression);
+ return unwrapped.type === "ObjectExpression" && unwrapped.properties.length === 0;
+}
+
+function isDictionaryAccumulatorTarget(destination: WideningTarget): boolean {
+ return destination.kind === "open dictionary" || destination.kind === "generic container";
+}
+
+function hasParentAssertion(node: ESTree.Node): boolean {
+ return node.parent?.type === "TSAsExpression" || node.parent?.type === "TSTypeAssertion";
+}
+
+/** Detect sound syntactic cases where a known value is explicitly widened and loses evidence. */
+export const noKnownValueWideningRule = defineRule({
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Disallow syntactically established values from flowing into explicitly broad or anonymous target types that discard useful evidence.",
+ },
+ messages: {
+ widening:
+ "The explicit {{target}} type on {{subject}} discards known type evidence. Keep inference, validate with `satisfies`, or use a named owner contract.",
+ },
+ },
+ createOnce(context) {
+ let environment: TypeEnvironment | null = null;
+
+ const reportFlow = (
+ expression: ESTree.Expression,
+ destination: WideningTarget | null,
+ subject: string,
+ ) => {
+ if (destination === null) return;
+ if (
+ isDictionaryAccumulatorTarget(destination) &&
+ isEmptyObjectExpression(expression)
+ ) {
+ return;
+ }
+ if (!hasKnownEvidence(context.sourceCode, expression)) return;
+ context.report({
+ node: expression,
+ messageId: "widening",
+ data: { subject, target: destination.kind },
+ });
+ };
+
+ const targetFromAnnotation = (annotation: ESTree.TSTypeAnnotation | null | undefined) =>
+ environment === null ? null : annotationTarget(annotation, environment);
+
+ return {
+ Program(node) {
+ environment = createTypeEnvironment(
+ node,
+ context.sourceCode.visitorKeys,
+ );
+ },
+ VariableDeclarator(node) {
+ if (node.init === null || node.id.type !== "Identifier") return;
+ reportFlow(
+ node.init,
+ targetFromAnnotation(node.id.typeAnnotation),
+ `binding \`${node.id.name}\``,
+ );
+ },
+ PropertyDefinition(node) {
+ if (node.value === null) return;
+ reportFlow(
+ node.value,
+ targetFromAnnotation(node.typeAnnotation),
+ `property \`${sourceKeyName(context.sourceCode, node.key)}\``,
+ );
+ },
+ AccessorProperty(node) {
+ if (node.value === null) return;
+ reportFlow(
+ node.value,
+ targetFromAnnotation(node.typeAnnotation),
+ `property \`${sourceKeyName(context.sourceCode, node.key)}\``,
+ );
+ },
+ AssignmentExpression(node) {
+ if (node.operator !== "=" || node.left.type !== "Identifier") return;
+ const variable = resolveVariable(context.sourceCode, node.left);
+ if (variable === null) return;
+ const declarator = variableDeclarator(variable);
+ if (declarator === null || declarator.id.type !== "Identifier") return;
+ reportFlow(
+ node.right,
+ targetFromAnnotation(declarator.id.typeAnnotation),
+ `binding \`${declarator.id.name}\``,
+ );
+ },
+ CallExpression(node) {
+ if (environment === null) return;
+ const owner = localFunctionForCall(context.sourceCode, node.callee);
+ if (owner === null) return;
+ const parameterIndex = typePredicateSubjectIndex(context.sourceCode, owner);
+ if (parameterIndex === null) return;
+ const parameter = owner.params[parameterIndex];
+ const argument = node.arguments[parameterIndex];
+ if (parameter === undefined || argument === undefined || argument.type === "SpreadElement") {
+ return;
+ }
+ const parameterAnnotation = functionParameterTypeAnnotation(parameter);
+ if (
+ parameterAnnotation === null ||
+ parameterAnnotation === undefined ||
+ !containsUnknownType(parameterAnnotation.typeAnnotation)
+ ) {
+ return;
+ }
+ if (
+ !hasKnownCallArgumentEvidence(
+ context.sourceCode,
+ argument,
+ environment,
+ )
+ ) {
+ return;
+ }
+ context.report({
+ node: argument,
+ messageId: "widening",
+ data: {
+ subject: `argument for parameter \`${functionParameterBindingName(parameter, context.sourceCode)}\` of \`${functionName(context.sourceCode, owner)}\``,
+ target: "unknown",
+ },
+ });
+ },
+ ReturnStatement(node) {
+ if (node.argument === null) return;
+ const owner = enclosingFunction(node);
+ reportFlow(
+ node.argument,
+ targetFromAnnotation(owner?.returnType),
+ `return value of \`${functionName(context.sourceCode, owner)}\``,
+ );
+ },
+ ArrowFunctionExpression(node) {
+ if (node.body.type === "BlockStatement") return;
+ reportFlow(
+ node.body,
+ targetFromAnnotation(node.returnType),
+ `return value of \`${functionName(context.sourceCode, node)}\``,
+ );
+ },
+ TSAsExpression(node) {
+ if (environment === null || hasParentAssertion(node)) return;
+ reportFlow(
+ node.expression,
+ classifyWideningTarget(node.typeAnnotation, environment),
+ "assertion",
+ );
+ },
+ TSTypeAssertion(node) {
+ if (environment === null || hasParentAssertion(node)) return;
+ reportFlow(
+ node.expression,
+ classifyWideningTarget(node.typeAnnotation, environment),
+ "assertion",
+ );
+ },
+ };
+ },
+});
diff --git a/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts b/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts
new file mode 100644
index 0000000..8cb615a
--- /dev/null
+++ b/tools/oxlint/anti-slop/rules/no-unsafe-dictionary-type.ts
@@ -0,0 +1,154 @@
+import { defineRule } from "@oxlint/plugins";
+
+import {
+ classifyUnsafeDictionary,
+ classifyUnsafeDictionaryValue,
+ createTypeEnvironment,
+ type TypeEnvironment,
+} from "../shared/dictionary-types.ts";
+import { visibleTypeAlias } from "../shared/type-alias-resolution.ts";
+
+import type { ESTree } from "@oxlint/plugins";
+
+const typeNodeKinds: ReadonlySet = new Set([
+ "JSDocNonNullableType",
+ "JSDocNullableType",
+ "JSDocUnknownType",
+ "TSAnyKeyword",
+ "TSArrayType",
+ "TSBigIntKeyword",
+ "TSBooleanKeyword",
+ "TSConditionalType",
+ "TSConstructorType",
+ "TSFunctionType",
+ "TSImportType",
+ "TSIndexedAccessType",
+ "TSInferType",
+ "TSIntersectionType",
+ "TSIntrinsicKeyword",
+ "TSLiteralType",
+ "TSMappedType",
+ "TSNamedTupleMember",
+ "TSNeverKeyword",
+ "TSNullKeyword",
+ "TSNumberKeyword",
+ "TSObjectKeyword",
+ "TSParenthesizedType",
+ "TSStringKeyword",
+ "TSSymbolKeyword",
+ "TSTemplateLiteralType",
+ "TSThisType",
+ "TSTupleType",
+ "TSTypeLiteral",
+ "TSTypeOperator",
+ "TSTypePredicate",
+ "TSTypeQuery",
+ "TSTypeReference",
+ "TSUndefinedKeyword",
+ "TSUnionType",
+ "TSUnknownKeyword",
+ "TSVoidKeyword",
+]);
+
+function isTypeNode(node: ESTree.Node): node is ESTree.TSType {
+ return typeNodeKinds.has(node.type);
+}
+
+function typeReferenceName(type: ESTree.TSTypeReference): string | null {
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
+}
+
+function isInsideTypeAliasDeclaration(node: ESTree.Node): boolean {
+ let current: ESTree.Node | null = node.parent;
+ while (current !== null && current.type !== "Program") {
+ if (current.type === "TSTypeAliasDeclaration") return true;
+ current = current.parent;
+ }
+ return false;
+}
+
+function isPlainAliasConsumerUse(node: ESTree.TSType, environment: TypeEnvironment): boolean {
+ if (node.type !== "TSTypeReference" || node.typeArguments?.params.length) return false;
+ const name = typeReferenceName(node);
+ return (
+ name !== null &&
+ visibleTypeAlias(name, node, environment.typeAliases) !== null &&
+ !isInsideTypeAliasDeclaration(node)
+ );
+}
+
+function isInsideTypeParameterConstraint(node: ESTree.TSType): boolean {
+ let child: ESTree.Node = node;
+ let parent: ESTree.Node | null = child.parent;
+ while (parent !== null && parent.type !== "Program") {
+ if (parent.type === "TSTypeParameter" && parent.constraint === child) return true;
+ child = parent;
+ parent = child.parent;
+ }
+ return false;
+}
+
+function shouldReportType(node: ESTree.TSType, environment: TypeEnvironment): boolean {
+ if (isInsideTypeParameterConstraint(node)) return false;
+ if (isPlainAliasConsumerUse(node, environment)) return false;
+ if (classifyUnsafeDictionary(node, environment) === null) return false;
+ let current: ESTree.Node | null = node.parent;
+ while (current !== null && current.type !== "Program") {
+ if (isTypeNode(current) && classifyUnsafeDictionary(current, environment) !== null)
+ return false;
+ current = current.parent;
+ }
+ return true;
+}
+
+/** Disallow object-dictionary contracts whose direct value type is an unsafe escape hatch. */
+export const noUnsafeDictionaryTypeRule = defineRule({
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Disallow object-dictionary contracts whose direct value type is unknown, any, object, {}, or a union/alias containing one of those escape hatches.",
+ },
+ messages: {
+ unsafeDictionary:
+ "This dictionary's {{value}} value type gives callers no concrete value contract. Use an owner/schema-derived value type; parse external payloads before insertion.",
+ },
+ },
+ createOnce(context) {
+ let environment: TypeEnvironment | null = null;
+ const report = (node: ESTree.Node, value: string) => {
+ context.report({ node, messageId: "unsafeDictionary", data: { value } });
+ };
+ const reportIfUnsafe = (node: ESTree.TSType) => {
+ if (environment === null || !shouldReportType(node, environment)) return;
+ const unsafe = classifyUnsafeDictionary(node, environment);
+ if (unsafe === null) return;
+ report(node, unsafe.unsafeValue);
+ };
+
+ return {
+ Program(node) {
+ environment = createTypeEnvironment(
+ node,
+ context.sourceCode.visitorKeys,
+ );
+ },
+ TSTypeReference: reportIfUnsafe,
+ TSTypeLiteral: reportIfUnsafe,
+ TSMappedType: reportIfUnsafe,
+ TSIndexSignature(node) {
+ if (
+ environment === null ||
+ node.typeAnnotation === null ||
+ node.parent.type === "TSTypeLiteral"
+ )
+ return;
+ const unsafe = classifyUnsafeDictionaryValue(
+ node.typeAnnotation.typeAnnotation,
+ environment,
+ );
+ if (unsafe !== null) report(node, unsafe.unsafeValue);
+ },
+ };
+ },
+});
diff --git a/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts b/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts
new file mode 100644
index 0000000..c5e07f7
--- /dev/null
+++ b/tools/oxlint/anti-slop/rules/no-widen-then-assert.ts
@@ -0,0 +1,366 @@
+import { defineRule } from "@oxlint/plugins";
+import type { ESTree, Variable } from "@oxlint/plugins";
+
+type BroadTypeKind = "top" | "object" | "record";
+
+type KnownValueEvidence = {
+ readonly type: ESTree.TSType | null;
+};
+
+const functionBoundaryTypes = new Set([
+ "ArrowFunctionExpression",
+ "FunctionDeclaration",
+ "FunctionExpression",
+ "TSDeclareFunction",
+ "TSEmptyBodyFunctionExpression",
+]);
+
+function unwrapExpressionParentheses(expression: ESTree.Expression): ESTree.Expression {
+ let current = expression;
+ while (current.type === "ParenthesizedExpression") current = current.expression;
+ return current;
+}
+
+function unwrapTypeParentheses(type: ESTree.TSType): ESTree.TSType {
+ let current = type;
+ while (current.type === "TSParenthesizedType") current = current.typeAnnotation;
+ return current;
+}
+
+function typeReferenceName(type: ESTree.TSTypeReference): string | null {
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
+}
+
+function isUnknownOrAnyType(type: ESTree.TSType): boolean {
+ const unwrapped = unwrapTypeParentheses(type);
+ return unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword";
+}
+
+function isBroadRecordKeyType(type: ESTree.TSType): boolean {
+ const unwrapped = unwrapTypeParentheses(type);
+ if (
+ unwrapped.type === "TSStringKeyword" ||
+ unwrapped.type === "TSNumberKeyword" ||
+ unwrapped.type === "TSSymbolKeyword"
+ ) {
+ return true;
+ }
+ if (unwrapped.type === "TSUnionType") return unwrapped.types.every(isBroadRecordKeyType);
+ return unwrapped.type === "TSTypeReference" && typeReferenceName(unwrapped) === "PropertyKey";
+}
+
+function isBroadRecordType(type: ESTree.TSType): boolean {
+ const unwrapped = unwrapTypeParentheses(type);
+
+ if (unwrapped.type === "TSTypeReference") {
+ if (typeReferenceName(unwrapped) === "Readonly") {
+ const [inner] = unwrapped.typeArguments?.params ?? [];
+ return inner !== undefined && isBroadRecordType(inner);
+ }
+
+ if (typeReferenceName(unwrapped) !== "Record") return false;
+ const parameters = unwrapped.typeArguments?.params ?? [];
+ return (
+ parameters.length === 2 &&
+ parameters[0] !== undefined &&
+ parameters[1] !== undefined &&
+ isBroadRecordKeyType(parameters[0]) &&
+ isUnknownOrAnyType(parameters[1])
+ );
+ }
+
+ if (unwrapped.type !== "TSTypeLiteral" || unwrapped.members.length !== 1) return false;
+ const [member] = unwrapped.members;
+ const [parameter] = member?.type === "TSIndexSignature" ? member.parameters : [];
+ return (
+ member?.type === "TSIndexSignature" &&
+ member.parameters.length === 1 &&
+ parameter !== undefined &&
+ isBroadRecordKeyType(parameter.typeAnnotation.typeAnnotation) &&
+ isUnknownOrAnyType(member.typeAnnotation.typeAnnotation)
+ );
+}
+
+function broadTypeKind(type: ESTree.TSType): BroadTypeKind | null {
+ const unwrapped = unwrapTypeParentheses(type);
+ if (unwrapped.type === "TSUnknownKeyword" || unwrapped.type === "TSAnyKeyword") return "top";
+ if (unwrapped.type === "TSObjectKeyword") return "object";
+ return isBroadRecordType(unwrapped) ? "record" : null;
+}
+
+function assertedExpression(
+ node: ESTree.TSAsExpression | ESTree.TSTypeAssertion,
+): ESTree.Expression {
+ return unwrapExpressionParentheses(node.expression);
+}
+
+function assertionFromExpression(
+ expression: ESTree.Expression,
+): ESTree.TSAsExpression | ESTree.TSTypeAssertion | null {
+ const unwrapped = unwrapExpressionParentheses(expression);
+ return unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion"
+ ? unwrapped
+ : null;
+}
+
+function normalizedTypeText(sourceText: string, type: ESTree.TSType): string {
+ return sourceText.slice(type.start, type.end).replaceAll(/\s+/gu, "");
+}
+
+function typesHaveSameSyntax(
+ sourceText: string,
+ left: ESTree.TSType | null,
+ right: ESTree.TSType,
+): boolean {
+ return (
+ left !== null &&
+ normalizedTypeText(sourceText, unwrapTypeParentheses(left)) ===
+ normalizedTypeText(sourceText, unwrapTypeParentheses(right))
+ );
+}
+
+function isDefinitelyObjectType(type: ESTree.TSType): boolean {
+ const unwrapped = unwrapTypeParentheses(type);
+ switch (unwrapped.type) {
+ case "TSArrayType":
+ case "TSConstructorType":
+ case "TSFunctionType":
+ case "TSMappedType":
+ case "TSObjectKeyword":
+ case "TSTupleType":
+ return true;
+ case "TSTypeLiteral":
+ return unwrapped.members.length > 0;
+ case "TSIntersectionType":
+ return unwrapped.types.every(isDefinitelyObjectType);
+ case "TSTypeOperator":
+ return unwrapped.operator === "readonly" && isDefinitelyObjectType(unwrapped.typeAnnotation);
+ default:
+ return false;
+ }
+}
+
+function isDefinitelyNarrowerRecordType(type: ESTree.TSType): boolean {
+ const unwrapped = unwrapTypeParentheses(type);
+ if (unwrapped.type === "TSTypeLiteral") {
+ return unwrapped.members.some((member) => member.type !== "TSIndexSignature");
+ }
+
+ if (unwrapped.type !== "TSTypeReference") return false;
+ if (typeReferenceName(unwrapped) === "Readonly") {
+ const [inner] = unwrapped.typeArguments?.params ?? [];
+ return inner !== undefined && isDefinitelyNarrowerRecordType(inner);
+ }
+ if (typeReferenceName(unwrapped) !== "Record") return false;
+
+ const parameters = unwrapped.typeArguments?.params ?? [];
+ return (
+ parameters.length === 2 && parameters[1] !== undefined && !isUnknownOrAnyType(parameters[1])
+ );
+}
+
+function functionBoundary(node: ESTree.Node): ESTree.Node | null {
+ let current = node.parent;
+ while (current !== null && current.type !== "Program") {
+ if (functionBoundaryTypes.has(current.type)) return current;
+ current = current.parent;
+ }
+ return null;
+}
+
+function resolvedVariableForIdentifier(
+ scopes: readonly {
+ readonly references: readonly {
+ readonly identifier: ESTree.Node;
+ readonly resolved: Variable | null;
+ }[];
+ }[],
+ identifier: ESTree.IdentifierReference,
+): Variable | null {
+ for (const scope of scopes) {
+ const reference = scope.references.find(
+ (candidate) =>
+ candidate.identifier.start === identifier.start &&
+ candidate.identifier.end === identifier.end,
+ );
+ if (reference !== undefined) return reference.resolved;
+ }
+ return null;
+}
+
+function variableDeclarator(variable: Variable): ESTree.VariableDeclarator | null {
+ for (const definition of variable.defs) {
+ if (definition.type === "Variable" && definition.node.type === "VariableDeclarator") {
+ return definition.node;
+ }
+ }
+ return null;
+}
+
+function knownValueEvidence(
+ expression: ESTree.Expression,
+ scopes: Parameters[0],
+ boundary: ESTree.Node | null,
+ visitedVariables: ReadonlySet,
+): KnownValueEvidence | null {
+ const unwrapped = unwrapExpressionParentheses(expression);
+
+ if (unwrapped.type === "TSAsExpression" || unwrapped.type === "TSTypeAssertion") {
+ if (broadTypeKind(unwrapped.typeAnnotation) !== null) return null;
+ return { type: unwrapped.typeAnnotation };
+ }
+
+ if (unwrapped.type === "Literal" || unwrapped.type === "TemplateLiteral") {
+ return { type: null };
+ }
+
+ if (
+ unwrapped.type === "ArrayExpression" ||
+ unwrapped.type === "ArrowFunctionExpression" ||
+ unwrapped.type === "ClassExpression" ||
+ unwrapped.type === "FunctionExpression" ||
+ unwrapped.type === "NewExpression" ||
+ unwrapped.type === "ObjectExpression"
+ ) {
+ return { type: null };
+ }
+
+ if (unwrapped.type !== "Identifier") return null;
+ const variable = resolvedVariableForIdentifier(scopes, unwrapped);
+ if (variable === null || visitedVariables.has(variable)) return null;
+
+ const annotatedIdentifier = variable.identifiers.find(
+ (identifier) => identifier.typeAnnotation !== null && identifier.typeAnnotation !== undefined,
+ );
+ const annotation = annotatedIdentifier?.typeAnnotation?.typeAnnotation;
+ if (annotation !== undefined && annotatedIdentifier !== undefined) {
+ if (functionBoundary(annotatedIdentifier) !== boundary || broadTypeKind(annotation) !== null) {
+ return null;
+ }
+ return { type: annotation };
+ }
+
+ const declarator = variableDeclarator(variable);
+ if (
+ declarator === null ||
+ declarator.parent.type !== "VariableDeclaration" ||
+ declarator.parent.kind !== "const" ||
+ declarator.init === null ||
+ variable.references.some((reference) => reference.isWrite() && !reference.init) ||
+ functionBoundary(declarator) !== boundary
+ ) {
+ return null;
+ }
+
+ return knownValueEvidence(
+ declarator.init,
+ scopes,
+ boundary,
+ new Set([...visitedVariables, variable]),
+ );
+}
+
+function widenedBinding(
+ variable: Variable,
+ scopes: Parameters[0],
+): {
+ readonly broadKind: BroadTypeKind;
+ readonly evidence: KnownValueEvidence;
+ readonly declaredAt: number;
+ readonly boundary: ESTree.Node | null;
+} | null {
+ const declarator = variableDeclarator(variable);
+ if (
+ declarator === null ||
+ declarator.parent.type !== "VariableDeclaration" ||
+ declarator.parent.kind !== "const" ||
+ declarator.id.type !== "Identifier" ||
+ declarator.init === null ||
+ variable.references.some((reference) => reference.isWrite() && !reference.init)
+ ) {
+ return null;
+ }
+
+ const boundary = functionBoundary(declarator);
+ const declaredType = declarator.id.typeAnnotation?.typeAnnotation;
+ const initializerAssertion = assertionFromExpression(declarator.init);
+ const initializerBroadKind =
+ initializerAssertion === null ? null : broadTypeKind(initializerAssertion.typeAnnotation);
+ const declaredBroadKind = declaredType === undefined ? null : broadTypeKind(declaredType);
+ const broadKind = declaredBroadKind ?? initializerBroadKind;
+ if (broadKind === null) return null;
+
+ const originalExpression =
+ initializerAssertion !== null && initializerBroadKind !== null
+ ? assertedExpression(initializerAssertion)
+ : declarator.init;
+ const evidence = knownValueEvidence(originalExpression, scopes, boundary, new Set([variable]));
+ return evidence === null ? null : { broadKind, evidence, declaredAt: declarator.end, boundary };
+}
+
+function assertionIsNarrower(
+ sourceText: string,
+ broadKind: BroadTypeKind,
+ evidence: KnownValueEvidence,
+ assertedType: ESTree.TSType,
+): boolean {
+ if (broadTypeKind(assertedType) !== null) return false;
+ if (broadKind === "top") return true;
+ if (typesHaveSameSyntax(sourceText, evidence.type, assertedType)) return true;
+ if (broadKind === "object") return isDefinitelyObjectType(assertedType);
+ return isDefinitelyNarrowerRecordType(assertedType);
+}
+
+/** Detect immutable local bindings that erase a known type and are later asserted back to a narrower type. */
+export const noWidenThenAssertRule = defineRule({
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Disallow local const flows that explicitly widen a known value before asserting the widened binding to a narrower type.",
+ },
+ messages: {
+ widenThenAssert:
+ 'Binding "{{name}}" discards type evidence and later recreates it with an assertion. Keep the precise type from initialization through use; parse boundary input once.',
+ },
+ },
+ createOnce(context) {
+ let scopes: Parameters[0] = [];
+
+ const checkAssertion = (node: ESTree.TSAsExpression | ESTree.TSTypeAssertion) => {
+ const expression = assertedExpression(node);
+ if (expression.type !== "Identifier") return;
+
+ const variable = resolvedVariableForIdentifier(scopes, expression);
+ if (variable === null) return;
+ const widened = widenedBinding(variable, scopes);
+ if (
+ widened === null ||
+ node.start <= widened.declaredAt ||
+ functionBoundary(node) !== widened.boundary ||
+ !assertionIsNarrower(
+ context.sourceCode.text,
+ widened.broadKind,
+ widened.evidence,
+ node.typeAnnotation,
+ )
+ ) {
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: "widenThenAssert",
+ data: { name: expression.name },
+ });
+ };
+
+ return {
+ Program() {
+ scopes = context.sourceCode.scopeManager.scopes;
+ },
+ TSAsExpression: checkAssertion,
+ TSTypeAssertion: checkAssertion,
+ };
+ },
+});
diff --git a/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts b/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts
new file mode 100644
index 0000000..bfea1a2
--- /dev/null
+++ b/tools/oxlint/anti-slop/rules/require-safety-comment-for-type-assertion.ts
@@ -0,0 +1,131 @@
+import { defineRule } from "@oxlint/plugins";
+
+import type { ESTree, SourceCode } from "@oxlint/plugins";
+
+type TypeAssertion = ESTree.TSAsExpression | ESTree.TSTypeAssertion;
+
+const DEFAULT_SAFETY_MARKERS = ["SAFETY"] as const;
+
+const commentOwnerKinds = new Set([
+ "ExpressionStatement",
+ "PropertyDefinition",
+ "ReturnStatement",
+ "ThrowStatement",
+ "VariableDeclaration",
+]);
+
+function isConstAssertion(node: TypeAssertion): boolean {
+ return (
+ node.typeAnnotation.type === "TSTypeReference" &&
+ node.typeAnnotation.typeName.type === "Identifier" &&
+ node.typeAnnotation.typeName.name === "const"
+ );
+}
+
+function configuredSafetyMarkers(option: unknown): readonly string[] {
+ if (typeof option !== "object" || option === null || !("markers" in option)) {
+ return DEFAULT_SAFETY_MARKERS;
+ }
+ const configured = option.markers;
+ if (!Array.isArray(configured)) return DEFAULT_SAFETY_MARKERS;
+ const markers = configured.flatMap((marker) =>
+ typeof marker === "string" && marker.trim().length > 0 ? [marker.trim()] : [],
+ );
+ return markers.length > 0 ? markers : DEFAULT_SAFETY_MARKERS;
+}
+
+function markerPattern(markers: readonly string[]): RegExp {
+ const alternation = markers
+ .map((marker) => marker.replaceAll(/[.*+?^${}()|[\]\\]/gu, String.raw`\$&`))
+ .join("|");
+ return new RegExp(
+ String.raw`(?:^|[^\p{L}\p{N}_])(?:${alternation})\s*:\s*\S`,
+ "u",
+ );
+}
+
+function hasSafetyJustificationBefore(
+ sourceCode: SourceCode,
+ owner: ESTree.Node,
+ assertion: TypeAssertion,
+ pattern: RegExp,
+): boolean {
+ return sourceCode
+ .getCommentsBefore(owner)
+ .some(
+ (comment) => comment.end <= assertion.start && pattern.test(comment.value),
+ );
+}
+
+function hasSafetyComment(
+ sourceCode: SourceCode,
+ node: TypeAssertion,
+ pattern: RegExp,
+): boolean {
+ let current: ESTree.Node = node;
+ while (true) {
+ if (hasSafetyJustificationBefore(sourceCode, current, node, pattern)) return true;
+ if (commentOwnerKinds.has(current.type)) {
+ const exportDeclaration = current.parent;
+ return (
+ exportDeclaration.type === "ExportNamedDeclaration" &&
+ exportDeclaration.declaration === current &&
+ hasSafetyJustificationBefore(sourceCode, exportDeclaration, node, pattern)
+ );
+ }
+ if (current.parent.type === "Program") return false;
+ current = current.parent;
+ }
+}
+
+/** Require every non-const type assertion to state the invariant TypeScript cannot express. */
+export const requireSafetyCommentForTypeAssertionRule = defineRule({
+ meta: {
+ type: "problem",
+ docs: {
+ description:
+ "Require a nearby SAFETY comment for every TypeScript type assertion except const assertions.",
+ },
+ messages: {
+ missingSafetyComment:
+ "This type assertion has no `{{marker}}:` justification. State the checked invariant immediately before the assertion or its containing statement.",
+ },
+ schema: [
+ {
+ type: "object",
+ properties: {
+ markers: {
+ type: "array",
+ items: { type: "string", minLength: 1 },
+ minItems: 1,
+ uniqueItems: true,
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ defaultOptions: [{ markers: ["SAFETY"] }],
+ },
+ createOnce(context) {
+ const patterns = new Map();
+
+ const checkAssertion = (node: TypeAssertion) => {
+ if (isConstAssertion(node)) return;
+ const markers = configuredSafetyMarkers(context.options?.[0]);
+ const patternKey = markers.join("\u0000");
+ const pattern = patterns.get(patternKey) ?? markerPattern(markers);
+ patterns.set(patternKey, pattern);
+ if (hasSafetyComment(context.sourceCode, node, pattern)) return;
+ context.report({
+ node,
+ messageId: "missingSafetyComment",
+ data: { marker: markers[0] ?? DEFAULT_SAFETY_MARKERS[0] },
+ });
+ };
+
+ return {
+ TSAsExpression: checkAssertion,
+ TSTypeAssertion: checkAssertion,
+ };
+ },
+});
diff --git a/tools/oxlint/anti-slop/shared/dictionary-types.ts b/tools/oxlint/anti-slop/shared/dictionary-types.ts
new file mode 100644
index 0000000..8db73ff
--- /dev/null
+++ b/tools/oxlint/anti-slop/shared/dictionary-types.ts
@@ -0,0 +1,515 @@
+import type { ESTree } from "@oxlint/plugins";
+
+import {
+ createTypeAliasEnvironment,
+ hasVisibleTypeBinding,
+ visibleTypeAlias,
+ type TypeAliasEnvironment as LexicalTypeAliasEnvironment,
+} from "./type-alias-resolution.ts";
+
+const BUILT_INS = new Set([
+ "Record",
+ "Readonly",
+ "Partial",
+ "Required",
+ "Pick",
+ "Omit",
+ "PropertyKey",
+ "NonNullable",
+]);
+const TRANSPARENT_WRAPPERS = new Set(["Readonly", "Partial", "Required", "NonNullable"]);
+
+type TypeAliasEnvironment = ReadonlyMap;
+
+type ResolvedType = {
+ readonly type: ESTree.TSType;
+ readonly substitutions: TypeAliasEnvironment;
+};
+
+export type UnsafeDictionary = {
+ readonly kind: "unsafe-dictionary";
+ readonly unsafeValue: "any" | "empty-object" | "object" | "union" | "unknown";
+};
+
+export type WideningTargetKind =
+ | "anonymous object"
+ | "generic container"
+ | "object"
+ | "open dictionary"
+ | "unknown";
+
+export type WideningTarget = {
+ readonly kind: WideningTargetKind;
+};
+
+export type TypeEnvironment = {
+ readonly interfaces: ReadonlyMap;
+ readonly typeAliases: LexicalTypeAliasEnvironment;
+};
+
+function declaredStatement(statement: ESTree.Statement): ESTree.Node | null {
+ return statement.type === "ExportNamedDeclaration" ||
+ statement.type === "ExportDefaultDeclaration"
+ ? (statement.declaration ?? null)
+ : statement;
+}
+
+export function createTypeEnvironment(
+ program: ESTree.Program,
+ visitorKeys: Readonly>,
+): TypeEnvironment {
+ const interfaces = new Map();
+
+ for (const statement of program.body) {
+ const declaration = declaredStatement(statement);
+ if (declaration?.type !== "TSInterfaceDeclaration") continue;
+ const declarations = interfaces.get(declaration.id.name) ?? [];
+ declarations.push(declaration);
+ interfaces.set(declaration.id.name, declarations);
+ }
+
+ return {
+ interfaces,
+ typeAliases: createTypeAliasEnvironment(program, visitorKeys),
+ };
+}
+
+function typeReferenceName(type: ESTree.TSTypeReference): string | null {
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
+}
+
+function isBuiltIn(
+ name: string,
+ use: ESTree.Node,
+ environment: TypeEnvironment,
+): boolean {
+ return (
+ BUILT_INS.has(name) &&
+ !hasVisibleTypeBinding(name, use, environment.typeAliases)
+ );
+}
+
+function isUnappliedReferenceTo(type: ESTree.TSType, name: string): boolean {
+ const unwrapped = unwrapTransparentType(type);
+ return (
+ unwrapped.type === "TSTypeReference" &&
+ typeReferenceName(unwrapped) === name &&
+ (unwrapped.typeArguments === null ||
+ unwrapped.typeArguments === undefined ||
+ unwrapped.typeArguments.params.length === 0)
+ );
+}
+
+function unwrapTransparentType(type: ESTree.TSType): ESTree.TSType {
+ let current = type;
+ while (
+ current.type === "TSParenthesizedType" ||
+ (current.type === "TSTypeOperator" && current.operator === "readonly")
+ ) {
+ current = current.typeAnnotation;
+ }
+ return current;
+}
+
+function isNeverType(type: ESTree.TSType): boolean {
+ return unwrapTransparentType(type).type === "TSNeverKeyword";
+}
+
+function isEffectivelyEmptyMember(member: ESTree.TSSignature): boolean {
+ return (
+ member.type === "TSPropertySignature" &&
+ member.optional === true &&
+ member.typeAnnotation !== null &&
+ member.typeAnnotation !== undefined &&
+ isNeverType(member.typeAnnotation.typeAnnotation)
+ );
+}
+
+function isEffectivelyEmptyTypeLiteral(type: ESTree.TSTypeLiteral): boolean {
+ return type.members.length === 0 || type.members.every(isEffectivelyEmptyMember);
+}
+
+function isEffectivelyEmptyInterface(
+ declarations: readonly ESTree.TSInterfaceDeclaration[],
+): boolean {
+ if (declarations.length !== 1) return false;
+ const [type] = declarations;
+ return (
+ type !== undefined &&
+ type.extends.length === 0 &&
+ (type.body.body.length === 0 || type.body.body.every(isEffectivelyEmptyMember))
+ );
+}
+
+function resolvedSubstitutionArgument(
+ type: ESTree.TSType,
+ base: TypeAliasEnvironment,
+ resolving: ReadonlySet = new Set(),
+): ESTree.TSType {
+ const unwrapped = unwrapTransparentType(type);
+ if (unwrapped.type !== "TSTypeReference") return type;
+ const name = typeReferenceName(unwrapped);
+ if (name === null || resolving.has(name)) return type;
+ const substitution = base.get(name);
+ if (substitution === undefined) return type;
+ const nextResolving = new Set(resolving);
+ nextResolving.add(name);
+ return resolvedSubstitutionArgument(substitution, base, nextResolving);
+}
+
+function aliasSubstitution(
+ alias: ESTree.TSTypeAliasDeclaration,
+ type: ESTree.TSTypeReference,
+ base: TypeAliasEnvironment,
+): TypeAliasEnvironment | null {
+ const parameters = alias.typeParameters?.params ?? [];
+ const arguments_ = type.typeArguments?.params ?? [];
+ const next = new Map(base);
+ for (const [index, parameter] of parameters.entries()) {
+ const argument = arguments_[index] ?? parameter.default;
+ if (argument === null || argument === undefined) return null;
+ next.set(parameter.name.name, resolvedSubstitutionArgument(argument, next));
+ }
+ return next;
+}
+
+function unsafeDirectValue(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+ substitutions: TypeAliasEnvironment,
+ resolvingAliases: ReadonlySet,
+): UnsafeDictionary["unsafeValue"] | null {
+ const unwrapped = unwrapTransparentType(type);
+ if (unwrapped.type === "TSUnknownKeyword") return "unknown";
+ if (unwrapped.type === "TSAnyKeyword") return "any";
+ if (unwrapped.type === "TSObjectKeyword") return "object";
+ if (unwrapped.type === "TSTypeLiteral" && isEffectivelyEmptyTypeLiteral(unwrapped))
+ return "empty-object";
+ if (unwrapped.type === "TSUnionType") {
+ return unwrapped.types.some(
+ (member) => unsafeDirectValue(member, environment, substitutions, resolvingAliases) !== null,
+ )
+ ? "union"
+ : null;
+ }
+ if (unwrapped.type === "TSIntersectionType") {
+ const unsafeMembers = unwrapped.types.map((member) =>
+ unsafeDirectValue(member, environment, substitutions, resolvingAliases),
+ );
+ if (unsafeMembers.includes("any")) return "any";
+ return unsafeMembers.length > 0 && unsafeMembers.every((member) => member !== null)
+ ? unsafeMembers[0]
+ : null;
+ }
+ if (unwrapped.type !== "TSTypeReference") return null;
+ const name = typeReferenceName(unwrapped);
+ if (name === null) return null;
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
+ const wrapped = unwrapped.typeArguments?.params[0];
+ return wrapped === undefined
+ ? null
+ : unsafeDirectValue(wrapped, environment, substitutions, resolvingAliases);
+ }
+ const substitution = substitutions.get(name);
+ if (substitution !== undefined) {
+ return isUnappliedReferenceTo(substitution, name)
+ ? null
+ : unsafeDirectValue(substitution, environment, substitutions, resolvingAliases);
+ }
+ const interfaceDeclarations = environment.interfaces.get(name);
+ if (interfaceDeclarations !== undefined) {
+ return isEffectivelyEmptyInterface(interfaceDeclarations) ? "empty-object" : null;
+ }
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
+ if (alias === null || resolvingAliases.has(name)) return null;
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
+ if (nextSubstitutions === null) return null;
+ const nextResolving = new Set(resolvingAliases);
+ nextResolving.add(name);
+ return unsafeDirectValue(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
+}
+
+function dictionaryValueTypes(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+ substitutions: TypeAliasEnvironment,
+ resolvingAliases: ReadonlySet,
+): readonly ResolvedType[] {
+ const unwrapped = unwrapTransparentType(type);
+
+ if (unwrapped.type === "TSTypeLiteral") {
+ return unwrapped.members.flatMap((member): readonly ResolvedType[] =>
+ member.type === "TSIndexSignature" && member.typeAnnotation !== null
+ ? [{ type: member.typeAnnotation.typeAnnotation, substitutions }]
+ : [],
+ );
+ }
+
+ if (unwrapped.type === "TSMappedType") {
+ return unwrapped.typeAnnotation === null
+ ? []
+ : [{ type: unwrapped.typeAnnotation, substitutions }];
+ }
+
+ if (unwrapped.type !== "TSTypeReference") return [];
+ const name = typeReferenceName(unwrapped);
+ if (name === null) return [];
+
+ const substitution = substitutions.get(name);
+ if (substitution !== undefined) {
+ return isUnappliedReferenceTo(substitution, name)
+ ? []
+ : dictionaryValueTypes(substitution, environment, substitutions, resolvingAliases);
+ }
+
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
+ const wrapped = unwrapped.typeArguments?.params[0];
+ return wrapped === undefined
+ ? []
+ : dictionaryValueTypes(wrapped, environment, substitutions, resolvingAliases);
+ }
+
+ if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
+ const value = unwrapped.typeArguments?.params[1] ?? null;
+ return value === null ? [] : [{ type: value, substitutions }];
+ }
+
+ if (
+ (name === "Pick" || name === "Omit") &&
+ isBuiltIn(name, unwrapped, environment)
+ ) {
+ const source = unwrapped.typeArguments?.params[0];
+ return source === undefined
+ ? []
+ : dictionaryValueTypes(source, environment, substitutions, resolvingAliases);
+ }
+
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
+ if (alias === null || resolvingAliases.has(name)) return [];
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
+ if (nextSubstitutions === null) return [];
+ const nextResolving = new Set(resolvingAliases);
+ nextResolving.add(name);
+ return dictionaryValueTypes(alias.typeAnnotation, environment, nextSubstitutions, nextResolving);
+}
+
+export function classifyUnsafeDictionaryValue(
+ valueType: ESTree.TSType,
+ environment: TypeEnvironment,
+): UnsafeDictionary | null {
+ const unsafeValue = unsafeDirectValue(valueType, environment, new Map(), new Set());
+ return unsafeValue === null ? null : { kind: "unsafe-dictionary", unsafeValue };
+}
+
+export function classifyUnsafeDictionary(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+): UnsafeDictionary | null {
+ for (const valueType of dictionaryValueTypes(type, environment, new Map(), new Set())) {
+ const unsafeValue = unsafeDirectValue(
+ valueType.type,
+ environment,
+ valueType.substitutions,
+ new Set(),
+ );
+ if (unsafeValue !== null) return { kind: "unsafe-dictionary", unsafeValue };
+ }
+ return null;
+}
+
+export function classifyWideningTarget(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+): WideningTarget | null {
+ const unwrapped = unwrapTransparentType(type);
+ if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
+ if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
+ if (unwrapped.type === "TSTypeLiteral") {
+ return unwrapped.members.some((member) => member.type === "TSIndexSignature")
+ ? { kind: "open dictionary" }
+ : unwrapped.members.length > 0
+ ? { kind: "anonymous object" }
+ : null;
+ }
+ if (unwrapped.type === "TSMappedType") return { kind: "open dictionary" };
+ if (unwrapped.type !== "TSTypeReference") return null;
+ const name = typeReferenceName(unwrapped);
+ if (name === null) return null;
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
+ const wrapped = unwrapped.typeArguments?.params[0];
+ return wrapped === undefined ? null : classifyWideningTarget(wrapped, environment);
+ }
+ if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
+ return hasBroadRecordKey(unwrapped, environment, new Map())
+ ? { kind: "open dictionary" }
+ : null;
+ }
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
+ if (alias === null) return null;
+ if ((alias.typeParameters?.params.length ?? 0) > 0) {
+ const substitutions = aliasSubstitution(alias, unwrapped, new Map());
+ const resolved =
+ substitutions === null
+ ? null
+ : classifyAliasBroadTarget(
+ alias.typeAnnotation,
+ environment,
+ substitutions,
+ new Set([name]),
+ );
+ return resolved?.kind === "open dictionary" ? { kind: "generic container" } : null;
+ }
+ const substitutions = aliasSubstitution(alias, unwrapped, new Map());
+ if (substitutions === null) return null;
+ const resolved = classifyAliasBroadTarget(
+ alias.typeAnnotation,
+ environment,
+ substitutions,
+ new Set([name]),
+ );
+ return resolved;
+}
+
+function hasBroadRecordKey(
+ type: ESTree.TSTypeReference,
+ environment: TypeEnvironment,
+ substitutions: TypeAliasEnvironment,
+): boolean {
+ const key = type.typeArguments?.params[0];
+ return key === undefined || isBroadMappedKey(key, environment, substitutions);
+}
+
+function isBroadMappedKey(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+ substitutions: TypeAliasEnvironment,
+ visitedAliases: ReadonlySet = new Set(),
+): boolean {
+ const unwrapped = unwrapTransparentType(type);
+ if (
+ unwrapped.type === "TSStringKeyword" ||
+ unwrapped.type === "TSNumberKeyword" ||
+ unwrapped.type === "TSSymbolKeyword"
+ ) {
+ return true;
+ }
+ if (unwrapped.type === "TSUnionType") {
+ return unwrapped.types.some((member) =>
+ isBroadMappedKey(member, environment, substitutions, visitedAliases),
+ );
+ }
+ if (unwrapped.type !== "TSTypeReference") return false;
+ const name = typeReferenceName(unwrapped);
+ if (name === null) return false;
+ const substitution = substitutions.get(name);
+ if (substitution !== undefined && !isUnappliedReferenceTo(substitution, name)) {
+ return isBroadMappedKey(substitution, environment, substitutions, visitedAliases);
+ }
+ if (name === "PropertyKey" && isBuiltIn(name, unwrapped, environment)) return true;
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
+ if (
+ alias === null ||
+ (alias.typeParameters?.params.length ?? 0) > 0 ||
+ visitedAliases.has(name)
+ ) {
+ return false;
+ }
+ const nextVisited = new Set(visitedAliases);
+ nextVisited.add(name);
+ return isBroadMappedKey(alias.typeAnnotation, environment, substitutions, nextVisited);
+}
+
+function classifyAliasBroadTarget(
+ type: ESTree.TSType,
+ environment: TypeEnvironment,
+ substitutions: TypeAliasEnvironment,
+ resolvingAliases: ReadonlySet,
+): WideningTarget | null {
+ const unwrapped = unwrapTransparentType(type);
+ if (unwrapped.type === "TSUnknownKeyword") return { kind: "unknown" };
+ if (unwrapped.type === "TSObjectKeyword") return { kind: "object" };
+ if (unwrapped.type === "TSTypeLiteral") {
+ return unwrapped.members.some((member) => member.type === "TSIndexSignature")
+ ? { kind: "open dictionary" }
+ : null;
+ }
+ if (unwrapped.type === "TSMappedType") {
+ return isBroadMappedKey(unwrapped.constraint, environment, substitutions)
+ ? { kind: "open dictionary" }
+ : null;
+ }
+ if (unwrapped.type !== "TSTypeReference") return null;
+ const name = typeReferenceName(unwrapped);
+ if (name === null) return null;
+ const substitution = substitutions.get(name);
+ if (substitution !== undefined) {
+ return isUnappliedReferenceTo(substitution, name)
+ ? null
+ : classifyAliasBroadTarget(
+ substitution,
+ environment,
+ substitutions,
+ resolvingAliases,
+ );
+ }
+ if (TRANSPARENT_WRAPPERS.has(name) && isBuiltIn(name, unwrapped, environment)) {
+ const wrapped = unwrapped.typeArguments?.params[0];
+ return wrapped === undefined
+ ? null
+ : classifyAliasBroadTarget(wrapped, environment, substitutions, resolvingAliases);
+ }
+ if (name === "Record" && isBuiltIn(name, unwrapped, environment)) {
+ return hasBroadRecordKey(unwrapped, environment, substitutions)
+ ? { kind: "open dictionary" }
+ : null;
+ }
+ const alias = visibleTypeAlias(name, unwrapped, environment.typeAliases);
+ if (alias === null || resolvingAliases.has(name)) return null;
+ const nextSubstitutions = aliasSubstitution(alias, unwrapped, substitutions);
+ if (nextSubstitutions === null) return null;
+ const nextResolving = new Set(resolvingAliases);
+ nextResolving.add(name);
+ return classifyAliasBroadTarget(
+ alias.typeAnnotation,
+ environment,
+ nextSubstitutions,
+ nextResolving,
+ );
+}
+
+export function isPopulatedObjectExpression(expression: ESTree.Expression): boolean {
+ let current = expression;
+ while (
+ current.type === "ParenthesizedExpression" ||
+ current.type === "TSAsExpression" ||
+ current.type === "TSTypeAssertion" ||
+ current.type === "TSNonNullExpression"
+ ) {
+ current = current.expression;
+ }
+ return current.type === "ObjectExpression" && current.properties.length > 0;
+}
+
+export function isKnownEvidenceExpression(expression: ESTree.Expression): boolean {
+ let current = expression;
+ while (
+ current.type === "ParenthesizedExpression" ||
+ current.type === "TSAsExpression" ||
+ current.type === "TSTypeAssertion" ||
+ current.type === "TSNonNullExpression" ||
+ current.type === "TSSatisfiesExpression"
+ ) {
+ current = current.expression;
+ }
+ if (current.type === "ObjectExpression") return true;
+ return (
+ current.type === "ArrayExpression" ||
+ current.type === "ArrowFunctionExpression" ||
+ current.type === "ClassExpression" ||
+ current.type === "FunctionExpression" ||
+ current.type === "NewExpression" ||
+ current.type === "Literal" ||
+ current.type === "TemplateLiteral" ||
+ current.type === "UnaryExpression"
+ );
+}
diff --git a/tools/oxlint/anti-slop/shared/function-parameters.ts b/tools/oxlint/anti-slop/shared/function-parameters.ts
new file mode 100644
index 0000000..80de91d
--- /dev/null
+++ b/tools/oxlint/anti-slop/shared/function-parameters.ts
@@ -0,0 +1,49 @@
+import type { ESTree, SourceCode } from "@oxlint/plugins";
+
+export type FunctionParameter = ESTree.ParamPattern;
+
+/** Return whether a type is or contains TypeScript's absorbing unknown top type. */
+export function containsUnknownType(type: ESTree.TSType): boolean {
+ if (type.type === "TSUnknownKeyword") return true;
+ if (type.type === "TSParenthesizedType") return containsUnknownType(type.typeAnnotation);
+ return type.type === "TSUnionType" && type.types.some(containsUnknownType);
+}
+
+/** Return the TypeScript annotation attached to a function parameter or its wrapped binding. */
+export function functionParameterTypeAnnotation(
+ parameter: FunctionParameter,
+): ESTree.TSTypeAnnotation | null | undefined {
+ if (parameter.type === "TSParameterProperty") {
+ return functionParameterTypeAnnotation(parameter.parameter);
+ }
+ if (parameter.type === "RestElement") {
+ return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.argument);
+ }
+ if (parameter.type === "AssignmentPattern") {
+ return parameter.typeAnnotation ?? functionParameterTypeAnnotation(parameter.left);
+ }
+ return parameter.typeAnnotation;
+}
+
+/** Return only a function parameter's local binding, excluding its annotation and default value. */
+export function functionParameterBindingName(
+ parameter: FunctionParameter,
+ sourceCode: SourceCode,
+): string {
+ if (parameter.type === "TSParameterProperty") {
+ return functionParameterBindingName(parameter.parameter, sourceCode);
+ }
+ if (parameter.type === "AssignmentPattern") {
+ return functionParameterBindingName(parameter.left, sourceCode);
+ }
+ if (parameter.type === "RestElement") {
+ return functionParameterBindingName(parameter.argument, sourceCode);
+ }
+ if (parameter.type === "Identifier") return parameter.name;
+
+ const sourceText = sourceCode.getText(parameter);
+ const annotationStart = parameter.typeAnnotation?.start;
+ return annotationStart === undefined
+ ? sourceText
+ : sourceText.slice(0, annotationStart - parameter.start).trimEnd();
+}
diff --git a/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts b/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts
new file mode 100644
index 0000000..7cdb18c
--- /dev/null
+++ b/tools/oxlint/anti-slop/shared/lexical-type-parameters.ts
@@ -0,0 +1,61 @@
+import type { ESTree } from "@oxlint/plugins";
+
+type VisitorKeys = Readonly>;
+
+function isNode(value: unknown): value is ESTree.Node {
+ return (
+ typeof value === "object" &&
+ value !== null &&
+ "type" in value &&
+ typeof value.type === "string"
+ );
+}
+
+function collectInferTypeParameterNames(
+ node: ESTree.Node,
+ visitorKeys: VisitorKeys,
+ names: Set,
+): void {
+ if (node.type === "TSInferType") names.add(node.typeParameter.name.name);
+ const record = node as unknown as Readonly>;
+ for (const key of visitorKeys[node.type] ?? []) {
+ const value = record[key];
+ if (isNode(value)) {
+ collectInferTypeParameterNames(value, visitorKeys, names);
+ continue;
+ }
+ if (!Array.isArray(value)) continue;
+ for (const child of value) {
+ if (isNode(child)) collectInferTypeParameterNames(child, visitorKeys, names);
+ }
+ }
+}
+
+/** Collect type binders that are in scope at a node and can shadow module aliases. */
+export function lexicalTypeParameterNames(
+ node: ESTree.Node,
+ visitorKeys: VisitorKeys,
+): ReadonlySet {
+ const names = new Set();
+ let descendant: ESTree.Node = node;
+ let current: ESTree.Node | null = node;
+ while (current !== null && current.type !== "Program") {
+ if ("typeParameters" in current) {
+ for (const parameter of current.typeParameters?.params ?? []) {
+ names.add(parameter.name.name);
+ }
+ }
+ if (
+ current.type === "TSMappedType" &&
+ (descendant === current.nameType || descendant === current.typeAnnotation)
+ ) {
+ names.add(current.key.name);
+ }
+ if (current.type === "TSConditionalType" && descendant === current.trueType) {
+ collectInferTypeParameterNames(current.extendsType, visitorKeys, names);
+ }
+ descendant = current;
+ current = current.parent;
+ }
+ return names;
+}
diff --git a/tools/oxlint/anti-slop/shared/scope.ts b/tools/oxlint/anti-slop/shared/scope.ts
new file mode 100644
index 0000000..602414a
--- /dev/null
+++ b/tools/oxlint/anti-slop/shared/scope.ts
@@ -0,0 +1,15 @@
+import type { ESTree, Scope, SourceCode, Variable } from "@oxlint/plugins";
+
+/** Resolve an identifier to its binding by walking lexical scopes upward. */
+export function resolveVariable(
+ sourceCode: SourceCode,
+ identifier: ESTree.IdentifierReference,
+): Variable | null {
+ let scope: Scope | null = sourceCode.getScope(identifier);
+ while (scope !== null) {
+ const variable = scope.set.get(identifier.name);
+ if (variable !== undefined) return variable;
+ scope = scope.upper;
+ }
+ return null;
+}
diff --git a/tools/oxlint/anti-slop/shared/type-alias-resolution.ts b/tools/oxlint/anti-slop/shared/type-alias-resolution.ts
new file mode 100644
index 0000000..4744222
--- /dev/null
+++ b/tools/oxlint/anti-slop/shared/type-alias-resolution.ts
@@ -0,0 +1,250 @@
+import type { ESTree } from "@oxlint/plugins";
+
+import { lexicalTypeParameterNames } from "./lexical-type-parameters.ts";
+
+type VisitorKeys = Readonly>;
+type TypeScope = ESTree.Node;
+
+type TypeBinding = {
+ readonly alias: ESTree.TSTypeAliasDeclaration | null;
+ readonly name: string;
+ readonly scope: TypeScope;
+};
+
+type Substitution = {
+ readonly substitutions: Substitutions;
+ readonly type: ESTree.TSType;
+};
+
+type Substitutions = ReadonlyMap;
+
+export type TypeAliasEnvironment = {
+ readonly aliases: readonly ESTree.TSTypeAliasDeclaration[];
+ readonly bindingsByName: ReadonlyMap;
+ readonly visitorKeys: VisitorKeys;
+};
+
+export type ResolvedTypeMatcher = (
+ type: ESTree.TSType,
+ matches: (child: ESTree.TSType) => boolean,
+) => boolean;
+
+const environmentsByProgram = new WeakMap();
+
+function isNode(value: unknown): value is ESTree.Node {
+ return (
+ typeof value === "object" &&
+ value !== null &&
+ "type" in value &&
+ typeof value.type === "string"
+ );
+}
+
+function enclosingTypeScope(node: ESTree.Node): TypeScope {
+ let current: ESTree.Node | null = node.parent;
+ while (current !== null) {
+ if (
+ current.type === "Program" ||
+ current.type === "BlockStatement" ||
+ current.type === "TSModuleBlock" ||
+ current.type === "StaticBlock" ||
+ current.type === "SwitchStatement"
+ ) {
+ return current;
+ }
+ current = current.parent;
+ }
+ return node;
+}
+
+function declaredTypeBinding(node: ESTree.Node): {
+ readonly alias: ESTree.TSTypeAliasDeclaration | null;
+ readonly name: string;
+} | null {
+ if (node.type === "TSTypeAliasDeclaration") {
+ return { alias: node, name: node.id.name };
+ }
+ if (
+ node.type === "TSInterfaceDeclaration" ||
+ node.type === "TSEnumDeclaration" ||
+ node.type === "ClassDeclaration" ||
+ node.type === "ClassExpression"
+ ) {
+ return node.id === null ? null : { alias: null, name: node.id.name };
+ }
+ if (
+ node.type === "ImportSpecifier" ||
+ node.type === "ImportDefaultSpecifier" ||
+ node.type === "ImportNamespaceSpecifier"
+ ) {
+ return { alias: null, name: node.local.name };
+ }
+ return null;
+}
+
+function collectTypeBindings(
+ node: ESTree.Node,
+ visitorKeys: VisitorKeys,
+ bindingsByName: Map,
+ aliases: ESTree.TSTypeAliasDeclaration[],
+): void {
+ const declared = declaredTypeBinding(node);
+ if (declared !== null) {
+ const bindings = bindingsByName.get(declared.name) ?? [];
+ bindings.push({ ...declared, scope: enclosingTypeScope(node) });
+ bindingsByName.set(declared.name, bindings);
+ if (declared.alias !== null) aliases.push(declared.alias);
+ }
+
+ // SAFETY: Oxlint's visitor keys identify only ESTree child-node properties.
+ const fields = node as unknown as Readonly>;
+ for (const key of visitorKeys[node.type] ?? []) {
+ const value = fields[key];
+ if (isNode(value)) {
+ collectTypeBindings(value, visitorKeys, bindingsByName, aliases);
+ continue;
+ }
+ if (!Array.isArray(value)) continue;
+ for (const child of value) {
+ if (isNode(child)) {
+ collectTypeBindings(child, visitorKeys, bindingsByName, aliases);
+ }
+ }
+ }
+}
+
+/** Collect every lexical type alias and competing type binding in a program. */
+export function createTypeAliasEnvironment(
+ program: ESTree.Program,
+ visitorKeys: VisitorKeys,
+): TypeAliasEnvironment {
+ const cached = environmentsByProgram.get(program);
+ if (cached !== undefined) return cached;
+ const bindingsByName = new Map();
+ const aliases: ESTree.TSTypeAliasDeclaration[] = [];
+ collectTypeBindings(program, visitorKeys, bindingsByName, aliases);
+ const environment = { aliases, bindingsByName, visitorKeys };
+ environmentsByProgram.set(program, environment);
+ return environment;
+}
+
+function ancestorDistance(ancestor: ESTree.Node, node: ESTree.Node): number | null {
+ let current: ESTree.Node | null = node;
+ let distance = 0;
+ while (current !== null) {
+ if (current === ancestor) return distance;
+ current = current.parent;
+ distance += 1;
+ }
+ return null;
+}
+
+function nearestTypeBindings(
+ name: string,
+ use: ESTree.Node,
+ environment: TypeAliasEnvironment,
+): readonly TypeBinding[] {
+ const candidates = environment.bindingsByName.get(name) ?? [];
+ let nearestDistance = Number.POSITIVE_INFINITY;
+ let nearest: TypeBinding[] = [];
+ for (const candidate of candidates) {
+ const distance = ancestorDistance(candidate.scope, use);
+ if (distance === null || distance > nearestDistance) continue;
+ if (distance === nearestDistance) {
+ nearest.push(candidate);
+ continue;
+ }
+ nearestDistance = distance;
+ nearest = [candidate];
+ }
+ return nearest;
+}
+
+/** Resolve the nearest visible alias with this name, respecting lexical shadowing. */
+export function visibleTypeAlias(
+ name: string,
+ use: ESTree.Node,
+ environment: TypeAliasEnvironment,
+): ESTree.TSTypeAliasDeclaration | null {
+ if (lexicalTypeParameterNames(use, environment.visitorKeys).has(name)) return null;
+ const bindings = nearestTypeBindings(name, use, environment);
+ return bindings.length === 1 ? (bindings[0]?.alias ?? null) : null;
+}
+
+/** Return whether a local declaration shadows a built-in type at this use. */
+export function hasVisibleTypeBinding(
+ name: string,
+ use: ESTree.Node,
+ environment: TypeAliasEnvironment,
+): boolean {
+ return (
+ lexicalTypeParameterNames(use, environment.visitorKeys).has(name) ||
+ nearestTypeBindings(name, use, environment).length > 0
+ );
+}
+
+function typeReferenceName(type: ESTree.TSTypeReference): string | null {
+ return type.typeName.type === "Identifier" ? type.typeName.name : null;
+}
+
+function aliasSubstitutions(
+ alias: ESTree.TSTypeAliasDeclaration,
+ reference: ESTree.TSTypeReference,
+ base: Substitutions,
+): Substitutions | null {
+ const parameters = alias.typeParameters?.params ?? [];
+ const arguments_ = reference.typeArguments?.params ?? [];
+ const next = new Map(base);
+ for (const [index, parameter] of parameters.entries()) {
+ const explicitArgument = arguments_[index];
+ const argument = explicitArgument ?? parameter.default;
+ if (argument === null || argument === undefined) return null;
+ const argumentSubstitutions = explicitArgument === undefined ? next : base;
+ next.set(parameter.name.name, {
+ type: argument,
+ substitutions: new Map(argumentSubstitutions),
+ });
+ }
+ return next;
+}
+
+/** Match a type after resolving visible aliases and substituting their type parameters. */
+export function resolvedTypeMatches(
+ type: ESTree.TSType,
+ environment: TypeAliasEnvironment,
+ matcher: ResolvedTypeMatcher,
+): boolean {
+ const evaluate = (
+ current: ESTree.TSType,
+ substitutions: Substitutions,
+ resolvingAliases: ReadonlySet,
+ ): boolean => {
+ if (current.type === "TSTypeReference") {
+ const name = typeReferenceName(current);
+ if (name !== null) {
+ const substitution = substitutions.get(name);
+ if (substitution !== undefined && !current.typeArguments?.params.length) {
+ return evaluate(
+ substitution.type,
+ substitution.substitutions,
+ resolvingAliases,
+ );
+ }
+ const alias = visibleTypeAlias(name, current, environment);
+ if (alias !== null && !resolvingAliases.has(alias)) {
+ const nextSubstitutions = aliasSubstitutions(alias, current, substitutions);
+ if (nextSubstitutions !== null) {
+ const nextResolving = new Set(resolvingAliases);
+ nextResolving.add(alias);
+ return evaluate(alias.typeAnnotation, nextSubstitutions, nextResolving);
+ }
+ }
+ }
+ }
+ return matcher(current, (child) =>
+ evaluate(child, substitutions, resolvingAliases),
+ );
+ };
+
+ return evaluate(type, new Map(), new Set());
+}