From fabd160250c37fdb8df0a6d3f94c0d889932b7f0 Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 8 Sep 2026 15:39:13 +0200 Subject: [PATCH 01/10] fix(ci): bound Docker build cache with BuildKit cache mounts - Move the pnpm store, apk and .next caches into --mount=type=cache so dependencies are shared across builds instead of duplicated in fresh image layers (was the source of unbounded disk growth). - Replace the deprecated --keep-storage prune flag in ci-deploy.sh with the working --max-used-space=4g (buildx v0.37 renamed the flag). The deprecated flag silently did nothing, so the BuildKit cache kept growing unbounded (was 15.86GB); it is now capped at 4GB after every deploy. --- Dockerfile | 27 ++++++++++++++++++------ scripts/ci-deploy.sh | 2 +- src/lib/deploy-workflow-contract.test.ts | 2 +- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index a573737e..2a9c0d45 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,22 +4,37 @@ FROM node:26.8.1-alpine AS migrations WORKDIR /app ENV NEXT_TELEMETRY_DISABLED=1 # Keep the bootstrap aligned with package.json packageManager. -RUN apk add --no-cache git \ +# The apk cache is persisted in a BuildKit cache mount so git is not +# re-downloaded on every build. +RUN --mount=type=cache,target=/var/cache/apk \ + apk add --no-cache git \ && npm install -g pnpm@11.25.0 +# The pnpm store is kept in a BuildKit cache mount that persists across builds +# on the builder. This is what stops disk usage from growing unbounded: the +# downloaded dependency store is shared and reused instead of being copied into +# a fresh image layer on every build. Unlike an image layer it is also prunable +# independently, so a hard cap (see ci-deploy.sh) keeps it bounded. +ENV PNPM_HOME=/pnpm PNPM_STORE=/pnpm/store # pnpm-workspace.yaml + .npmrc must be present too: the lockfile records the # overrides from pnpm-workspace.yaml, and --frozen-lockfile rejects a build # where the workspace config is absent (ERR_PNPM_LOCKFILE_CONFIG_MISMATCH). COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* .npmrc* ./ -# pnpm fetch: download all deps into $PNPM_STORE first, so only the lockfile -# change (not source changes) invalidates the network-heavy download layer. -RUN pnpm fetch --ignore-scripts -RUN pnpm install --frozen-lockfile --ignore-scripts --offline +# pnpm fetch: download all deps into the shared cache-mounted store. +RUN --mount=type=cache,target=/pnpm \ + pnpm fetch --ignore-scripts +# Install offline from the cache-mounted store; the store itself stays in the +# build cache between builds. +RUN --mount=type=cache,target=/pnpm \ + pnpm install --frozen-lockfile --ignore-scripts --offline COPY . . FROM migrations AS builder ARG NEXT_DEPLOYMENT_ID="unknown" ENV NEXT_DEPLOYMENT_ID="$NEXT_DEPLOYMENT_ID" # Fixture values exist only for this build command; production secrets are runtime-only. -RUN DATABASE_URL="mysql://build:build@127.0.0.1:9/build" \ +# Cache Next.js build output and webpack caches so rebuilds only redo the +# changed parts. +RUN --mount=type=cache,target=/app/.next/cache \ + DATABASE_URL="mysql://build:build@127.0.0.1:9/build" \ HOTEL_NAME="Build fixture" APP_URL="http://localhost:3002" \ AUTH_SECRET="build-fixture-not-for-runtime-use-000000000000" \ pnpm run build diff --git a/scripts/ci-deploy.sh b/scripts/ci-deploy.sh index 01b2d5fe..0f926123 100644 --- a/scripts/ci-deploy.sh +++ b/scripts/ci-deploy.sh @@ -176,5 +176,5 @@ while IFS= read -r tag; do if [ -n "$tagged_image" ] && [ "$tagged_image" != "$previous_image" ]; then docker image rm "$tag" || true; fi fi done < <(docker image ls --format '{{.Repository}}:{{.Tag}}' epicnext-cms) -docker builder prune -af --filter "until=72h" --keep-storage=2g || true +docker builder prune -af --filter "until=72h" --max-used-space=4g || true docker image prune -f --filter "until=168h" || true diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts index 343a733f..b459136b 100644 --- a/src/lib/deploy-workflow-contract.test.ts +++ b/src/lib/deploy-workflow-contract.test.ts @@ -27,7 +27,7 @@ it("preserves production runtime configuration and recent cache", () => { expect(deploy).toContain("/app/storage"); expect(deploy).not.toContain("--env-file"); expect(deploy).toContain( - 'docker builder prune -af --filter "until=72h" --keep-storage=2g', + 'docker builder prune -af --filter "until=72h" --max-used-space=4g', ); expect(deploy).not.toContain("docker volume prune"); }); From 25f4d7420927c3a8bc1fcc850558aa4e3cd58c0c Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 8 Sep 2026 16:02:12 +0200 Subject: [PATCH 02/10] feat(ci): switch Cloudflare bypass from FlareSolverr to Byparr --- .env.example | 4 +- README.md | 8 +-- docker-compose.yml | 17 +++-- scripts/health-check.sh | 14 ++-- src/lib/services/byparr.ts | 90 ++++++++++++++++++++++++ src/lib/services/clone-import.test.ts | 26 +++---- src/lib/services/clone-import.ts | 18 ++--- src/lib/services/flare-solver.ts | 82 --------------------- src/lib/services/import/core/download.ts | 8 +-- update-Nitrov3.sh | 2 +- 10 files changed, 138 insertions(+), 131 deletions(-) create mode 100644 src/lib/services/byparr.ts delete mode 100644 src/lib/services/flare-solver.ts diff --git a/.env.example b/.env.example index 6acc2981..331e866b 100644 --- a/.env.example +++ b/.env.example @@ -76,8 +76,8 @@ PAYPAL_API=https://api-m.sandbox.paypal.com # --- LOGGING --- LOG_LEVEL=error -# --- FLARESOLVERR (Cloudflare bypass for clone sources) --- -FLARESOLVERR_URL=http://localhost:8191 +# --- BYPARR (Cloudflare bypass for clone sources) --- +BYPARR_URL=http://localhost:8191 # Catalog Studio export: dedicated clean clone on Beta-3 with Git push credentials. CATALOG_GIT_CHECKOUT= diff --git a/README.md b/README.md index 05c86bf8..c56352db 100644 --- a/README.md +++ b/README.md @@ -808,16 +808,16 @@ pnpm jobs:worker # or: npm run jobs:worker / yarn jobs:worker Optional OpenAI-powered moderation for comments and guestbook posts. Set `OPENAI_API_KEY`. -### FlareSolverr (Cloudflare Bypass) +### Byparr (Cloudflare Bypass) -Some clone sources are protected by Cloudflare. FlareSolverr solves these challenges automatically. +Some clone sources are protected by Cloudflare. Byparr (a FlareSolverr successor) solves these challenges automatically. ```bash -docker compose up -d flaresolverr +docker compose up -d byparr ``` ```dotenv -FLARESOLVERR_URL=http://localhost:8191 +BYPARR_URL=http://localhost:8191 ``` ### Furniture Import with Auto-Translation diff --git a/docker-compose.yml b/docker-compose.yml index 17f71ad4..f628f4e7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,19 +58,18 @@ services: start_period: 40s mem_limit: 2g - # ── FlareSolverr (Cloudflare bypass for clone sources) ── + # ── Byparr (Cloudflare bypass for clone sources) ── # Solves Cloudflare/TLS-fingerprint blocks via a headless Chrome browser. - # The CMS calls this on http://localhost:8191 for sources that block Node's - # TLS fingerprint (e.g. Leet.city). Used by src/lib/services/flare-solver.ts. - flaresolverr: - image: ghcr.io/flaresolverr/flaresolverr:latest - container_name: flaresolverr + # Drop-in successor to FlareSolverr. The CMS calls this on + # http://localhost:8191 for sources that block Node's TLS fingerprint + # (e.g. Leet.city). Used by src/lib/services/byparr.ts. + byparr: + image: ghcr.io/thephaseless/byparr:latest + container_name: byparr network_mode: host restart: unless-stopped environment: - - LOG_LEVEL=info - - BROWSER_TIMEOUT=60000 - - BROWSER_WORKERS=1 + - LOG_LEVEL=INFO mem_limit: 2g healthcheck: test: ["CMD", "curl", "-sf", "http://localhost:8191/health"] diff --git a/scripts/health-check.sh b/scripts/health-check.sh index b8bcb2bc..5b3c4535 100755 --- a/scripts/health-check.sh +++ b/scripts/health-check.sh @@ -1,20 +1,20 @@ #!/usr/bin/env bash set -euo pipefail -URL="${FLARESOLVERR_URL:-http://localhost:8191}" -MAX_RETRIES="${FLARESOLVERR_MAX_RETRIES:-30}" -RETRY_INTERVAL="${FLARESOLVERR_RETRY_INTERVAL:-2}" +URL="${BYPARR_URL:-${FLARESOLVERR_URL:-http://localhost:8191}}" +MAX_RETRIES="${BYPARR_MAX_RETRIES:-30}" +RETRY_INTERVAL="${BYPARR_RETRY_INTERVAL:-2}" -echo "Waiting for FlareSolverr at ${URL}..." +echo "Waiting for Byparr at ${URL}..." for i in $(seq 1 "$MAX_RETRIES"); do if curl -sf "${URL}/health" >/dev/null 2>&1; then - echo "FlareSolverr is healthy." + echo "Byparr is healthy." exit 0 fi - echo "Attempt ${i}/${MAX_RETRIES} - FlareSolverr not ready, retrying in ${RETRY_INTERVAL}s..." + echo "Attempt ${i}/${MAX_RETRIES} - Byparr not ready, retrying in ${RETRY_INTERVAL}s..." sleep "$RETRY_INTERVAL" done -echo "ERROR: FlareSolverr did not become healthy after ${MAX_RETRIES} attempts." +echo "ERROR: Byparr did not become healthy after ${MAX_RETRIES} attempts." exit 1 \ No newline at end of file diff --git a/src/lib/services/byparr.ts b/src/lib/services/byparr.ts new file mode 100644 index 00000000..660c1910 --- /dev/null +++ b/src/lib/services/byparr.ts @@ -0,0 +1,90 @@ +const BYPARR_URL = + process.env.BYPARR_URL ?? + process.env.FLARESOLVERR_URL ?? // legacy env fallback + "http://localhost:8191"; + +const BYPARR_API = `${BYPARR_URL}/v1`; + +type ByparrCookie = { + name: string; + value: string; + domain?: string; + path?: string; +}; + +type ByparrSolution = { + response: string; + status: number; + cookies: ByparrCookie[]; +}; + +type ByparrResult = { + status?: "ok" | "error"; + message?: string; + solution?: ByparrSolution; + error?: string; +}; + +async function byparrRequest( + url: string, + timeout = 30000, +): Promise { + const res = await fetch(BYPARR_API, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + cmd: "request.get", + url, + maxTimeout: timeout, + }), + signal: AbortSignal.timeout(timeout + 5000), + }); + + if (!res.ok) { + throw new Error(`Byparr request failed: ${res.status} ${res.statusText}`); + } + + const data = (await res.json()) as ByparrResult; + + if (data.error) { + throw new Error(`Byparr error: ${data.error}`); + } + + // Byparr returns HTTP 200 with `status: "error"` for invalid requests + // (FlareSolverr used a top-level `error` field instead). + if (data.status && data.status !== "ok") { + throw new Error(`Byparr error: ${data.message ?? "request failed"}`); + } + + if (!data.solution) { + throw new Error("Byparr: no solution in response"); + } + + return data.solution; +} + +export async function fetchWithByparr( + url: string, + timeout = 30000, +): Promise { + const solution = await byparrRequest(url, timeout); + return solution.response; +} + +/** + * Solve a Cloudflare challenge for the given URL and return the clearance + * cookies as a `-b "name=value; ..."` argument string suitable for curl. + * Returns null if Byparr is unavailable or no cookies were set. + */ +export async function getByparrCookies( + url: string, + timeout = 30000, +): Promise { + try { + const solution = await byparrRequest(url, timeout); + if (!solution.cookies?.length) return null; + return solution.cookies.map((c) => `${c.name}=${c.value}`).join("; "); + } catch { + return null; + } +} diff --git a/src/lib/services/clone-import.test.ts b/src/lib/services/clone-import.test.ts index b059e177..a2f52623 100644 --- a/src/lib/services/clone-import.test.ts +++ b/src/lib/services/clone-import.test.ts @@ -12,7 +12,7 @@ const { selectLimit, fsUnlink, fsReadFile, - fetchWithFlareSolver, + fetchWithByparr, curlFetchText, curlDownload, } = vi.hoisted(() => ({ @@ -23,7 +23,7 @@ const { selectLimit: vi.fn(async () => []), fsUnlink: vi.fn(async () => {}), fsReadFile: vi.fn(async () => Buffer.from("NITRO")), - fetchWithFlareSolver: vi.fn(async () => ""), + fetchWithByparr: vi.fn(async () => ""), curlFetchText: vi.fn(async () => ""), curlDownload: vi.fn(async () => ({ ok: true, size: 200 })), })); @@ -33,7 +33,7 @@ vi.mock("@/lib/services/import/core/curl-fetch", () => ({ curlDownload, curlFetchText, })); -vi.mock("@/lib/services/flare-solver", () => ({ fetchWithFlareSolver })); +vi.mock("@/lib/services/byparr", () => ({ fetchWithByparr })); vi.mock("@/lib/services/furni-data", () => ({ appendFurniEntry })); vi.mock("@/lib/services/furni-import", () => ({ ensureDirectories: vi.fn(async () => {}), @@ -217,8 +217,8 @@ describe("fetchSourceFurnidata", () => { const HTML_WRAPPED = `
${JSON_BODY}
`; beforeEach(() => { - fetchWithFlareSolver.mockReset(); - fetchWithFlareSolver.mockResolvedValue(""); + fetchWithByparr.mockReset(); + fetchWithByparr.mockResolvedValue(""); vi.unstubAllGlobals(); }); @@ -235,10 +235,10 @@ describe("fetchSourceFurnidata", () => { ); const list = await fetchSourceFurnidata("https://a.test/fd.json", 1); expect(list.map((e) => e.classname)).toEqual(["bc_sofa", "wall_x"]); - expect(fetchWithFlareSolver).not.toHaveBeenCalled(); + expect(fetchWithByparr).not.toHaveBeenCalled(); }); - it("extracts JSON from an HTML-wrapped furnidata (e.g. Leet via FlareSolverr)", async () => { + it("extracts JSON from an HTML-wrapped furnidata (e.g. Leet via Byparr)", async () => { vi.stubGlobal( "fetch", vi.fn( @@ -249,13 +249,13 @@ describe("fetchSourceFurnidata", () => { }), ), ); - fetchWithFlareSolver.mockResolvedValue(HTML_WRAPPED); + fetchWithByparr.mockResolvedValue(HTML_WRAPPED); const list = await fetchSourceFurnidata("https://b.test/fd.json", 1); expect(list.map((e) => e.classname)).toEqual(["bc_sofa", "wall_x"]); - expect(fetchWithFlareSolver).toHaveBeenCalledWith("https://b.test/fd.json"); + expect(fetchWithByparr).toHaveBeenCalledWith("https://b.test/fd.json"); }); - it("falls back to FlareSolverr on HTML body even when status is 200", async () => { + it("falls back to Byparr on HTML body even when status is 200", async () => { vi.stubGlobal( "fetch", vi.fn( @@ -266,7 +266,7 @@ describe("fetchSourceFurnidata", () => { }), ), ); - fetchWithFlareSolver.mockResolvedValue(HTML_WRAPPED); + fetchWithByparr.mockResolvedValue(HTML_WRAPPED); const list = await fetchSourceFurnidata("https://c.test/fd.json", 1); expect(list.map((e) => e.classname)).toEqual(["bc_sofa", "wall_x"]); }); @@ -284,7 +284,7 @@ describe("fetchSourceFurnidata", () => { ); const list = await fetchSourceFurnidata("https://g.test/fd.json", 1); expect(list.map((e) => e.classname)).toEqual(["bc_sofa", "wall_x"]); - expect(fetchWithFlareSolver).not.toHaveBeenCalled(); + expect(fetchWithByparr).not.toHaveBeenCalled(); }); it("throws when HTML has no embedded JSON payload", async () => { @@ -298,7 +298,7 @@ describe("fetchSourceFurnidata", () => { }), ), ); - fetchWithFlareSolver.mockResolvedValue("still a challenge"); + fetchWithByparr.mockResolvedValue("still a challenge"); await expect( fetchSourceFurnidata("https://d.test/fd.json", 1), ).rejects.toThrow(/Cloudflare challenge/); diff --git a/src/lib/services/clone-import.ts b/src/lib/services/clone-import.ts index a133fae3..4f8ba421 100644 --- a/src/lib/services/clone-import.ts +++ b/src/lib/services/clone-import.ts @@ -5,9 +5,9 @@ import { autoDetectInteraction } from "@/lib/furni/auto-interaction"; import { normalizeClassname } from "@/lib/furni/classname"; import { HABBO_GAMEDATA_HOTEL_SETTING_KEY } from "@/lib/habbo-gamedata-hotel"; import { logger } from "@/lib/logger"; +import { fetchWithByparr } from "@/lib/services/byparr"; import { extractFurniIconPng } from "@/lib/services/clone-icon"; import type { CloneSource } from "@/lib/services/clone-sources"; -import { fetchWithFlareSolver } from "@/lib/services/flare-solver"; import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs"; import { appendFurniEntry } from "@/lib/services/furni-data"; import { @@ -101,7 +101,7 @@ const TTL = 5 * 60 * 1000; /** * Some sources serve their furnidata wrapped in an HTML document (e.g. Leet's * JSON is embedded in a `
` block inside the page, often surfaced by the
- * FlareSolverr fallback). Extract the JSON payload before giving up.
+ * Byparr fallback). Extract the JSON payload before giving up.
  */
 function extractJsonFromHtml(body: string): string | null {
 	const pre = body.match(/]*>([\s\S]*?)<\/pre>/);
@@ -117,20 +117,20 @@ function extractJsonFromHtml(body: string): string | null {
 }
 
 /**
- * Fetch a source's furnidata via FlareSolverr, falling back to a curl
+ * Fetch a source's furnidata via Byparr, falling back to a curl
  * subprocess. Some CDNs (e.g. Leet.city) block Node's TLS fingerprint while
  * the same request succeeds from curl — so curl is a useful second fallback
- * when FlareSolverr is unavailable.
+ * when Byparr is unavailable.
  */
 async function fetchWithFallback(url: string): Promise {
 	try {
-		return await fetchWithFlareSolver(url);
+		return await fetchWithByparr(url);
 	} catch {
 		try {
 			return await curlFetchText(url);
 		} catch (err) {
 			throw new Error(
-				`FlareSolverr + curl fallback failed for ${url}: ${(err as Error).message}`,
+				`Byparr + curl fallback failed for ${url}: ${(err as Error).message}`,
 			);
 		}
 	}
@@ -150,8 +150,8 @@ export async function fetchSourceFurnidata(
 		});
 		if (res.ok) {
 			// Some sources (e.g. GitHub raw) serve JSON with a text/plain
-			// content-type — only route to FlareSolverr when the body is
-			// actually HTML, otherwise parse the JSON directly.
+			// content-type — only route to Byparr when the body is actually
+			// HTML, otherwise parse the JSON directly.
 			const text = await res.text();
 			if (
 				text.trimStart().startsWith("{") ||
@@ -171,7 +171,7 @@ export async function fetchSourceFurnidata(
 		const extracted = extractJsonFromHtml(body);
 		if (!extracted) {
 			throw new Error(
-				`Source ${url} is behind a Cloudflare challenge that could not be bypassed. Start FlareSolverr and check its logs.`,
+				`Source ${url} is behind a Cloudflare challenge that could not be bypassed. Start Byparr and check its logs.`,
 			);
 		}
 		body = extracted;
diff --git a/src/lib/services/flare-solver.ts b/src/lib/services/flare-solver.ts
deleted file mode 100644
index 7f011879..00000000
--- a/src/lib/services/flare-solver.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-const FLARESOLVERR_URL =
-	process.env.FLARESOLVERR_URL ?? "http://localhost:8191";
-
-const FLARESOLVERR_API = `${FLARESOLVERR_URL}/v1`;
-
-type FlareCookie = {
-	name: string;
-	value: string;
-	domain: string;
-	path: string;
-};
-
-type FlareSolution = {
-	response: string;
-	status: number;
-	cookies: FlareCookie[];
-};
-
-type FlareResult = {
-	solution?: FlareSolution;
-	error?: string;
-};
-
-async function flareRequest(
-	url: string,
-	timeout = 30000,
-): Promise {
-	const res = await fetch(FLARESOLVERR_API, {
-		method: "POST",
-		headers: { "Content-Type": "application/json" },
-		body: JSON.stringify({
-			cmd: "request.get",
-			url,
-			maxTimeout: timeout,
-		}),
-		signal: AbortSignal.timeout(timeout + 5000),
-	});
-
-	if (!res.ok) {
-		throw new Error(
-			`FlareSolverr request failed: ${res.status} ${res.statusText}`,
-		);
-	}
-
-	const data = (await res.json()) as FlareResult;
-
-	if (data.error) {
-		throw new Error(`FlareSolverr error: ${data.error}`);
-	}
-
-	if (!data.solution) {
-		throw new Error("FlareSolverr: no solution in response");
-	}
-
-	return data.solution;
-}
-
-export async function fetchWithFlareSolver(
-	url: string,
-	timeout = 30000,
-): Promise {
-	const solution = await flareRequest(url, timeout);
-	return solution.response;
-}
-
-/**
- * Solve a Cloudflare challenge for the given URL and return the clearance
- * cookies as a `-b "name=value; ..."` argument string suitable for curl.
- * Returns null if FlareSolverr is unavailable or no cookies were set.
- */
-export async function getFlareSolverrCookies(
-	url: string,
-	timeout = 30000,
-): Promise {
-	try {
-		const solution = await flareRequest(url, timeout);
-		if (!solution.cookies?.length) return null;
-		return solution.cookies.map((c) => `${c.name}=${c.value}`).join("; ");
-	} catch {
-		return null;
-	}
-}
diff --git a/src/lib/services/import/core/download.ts b/src/lib/services/import/core/download.ts
index 8286711d..b8110714 100644
--- a/src/lib/services/import/core/download.ts
+++ b/src/lib/services/import/core/download.ts
@@ -1,5 +1,5 @@
 import { promises as fs } from "node:fs";
-import { getFlareSolverrCookies } from "@/lib/services/flare-solver";
+import { getByparrCookies } from "@/lib/services/byparr";
 import { parseNitroBundle } from "../../swf/nitro-builder";
 import { browserHeaders } from "./browser-headers";
 import { curlDownload } from "./curl-fetch";
@@ -62,7 +62,7 @@ export async function downloadFile(
 	const curlFallback = async (): Promise => {
 		let curlRes = await curlDownload(url, destPath);
 		if (!curlRes.ok) {
-			const cookieHeader = await getFlareSolverrCookies(url);
+			const cookieHeader = await getByparrCookies(url);
 			curlRes = await curlDownload(
 				url,
 				destPath,
@@ -108,7 +108,7 @@ export async function downloadFile(
 					res.status === 404 || res.status === 410 || res.status === 403;
 				if (deterministic || attempt === maxRetries) {
 					// HTTP 403 is often a TLS-fingerprint / Cloudflare challenge
-					// block (e.g. Leet.city) — retry via curl, with FlareSolverr
+					// block (e.g. Leet.city) — retry via curl, with Byparr
 					// clearance cookies if the plain curl is also challenged.
 					if (res.status === 403 && (await curlFallback())) {
 						const stat = await fs.stat(destPath);
@@ -148,7 +148,7 @@ export async function downloadFile(
 			return { ok: true, size: buffer.length };
 		} catch (err) {
 			if (attempt === maxRetries || stage === "write") {
-				// TLS fingerprint aborts land here too — try curl/FlareSolverr
+				// TLS fingerprint aborts land here too — try curl/Byparr
 				// before reporting the network failure.
 				if (stage === "download" && (await curlFallback())) {
 					const stat = await fs.stat(destPath);
diff --git a/update-Nitrov3.sh b/update-Nitrov3.sh
index cb773d3e..12d4c125 100755
--- a/update-Nitrov3.sh
+++ b/update-Nitrov3.sh
@@ -1282,7 +1282,7 @@ with open(out, "w", encoding="utf-8") as f:
 
 # Sanity-check a .env file for the failure modes that actually break a deploy:
 # duplicate keys, and a value concatenated onto another key (missing newline),
-# e.g. FLARESOLVERR_URL=http://localhost:8191AUTH_TRUST_HOST="..."
+# e.g. BYPARR_URL=http://localhost:8191AUTH_TRUST_HOST="..."
 validate_env_file() {
     local base_dir="${1:-$SCRIPT_DIR}"
     local en="$base_dir/.env"

From bbb7d660671a084b70c9a25aaa82615fea1f62b0 Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 16:42:10 +0200
Subject: [PATCH 03/10] chore: remove Playwright end-to-end testing setup

Remove Playwright config, e2e spec, dependencies, and related references from package.json, tsconfig.json, and .gitignore.
---
 .gitignore           |   5 -
 e2e/smoke.spec.ts    |  13 --
 package.json         |   2 -
 playwright.config.ts |  20 --
 pnpm-lock.yaml       | 466 +++++++++++++++++++------------------------
 tsconfig.json        |   2 +-
 6 files changed, 205 insertions(+), 303 deletions(-)
 delete mode 100644 e2e/smoke.spec.ts
 delete mode 100644 playwright.config.ts

diff --git a/.gitignore b/.gitignore
index 6f60ed50..469d2d3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -33,11 +33,6 @@ public/tmp/
 
 # Test coverage reports
 coverage/
-
-# Playwright test artifacts
-test-results/
-playwright-report/
-blob-report/
 .aider*
 
 /public/vendor/tinymce/
diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts
deleted file mode 100644
index 60b9f1a8..00000000
--- a/e2e/smoke.spec.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { expect, test } from "@playwright/test";
-
-test("health endpoint is reachable", async ({ request }) => {
-	const res = await request.get("/api/health");
-	expect(res.ok()).toBeTruthy();
-	const body = await res.json();
-	expect(body).toHaveProperty("status");
-});
-
-test("homepage renders", async ({ page }) => {
-	await page.goto("/");
-	await expect(page).toHaveTitle(/.+/);
-});
diff --git a/package.json b/package.json
index 5945e733..3166b4ce 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
 		"diag:permissions": "tsx scripts/diagnose-permission-page.ts",
 		"jobs:worker": "node --conditions=react-server --import tsx scripts/jobs-worker.ts",
 		"test": "vitest run",
-		"test:e2e": "playwright test",
 		"typecheck": "tsc --noEmit",
 		"db:generate": "drizzle-kit generate",
 		"db:introspect": "drizzle-kit introspect",
@@ -86,7 +85,6 @@
 	"devDependencies": {
 		"@babel/parser": "7.29.8",
 		"@biomejs/biome": "2.5.12",
-		"@playwright/test": "^1.62.1",
 		"@tailwindcss/forms": "0.5.11",
 		"@tailwindcss/postcss": "4.3.3",
 		"@tailwindcss/typography": "0.5.20",
diff --git a/playwright.config.ts b/playwright.config.ts
deleted file mode 100644
index c14dd960..00000000
--- a/playwright.config.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { defineConfig, devices } from "@playwright/test";
-
-const baseURL = process.env.PLAYWRIGHT_BASE_URL ?? "http://127.0.0.1:3000";
-
-export default defineConfig({
-	testDir: "./e2e",
-	fullyParallel: true,
-	retries: process.env.CI ? 2 : 0,
-	reporter: process.env.CI ? "github" : "list",
-	use: { baseURL, trace: "on-first-retry" },
-	webServer: process.env.PLAYWRIGHT_BASE_URL
-		? undefined
-		: {
-				command: "pnpm dev --port 3000",
-				url: "http://127.0.0.1:3000/api/health",
-				reuseExistingServer: !process.env.CI,
-				timeout: 120_000,
-			},
-	projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
-});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index bed4d714..322e4f7c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -30,7 +30,7 @@ importers:
         version: 3.5.17
       '@hookform/resolvers':
         specifier: 5.9.1
-        version: 5.9.1(@standard-schema/spec@1.1.0)(react-hook-form@7.87.0(react@19.2.8))(zod@4.5.4)
+        version: 5.9.1(react-hook-form@7.87.0(react@19.2.8))(zod@4.5.4)
       '@tanstack/react-virtual':
         specifier: 3.14.10
         version: 3.14.10(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
@@ -54,7 +54,7 @@ importers:
         version: 4.12.0
       ioredis:
         specifier: 6.0.0
-        version: 6.0.0(supports-color@7.2.0)
+        version: 6.0.0
       isomorphic-dompurify:
         specifier: ^4.1.0
         version: 4.1.0(@noble/hashes@2.4.0)
@@ -78,19 +78,19 @@ importers:
         version: 13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
       music-metadata:
         specifier: 11.15.0
-        version: 11.15.0(supports-color@7.2.0)
+        version: 11.15.0
       mysql2:
         specifier: 3.24.3
         version: 3.24.3(@types/node@26.4.1)
       next:
         specifier: 16.3.4
-        version: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+        version: 16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
       next-auth:
         specifier: 5.0.0-beta.32
-        version: 5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
+        version: 5.0.0-beta.32(next@16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
       next-intl:
         specifier: 4.14.2
-        version: 4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
+        version: 4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
       otplib:
         specifier: 13.5.0
         version: 13.5.0
@@ -134,9 +134,6 @@ importers:
       '@biomejs/biome':
         specifier: 2.5.12
         version: 2.5.12
-      '@playwright/test':
-        specifier: ^1.62.1
-        version: 1.62.1
       '@tailwindcss/forms':
         specifier: 0.5.11
         version: 0.5.11(tailwindcss@4.3.3)
@@ -191,8 +188,8 @@ importers:
 
 packages:
 
-  '@alloc/quick-lru@5.2.0':
-    resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
+  '@alloc/quick-lru@5.3.0':
+    resolution: {integrity: sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==}
     engines: {node: '>=10'}
 
   '@asamuzakjp/css-color@6.0.7':
@@ -1236,12 +1233,12 @@ packages:
     cpu: [x64]
     os: [win32]
 
-  '@oxc-project/types@0.146.0':
-    resolution: {integrity: sha512-XC0QsnnhVe7sLIWmYmdPw7x5P0h4W8vUU3Nv1ySgWXtvCz8NizoAEpGXA0sOYoJQV2Rl13LgURAHQ5cI5ILCSA==}
-
   '@oxc-project/types@0.147.0':
     resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==}
 
+  '@oxc-project/types@0.148.0':
+    resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==}
+
   '@oxc-resolver/binding-android-arm-eabi@11.24.2':
     resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==}
     cpu: [arm]
@@ -1433,11 +1430,6 @@ packages:
   '@pinojs/redact@0.4.0':
     resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
 
-  '@playwright/test@1.62.1':
-    resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==}
-    engines: {node: '>=20'}
-    hasBin: true
-
   '@radix-ui/primitive@1.1.7':
     resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==}
 
@@ -1600,98 +1592,98 @@ packages:
       '@types/react':
         optional: true
 
-  '@rolldown/binding-android-arm-eabi@1.2.5':
-    resolution: {integrity: sha512-DLe/i+l8ynIBY7XEQ191TeZvCoowIGa18R+dIV30GW7DiOtp74i/xX8hs8GUjW5ARV7VZuie3d6AumSmCwbeRA==}
+  '@rolldown/binding-android-arm-eabi@1.2.7':
+    resolution: {integrity: sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm]
     os: [android]
 
-  '@rolldown/binding-android-arm64@1.2.5':
-    resolution: {integrity: sha512-zXcwKlQApYAOELHd8PwKDFkagYF9Wy4e0RJ+0qnzl9Pjnpj75TEG8ufv40p2J7kCEfwZAsNiuzRIyNNMWT38ig==}
+  '@rolldown/binding-android-arm64@1.2.7':
+    resolution: {integrity: sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [android]
 
-  '@rolldown/binding-darwin-arm64@1.2.5':
-    resolution: {integrity: sha512-dK4QakI42nzWgJT5sm4y4y/O//D4OxM75/cH28RLV+nzIN9AY+YsbuUVrUTjlLjXR6vpyxFbSsbmNuJ6BP9sww==}
+  '@rolldown/binding-darwin-arm64@1.2.7':
+    resolution: {integrity: sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [darwin]
 
-  '@rolldown/binding-darwin-x64@1.2.5':
-    resolution: {integrity: sha512-fqSALaUu1Wjd1nK2uW2kJDWdLCc8lx1IcY+MTY26Aurfdx19anlzhqXOgCFbBFQnlFDTn4TC1/7Nz4Bl2mLP3A==}
+  '@rolldown/binding-darwin-x64@1.2.7':
+    resolution: {integrity: sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [x64]
     os: [darwin]
 
-  '@rolldown/binding-freebsd-x64@1.2.5':
-    resolution: {integrity: sha512-/vCnNxlkxs9tKxNDcyWUePpJ/PgTzxIaVhoM5SmG8UV+GR/IcPam4VYxi7GIMo7PSDuNqlJqvprqii9NqqVCMw==}
+  '@rolldown/binding-freebsd-x64@1.2.7':
+    resolution: {integrity: sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [x64]
     os: [freebsd]
 
-  '@rolldown/binding-linux-arm-gnueabihf@1.2.5':
-    resolution: {integrity: sha512-abk0NLA519LxRCszmbE0jYKuQ9YPocOXTiOXOo6Yr+YAT95VH+PtqYAjOJvGKt3viEd/x4qzabAlwd5bHOOARg==}
+  '@rolldown/binding-linux-arm-gnueabihf@1.2.7':
+    resolution: {integrity: sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm]
     os: [linux]
 
-  '@rolldown/binding-linux-arm64-gnu@1.2.5':
-    resolution: {integrity: sha512-Y7eALiJ8lr0M2HH103Js+g7V34wf6snlpZLAsHI90uLhr3PVlNsbFVAXJC9d/V6BnPyKtpSwI+NcB/RLxsQxuA==}
+  '@rolldown/binding-linux-arm64-gnu@1.2.7':
+    resolution: {integrity: sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
-  '@rolldown/binding-linux-arm64-musl@1.2.5':
-    resolution: {integrity: sha512-xMvZgnbZg4YVnR/AX2b3oOPDTFYJvUVaJg5FedA/LuvexAtXibZQej4cnTkw3rjsJ/ggUROB64TdtETiim+FYA==}
+  '@rolldown/binding-linux-arm64-musl@1.2.7':
+    resolution: {integrity: sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
-  '@rolldown/binding-linux-ppc64-gnu@1.2.5':
-    resolution: {integrity: sha512-GRjeqTUDHTo5GwntsLaAMcBahG3nlpjftXWZLN73HiYQlhwEowvarFgQnRnQZtIp4keXX7quXFbG38uPZBa2EA==}
+  '@rolldown/binding-linux-ppc64-gnu@1.2.7':
+    resolution: {integrity: sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
 
-  '@rolldown/binding-linux-s390x-gnu@1.2.5':
-    resolution: {integrity: sha512-vLNTR45F2Uwc8AufkNXPmB4VliaXs+FvcheEogIzOXzO4l+LzieXF5A/TWxLy5HtqpsRCHUfd0lPVrrdgXdLHQ==}
+  '@rolldown/binding-linux-s390x-gnu@1.2.7':
+    resolution: {integrity: sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
 
-  '@rolldown/binding-linux-x64-gnu@1.2.5':
-    resolution: {integrity: sha512-Mgj59/HTuYeK9Gz2MA+mBWKnHsAgkBSec15ZMb1st3oIfFbX7gCjOae7GydHhzcyQi9Z/7M1QuN9bR3oFqF0jQ==}
+  '@rolldown/binding-linux-x64-gnu@1.2.7':
+    resolution: {integrity: sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
-  '@rolldown/binding-linux-x64-musl@1.2.5':
-    resolution: {integrity: sha512-mY8AP0/ichsbhAxGnLa3d3+MwV0EfgrPND2bplI3Ym8T6R2pJ0N87bvrKVwNXmdy3jnr6eQBecdqx/HMknBmpA==}
+  '@rolldown/binding-linux-x64-musl@1.2.7':
+    resolution: {integrity: sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
-  '@rolldown/binding-openharmony-arm64@1.2.5':
-    resolution: {integrity: sha512-8SLssA2oweAxyRgDp789ACfRb/3P+zNRJpzZxSizxF9m8NUDQ4+3xjo8ttjhVGGw6Qxb70oZiEtIjaKikCO7Yw==}
+  '@rolldown/binding-openharmony-arm64@1.2.7':
+    resolution: {integrity: sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [openharmony]
 
-  '@rolldown/binding-win32-arm64-msvc@1.2.5':
-    resolution: {integrity: sha512-vGbruD5zquhoc8D9SViXgN2FBJtNdTyQ4DtG+SWiEGlJiAzoKcZ2xp+xuXCffhubVdt0NJlTZqkeRuERy7g8Cw==}
+  '@rolldown/binding-win32-arm64-msvc@1.2.7':
+    resolution: {integrity: sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [arm64]
     os: [win32]
 
-  '@rolldown/binding-win32-x64-msvc@1.2.5':
-    resolution: {integrity: sha512-e/SXpgISz+IoqVcSSI0rx/d/he8zqLex+/rCWpnHpmVfmPIUjag9H6P7zotf0gJHwPUhQxZ/mF8tr6acebT9yw==}
+  '@rolldown/binding-win32-x64-msvc@1.2.7':
+    resolution: {integrity: sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==}
     engines: {node: ^20.19.0 || >=22.12.0}
     cpu: [x64]
     os: [win32]
@@ -1708,92 +1700,89 @@ packages:
   '@stablelib/base64@1.0.1':
     resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
 
-  '@standard-schema/spec@1.1.0':
-    resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
-
   '@standard-schema/utils@0.3.0':
     resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
 
-  '@swc/core-darwin-arm64@1.16.1':
-    resolution: {integrity: sha512-zlJblJ8ncErD43lKdxjbUaUskJQf+LxiPXYcWXD8/8ZMV+7uuAT+CwjciLXpyZBd5Pq/S726bMpeeAwSeL1hhg==}
+  '@swc/core-darwin-arm64@1.16.2':
+    resolution: {integrity: sha512-i/j0HNbnn79qnTVPicvay92Nark8fW8NQqn1e2mGERjUXNpBV0+SwQxlRpk2zBhn6laJ8PDI6Kn1nHZhnz3LCA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
 
-  '@swc/core-darwin-x64@1.16.1':
-    resolution: {integrity: sha512-IN0BmPWb0YAh/17mmlWB/HDBtTw2MfuW4hulf/tQAgTQBRH17l+z499bNJLK6LizSjqs0P7V+jU38Zj+vJC1DA==}
+  '@swc/core-darwin-x64@1.16.2':
+    resolution: {integrity: sha512-HrwqHyEyHVXO3qTk8EkNK7/b6sOZSEoNh+pot6RdE5x0LbNqfo8LtJUvi3UTXr+5ja/o5HbJdW80eCXo+NjbiA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
 
-  '@swc/core-linux-arm-gnueabihf@1.16.1':
-    resolution: {integrity: sha512-EYgrx2YOCQ2Twz2S793kqNjPkpvYVUPzzR95bIb7by+VQcyaai4lZZ2iz/tZvcFVKSNcN3/JTKwx+aBn2ZL52A==}
+  '@swc/core-linux-arm-gnueabihf@1.16.2':
+    resolution: {integrity: sha512-MdXi83Z/gGp1LIrg+h7HKxiul/z/Bty/ZJSvYAFqDl9zteC1XLSAZdScquKtXPp50rdyXqritTDCqQBhwVfZKA==}
     engines: {node: '>=10'}
     cpu: [arm]
     os: [linux]
 
-  '@swc/core-linux-arm64-gnu@1.16.1':
-    resolution: {integrity: sha512-moyKm0YZlHdHohzm1YwgAyesqnE853rO0REMfJLFAova51wF9BNi+3ZW2PeS7Vqvn6HeJuepLpAHbBdZctxpHA==}
+  '@swc/core-linux-arm64-gnu@1.16.2':
+    resolution: {integrity: sha512-/jcTmK6Ktz3owM3YtiKvjofV6p3VpHnYzTIrOGwDIOsDigRAAVuZ8east33wYO/7UTdKYFlyHNnJNT0WJqOA3Q==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
     libc: [glibc]
 
-  '@swc/core-linux-arm64-musl@1.16.1':
-    resolution: {integrity: sha512-kKGBO9wdapiSzuf5ZzZ2fYtlu1BNSYtIIUxvH1ir/gcelTOREEHGDCLTDFx/2Knf878nU11A40z7LxwasEFxqA==}
+  '@swc/core-linux-arm64-musl@1.16.2':
+    resolution: {integrity: sha512-4gFarKaFnlJTSlJYKmMhV4u+3YE4uYfiydpBoYjmgQhCf9lAieOq+WilZaK9vVSHeqLuQpTEiGULZqAdsRX5Dw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
     libc: [musl]
 
-  '@swc/core-linux-ppc64-gnu@1.16.1':
-    resolution: {integrity: sha512-nZ6qahtLxC3PM54cWOQZHxt4lTCF/3J4LIoWWzz6v7A+rLs8Dx54anYQf7mH3eIi8KlNpgKci/ie8ZSqFN8O7A==}
+  '@swc/core-linux-ppc64-gnu@1.16.2':
+    resolution: {integrity: sha512-syqSLGd6KlZ1PciNzs6bIUlhOuFztZufebOHaERjc4N4SqNZxyqYd4I+jj/EfOYnpe0kNjccn9HJLN1p5dz3+w==}
     engines: {node: '>=10'}
     cpu: [ppc64]
     os: [linux]
     libc: [glibc]
 
-  '@swc/core-linux-s390x-gnu@1.16.1':
-    resolution: {integrity: sha512-4ji5PNzhYq193Z4/4xUaSoNJza6iCkDJSzhetrbB6KOYxsr+kxtQr8ePWhMJUiMt6JUWtXaZ1PYT8FhtED+nGA==}
+  '@swc/core-linux-s390x-gnu@1.16.2':
+    resolution: {integrity: sha512-ZBBLK+ewGyXLzWeMS7wbKtWBdnif6etn7xvPY/iOfbdsjX/+bgkp1pQt2lWF2wlu2hXYZuhJ/tHZE/QR8/apzg==}
     engines: {node: '>=10'}
     cpu: [s390x]
     os: [linux]
     libc: [glibc]
 
-  '@swc/core-linux-x64-gnu@1.16.1':
-    resolution: {integrity: sha512-VJQxqrisHV+B394IgrOu8YsIIXZgffnf5tO+yc9Z/hoUpuZEvuQTjWwlnpZdpyD+0nx6LTD1/3k646JYm43yJA==}
+  '@swc/core-linux-x64-gnu@1.16.2':
+    resolution: {integrity: sha512-LyHJgxCA4Tje0ysBMbEb0tt/ie8kgUKoFE3JAKFhpevmTmhYEoC0H9s47WuDsqiFckF1ITUguZIXJG6K5e0dvg==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
     libc: [glibc]
 
-  '@swc/core-linux-x64-musl@1.16.1':
-    resolution: {integrity: sha512-r9oV1mwxxsIGcLV1IQ/tw76MW3doatKze1QFWuC+a7QqJUkhY/bKTSVk6NpKKUGm2LDsE33Va8VqSClfA7vSiQ==}
+  '@swc/core-linux-x64-musl@1.16.2':
+    resolution: {integrity: sha512-PghXJlVM1cgtLfNUR1vxFo1z+PDRAe8cWAJlZZ7spmeiN7BospGXg/MHUg7oNSgwSX7Zo//YKv9P5yD9apsFJQ==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
     libc: [musl]
 
-  '@swc/core-win32-arm64-msvc@1.16.1':
-    resolution: {integrity: sha512-6huNRessoBLxWEqBm5zJXyCQ27TO7anvkdiuQ5MDO4CJni0nOXEqKtV9RllQ2TdyENKKsUMXVnIfW2hIXx/R5Q==}
+  '@swc/core-win32-arm64-msvc@1.16.2':
+    resolution: {integrity: sha512-StTOSefYBxemvNYYUI3UmO1a8y+hSPjjfHogC2TEHL+Z1PlEBim/XtLas5rS04jAzT9RrNmbtX911SZ42H9jSQ==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
 
-  '@swc/core-win32-ia32-msvc@1.16.1':
-    resolution: {integrity: sha512-OVKJFUzphrGmsh+BGtcZDesx0YryV7/Yvy5XGgTqnrZfjnyfcr5uaqYQugCckdIlupc5Vs3XtDjRAj12z4ZPlw==}
+  '@swc/core-win32-ia32-msvc@1.16.2':
+    resolution: {integrity: sha512-fycER209DYIzsibpTMC+chND05OfOjgztWL9U8OE6/uUlsOUZH3eh98isBLEnOymYUhlJLEt5++W1+KL/FOh5Q==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
 
-  '@swc/core-win32-x64-msvc@1.16.1':
-    resolution: {integrity: sha512-Bt+VIhWYCGk4urklnkkteLUOeLv1VxigwTCeB/xC6rBZxY6IIKdDwCJf6on3E3SUGsIqmQS6QqtuJQc1VxF4Aw==}
+  '@swc/core-win32-x64-msvc@1.16.2':
+    resolution: {integrity: sha512-cSd1z6ivSrJPVr+moVwOHWjeKy6TpO4/Shwcv5KCrKYXCccxwh4pRy1C3fDioNx2PF1jPZWHKZjtXt+Be9VbaQ==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
 
-  '@swc/core@1.16.1':
-    resolution: {integrity: sha512-nUaeu91O5QZKrQdaDCHd402ogUIoNOOjpkZNq0UomWK0G6gDaGmLhvddF1/3BXf5O8aLyo6ZPY/aMDWvaJQ/hg==}
+  '@swc/core@1.16.2':
+    resolution: {integrity: sha512-95I4kiSMeveI/Mhi+tE4fiWcWLUMfzfKrk0jtr8LRMqHgOgq+xHS+zExkDqoO4b5OeeuXHMWVdD5MeP3X6sULw==}
     engines: {node: '>=10'}
     peerDependencies:
       '@swc/helpers': '>=0.5.17'
@@ -2113,8 +2102,8 @@ packages:
     resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
     engines: {node: '>=12'}
 
-  ast-v8-to-istanbul@1.0.5:
-    resolution: {integrity: sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==}
+  ast-v8-to-istanbul@1.0.6:
+    resolution: {integrity: sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==}
 
   atomic-sleep@1.0.0:
     resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
@@ -2124,13 +2113,13 @@ packages:
     resolution: {integrity: sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==}
     engines: {node: '>= 6.0.0'}
 
-  baseline-browser-mapping@2.11.20:
-    resolution: {integrity: sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw==}
+  baseline-browser-mapping@2.11.21:
+    resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==}
     engines: {node: '>=6.0.0'}
     hasBin: true
 
-  bidi-js@1.0.3:
-    resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+  bidi-js@1.1.0:
+    resolution: {integrity: sha512-fX1Onk0tdVPC7obPWB5EbJ1z7NVhLq4m2xZLq2YXBkxzMXIGRpNMU88n0EPgWseKl12J7zXs7qrDxPK4sRs2fg==}
 
   buffer-from@1.1.2:
     resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -2218,8 +2207,8 @@ packages:
   detect-node-es@1.1.0:
     resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
 
-  dompurify@3.4.14:
-    resolution: {integrity: sha512-dVoH9z+MY+C9IilgGCk3YfFqjLi3fChm2OiKJMzh6axrJ5qwxqWaZamgmHrpv22CN/KdbZJuGEGgfQoL00LTdg==}
+  dompurify@3.4.15:
+    resolution: {integrity: sha512-EUBjM+B+lkDE41iE82DDSCfkoPGfXx8IxFxPMjNzm/Uk4xDet77rTN9wqlxlVg71kK7XGuUMv6wUxJUwwv+Xyw==}
 
   drizzle-kit@0.31.10:
     resolution: {integrity: sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw==}
@@ -2348,8 +2337,8 @@ packages:
     resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==}
     engines: {node: '>=12.0.0'}
 
-  fast-copy@4.1.0:
-    resolution: {integrity: sha512-f5oNUvGAmBPLP85im9czQ7fVWh3RfcIIIzyXrcXKmCiWoDn+CRmfsoQN3jmE5J+UEquj4Ugc1woTaxD0oa37Gg==}
+  fast-copy@4.1.1:
+    resolution: {integrity: sha512-A4QTJmuiztpGtr6AMeJts9R4hbj2ZBUwtOaKrG6rw2y7t6+IaJKjz5M3XDs8BUznxDH43FVc6A0y/gWlMl4UtA==}
 
   fast-safe-stringify@2.1.1:
     resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
@@ -2389,11 +2378,6 @@ packages:
       react-dom:
         optional: true
 
-  fsevents@2.3.2:
-    resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
-    engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
-    os: [darwin]
-
   fsevents@2.3.3:
     resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
     engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -2412,10 +2396,6 @@ packages:
   graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
-  has-flag@4.0.0:
-    resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
-    engines: {node: '>=8'}
-
   hash-wasm@4.12.0:
     resolution: {integrity: sha512-+/2B2rYLb48I/evdOIhP+K/DD2ca2fgBjp6O+GBEnCDk2e4rpeXIK8GvIyRPjTezgmWn9gmKwkQjjx6BtqDHVQ==}
 
@@ -2479,8 +2459,8 @@ packages:
     resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
     hasBin: true
 
-  jose@6.2.10:
-    resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==}
+  jose@6.2.12:
+    resolution: {integrity: sha512-9NiFmJEex0sy2Dk58j2UGBSHgUs2ypF9eZSu4L6vjOX3Dp96Sw1F3uL+H+D1sx02jZZdzUT0HgvCy59CuvXcWw==}
 
   joycon@3.1.1:
     resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
@@ -2675,8 +2655,8 @@ packages:
     resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==}
     engines: {node: 20 || >=22}
 
-  lru.min@1.1.4:
-    resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==}
+  lru.min@1.1.5:
+    resolution: {integrity: sha512-5J9ysMYUpYIg9RF2vJpy9SinEmSviFSe0GyPpCQ4L5QSkLAgeLXlTAOu2ZwWUU5m+0SBl6gUU1R1ZQB3aKypfA==}
     engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'}
 
   lucide-react@1.41.0:
@@ -2803,8 +2783,8 @@ packages:
   node-addon-api@7.1.1:
     resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
 
-  oauth4webapi@3.8.7:
-    resolution: {integrity: sha512-4RxcKxXjuItDFZ20RRPf4YTw3kpeXJyCgJFxVzJ068A7PNJ18st2Dg90tlC1LkSDS0GecroagCLHYEIVUhCAkw==}
+  oauth4webapi@3.8.8:
+    resolution: {integrity: sha512-8N28E+a/oxfXWBgOMt+ZP/JUf/XR+IFbvkAEPP3gznXOMv9BpAAwiIj0TFNz3tGTPc0ZQ8zmWBNgN1nAys0gng==}
 
   obug@2.1.4:
     resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
@@ -2857,16 +2837,6 @@ packages:
     resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
     hasBin: true
 
-  playwright-core@1.62.1:
-    resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
-    engines: {node: '>=20'}
-    hasBin: true
-
-  playwright@1.62.1:
-    resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
-    engines: {node: '>=20'}
-    hasBin: true
-
   po-parser@2.2.0:
     resolution: {integrity: sha512-NdTrKgh0oO7+y+RFX8KKhOB438x/j94UGXEFzl/7pHH0JjWSn42iJ9tgmPksIO6n1JKDHmNDcejPJfKspljB9g==}
 
@@ -2987,8 +2957,8 @@ packages:
   resolve-pkg-maps@1.0.0:
     resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
 
-  rolldown@1.2.5:
-    resolution: {integrity: sha512-VD2IE5PUG4Oj8zz2VGykiYd5wbnjdIiSsNQb8Qu5B+noEp+A78mu2iVvpp27g8es14Tk9rofNs5Tku9iQCS4fA==}
+  rolldown@1.2.7:
+    resolution: {integrity: sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==}
     engines: {node: ^20.19.0 || >=22.12.0}
     hasBin: true
 
@@ -3111,10 +3081,6 @@ packages:
       babel-plugin-macros:
         optional: true
 
-  supports-color@7.2.0:
-    resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
-    engines: {node: '>=8'}
-
   symbol-tree@3.2.4:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
 
@@ -3140,6 +3106,10 @@ packages:
     resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
     engines: {node: '>=18'}
 
+  tinyexec@1.3.1:
+    resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
+    engines: {node: '>=18'}
+
   tinyglobby@0.2.17:
     resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
     engines: {node: '>=12.0.0'}
@@ -3151,11 +3121,11 @@ packages:
     resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==}
     engines: {node: '>=14.0.0'}
 
-  tldts-core@7.4.11:
-    resolution: {integrity: sha512-CW3WN2rIIE/Of21mulhgnGOwoDyEFNygyIBOONSdyAuSATgMMUCpLeUlB+E8sAwA5xRV9hYPl+kyZ9citHCaKg==}
+  tldts-core@7.4.12:
+    resolution: {integrity: sha512-nYNzS2WRf4QJmjzFFgAxLOBjyBxAGRbCy9PVBPaglcYyYajh40VBn+v5Ngr96ZMc7oM0+aCJdtQnNejvdBnXMQ==}
 
-  tldts@7.4.11:
-    resolution: {integrity: sha512-aBiNayCfTQxuIJBm06M+xR14cYaYlDlSXZbgsnKzKNxDKUVq7KFwTjwBSsb7m9Y5xO8WfPnBc63WaYFMTGlvqw==}
+  tldts@7.4.12:
+    resolution: {integrity: sha512-WylhSDKVeYnWXL3a+vKTaOxjnOeEGw938hImY8zoRWJjRRK/Jp1K+IihBzIONpUmW4e3WmXT6q5FW6vlESVZCA==}
     hasBin: true
 
   token-types@6.1.2:
@@ -3187,15 +3157,15 @@ packages:
     resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
     engines: {node: '>=18'}
 
-  unbash@4.0.10:
-    resolution: {integrity: sha512-b7zoBQvpWp0vuN5q2vK2RRBR2SvuruQAs50DApdDveBSn3eSYd84IaHodFqQIMlvY9K2VnyBUEXgwOBuGU9GBg==}
+  unbash@4.0.11:
+    resolution: {integrity: sha512-FoSOKV7NEofQSkAefMVHam4ZPKYMxjAydxiV72UFEDNV/YofxjGfiZ2A9pZjdL/lRJzTjcu4PABo1JYJX8N5iQ==}
     engines: {node: '>=14'}
 
   undici-types@8.3.0:
     resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
 
-  undici@8.10.1:
-    resolution: {integrity: sha512-YQ3WlbqjYMmNpdvDH64jAgLjxuAR9+649calDWhbshYaeQGO2bR4nI94ORJmwI3J9YhoKQnpyGOK+0zlWS5N5Q==}
+  undici@8.10.2:
+    resolution: {integrity: sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==}
     engines: {node: '>=22.19.0'}
 
   use-callback-ref@1.3.3:
@@ -3367,7 +3337,7 @@ packages:
 
 snapshots:
 
-  '@alloc/quick-lru@5.2.0': {}
+  '@alloc/quick-lru@5.3.0': {}
 
   '@asamuzakjp/css-color@6.0.7':
     dependencies:
@@ -3379,7 +3349,7 @@ snapshots:
 
   '@asamuzakjp/dom-selector@8.3.2':
     dependencies:
-      bidi-js: 1.0.3
+      bidi-js: 1.1.0
       css-tree: 3.2.1
       is-potential-custom-element-name: 1.0.1
       lru-cache: 11.5.2
@@ -3387,8 +3357,8 @@ snapshots:
   '@auth/core@0.41.3':
     dependencies:
       '@panva/hkdf': 1.2.1
-      jose: 6.2.10
-      oauth4webapi: 3.8.7
+      jose: 6.2.12
+      oauth4webapi: 3.8.8
       preact: 10.24.3
       preact-render-to-string: 6.5.11(preact@10.24.3)
 
@@ -3755,12 +3725,11 @@ snapshots:
     dependencies:
       '@formatjs/fast-memoize': 3.1.7
 
-  '@hookform/resolvers@5.9.1(@standard-schema/spec@1.1.0)(react-hook-form@7.87.0(react@19.2.8))(zod@4.5.4)':
+  '@hookform/resolvers@5.9.1(react-hook-form@7.87.0(react@19.2.8))(zod@4.5.4)':
     dependencies:
       '@standard-schema/utils': 0.3.0
       react-hook-form: 7.87.0(react@19.2.8)
     optionalDependencies:
-      '@standard-schema/spec': 1.1.0
       zod: 4.5.4
 
   '@img/colour@1.1.0': {}
@@ -4009,10 +3978,10 @@ snapshots:
   '@oxc-parser/binding-win32-x64-msvc@0.147.0':
     optional: true
 
-  '@oxc-project/types@0.146.0': {}
-
   '@oxc-project/types@0.147.0': {}
 
+  '@oxc-project/types@0.148.0': {}
+
   '@oxc-resolver/binding-android-arm-eabi@11.24.2':
     optional: true
 
@@ -4134,10 +4103,6 @@ snapshots:
 
   '@pinojs/redact@0.4.0': {}
 
-  '@playwright/test@1.62.1':
-    dependencies:
-      playwright: 1.62.1
-
   '@radix-ui/primitive@1.1.7': {}
 
   '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.18)(react@19.2.8)':
@@ -4275,49 +4240,49 @@ snapshots:
     optionalDependencies:
       '@types/react': 19.2.18
 
-  '@rolldown/binding-android-arm-eabi@1.2.5':
+  '@rolldown/binding-android-arm-eabi@1.2.7':
     optional: true
 
-  '@rolldown/binding-android-arm64@1.2.5':
+  '@rolldown/binding-android-arm64@1.2.7':
     optional: true
 
-  '@rolldown/binding-darwin-arm64@1.2.5':
+  '@rolldown/binding-darwin-arm64@1.2.7':
     optional: true
 
-  '@rolldown/binding-darwin-x64@1.2.5':
+  '@rolldown/binding-darwin-x64@1.2.7':
     optional: true
 
-  '@rolldown/binding-freebsd-x64@1.2.5':
+  '@rolldown/binding-freebsd-x64@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-arm-gnueabihf@1.2.5':
+  '@rolldown/binding-linux-arm-gnueabihf@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-arm64-gnu@1.2.5':
+  '@rolldown/binding-linux-arm64-gnu@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-arm64-musl@1.2.5':
+  '@rolldown/binding-linux-arm64-musl@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-ppc64-gnu@1.2.5':
+  '@rolldown/binding-linux-ppc64-gnu@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-s390x-gnu@1.2.5':
+  '@rolldown/binding-linux-s390x-gnu@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-x64-gnu@1.2.5':
+  '@rolldown/binding-linux-x64-gnu@1.2.7':
     optional: true
 
-  '@rolldown/binding-linux-x64-musl@1.2.5':
+  '@rolldown/binding-linux-x64-musl@1.2.7':
     optional: true
 
-  '@rolldown/binding-openharmony-arm64@1.2.5':
+  '@rolldown/binding-openharmony-arm64@1.2.7':
     optional: true
 
-  '@rolldown/binding-win32-arm64-msvc@1.2.5':
+  '@rolldown/binding-win32-arm64-msvc@1.2.7':
     optional: true
 
-  '@rolldown/binding-win32-x64-msvc@1.2.5':
+  '@rolldown/binding-win32-x64-msvc@1.2.7':
     optional: true
 
   '@rolldown/pluginutils@1.0.1': {}
@@ -4328,64 +4293,61 @@ snapshots:
 
   '@stablelib/base64@1.0.1': {}
 
-  '@standard-schema/spec@1.1.0':
-    optional: true
-
   '@standard-schema/utils@0.3.0': {}
 
-  '@swc/core-darwin-arm64@1.16.1':
+  '@swc/core-darwin-arm64@1.16.2':
     optional: true
 
-  '@swc/core-darwin-x64@1.16.1':
+  '@swc/core-darwin-x64@1.16.2':
     optional: true
 
-  '@swc/core-linux-arm-gnueabihf@1.16.1':
+  '@swc/core-linux-arm-gnueabihf@1.16.2':
     optional: true
 
-  '@swc/core-linux-arm64-gnu@1.16.1':
+  '@swc/core-linux-arm64-gnu@1.16.2':
     optional: true
 
-  '@swc/core-linux-arm64-musl@1.16.1':
+  '@swc/core-linux-arm64-musl@1.16.2':
     optional: true
 
-  '@swc/core-linux-ppc64-gnu@1.16.1':
+  '@swc/core-linux-ppc64-gnu@1.16.2':
     optional: true
 
-  '@swc/core-linux-s390x-gnu@1.16.1':
+  '@swc/core-linux-s390x-gnu@1.16.2':
     optional: true
 
-  '@swc/core-linux-x64-gnu@1.16.1':
+  '@swc/core-linux-x64-gnu@1.16.2':
     optional: true
 
-  '@swc/core-linux-x64-musl@1.16.1':
+  '@swc/core-linux-x64-musl@1.16.2':
     optional: true
 
-  '@swc/core-win32-arm64-msvc@1.16.1':
+  '@swc/core-win32-arm64-msvc@1.16.2':
     optional: true
 
-  '@swc/core-win32-ia32-msvc@1.16.1':
+  '@swc/core-win32-ia32-msvc@1.16.2':
     optional: true
 
-  '@swc/core-win32-x64-msvc@1.16.1':
+  '@swc/core-win32-x64-msvc@1.16.2':
     optional: true
 
-  '@swc/core@1.16.1(@swc/helpers@0.5.23)':
+  '@swc/core@1.16.2(@swc/helpers@0.5.23)':
     dependencies:
       '@swc/counter': 0.1.3
       '@swc/types': 0.1.28
     optionalDependencies:
-      '@swc/core-darwin-arm64': 1.16.1
-      '@swc/core-darwin-x64': 1.16.1
-      '@swc/core-linux-arm-gnueabihf': 1.16.1
-      '@swc/core-linux-arm64-gnu': 1.16.1
-      '@swc/core-linux-arm64-musl': 1.16.1
-      '@swc/core-linux-ppc64-gnu': 1.16.1
-      '@swc/core-linux-s390x-gnu': 1.16.1
-      '@swc/core-linux-x64-gnu': 1.16.1
-      '@swc/core-linux-x64-musl': 1.16.1
-      '@swc/core-win32-arm64-msvc': 1.16.1
-      '@swc/core-win32-ia32-msvc': 1.16.1
-      '@swc/core-win32-x64-msvc': 1.16.1
+      '@swc/core-darwin-arm64': 1.16.2
+      '@swc/core-darwin-x64': 1.16.2
+      '@swc/core-linux-arm-gnueabihf': 1.16.2
+      '@swc/core-linux-arm64-gnu': 1.16.2
+      '@swc/core-linux-arm64-musl': 1.16.2
+      '@swc/core-linux-ppc64-gnu': 1.16.2
+      '@swc/core-linux-s390x-gnu': 1.16.2
+      '@swc/core-linux-x64-gnu': 1.16.2
+      '@swc/core-linux-x64-musl': 1.16.2
+      '@swc/core-win32-arm64-msvc': 1.16.2
+      '@swc/core-win32-ia32-msvc': 1.16.2
+      '@swc/core-win32-x64-msvc': 1.16.2
       '@swc/helpers': 0.5.23
 
   '@swc/counter@0.1.3': {}
@@ -4466,7 +4428,7 @@ snapshots:
 
   '@tailwindcss/postcss@4.3.3':
     dependencies:
-      '@alloc/quick-lru': 5.2.0
+      '@alloc/quick-lru': 5.3.0
       '@tailwindcss/node': 4.3.3
       '@tailwindcss/oxide': 4.3.3
       postcss: 8.5.28
@@ -4485,9 +4447,9 @@ snapshots:
 
   '@tanstack/virtual-core@3.17.8': {}
 
-  '@tokenizer/inflate@0.4.1(supports-color@7.2.0)':
+  '@tokenizer/inflate@0.4.1':
     dependencies:
-      debug: 4.4.3(supports-color@7.2.0)
+      debug: 4.4.3
       token-types: 6.1.2
     transitivePeerDependencies:
       - supports-color
@@ -4588,7 +4550,7 @@ snapshots:
       '@bcoe/v8-coverage': 1.0.2
       '@vitest/istanbul-lib-coverage': 1.0.1
       '@vitest/istanbul-lib-report': 1.0.1
-      ast-v8-to-istanbul: 1.0.5
+      ast-v8-to-istanbul: 1.0.6
       magicast: 0.5.4
       obug: 2.1.4
       std-env: 4.2.0
@@ -4618,7 +4580,7 @@ snapshots:
 
   assertion-error@2.0.1: {}
 
-  ast-v8-to-istanbul@1.0.5:
+  ast-v8-to-istanbul@1.0.6:
     dependencies:
       '@jridgewell/trace-mapping': 0.3.31
       estree-walker: 3.0.3
@@ -4628,9 +4590,9 @@ snapshots:
 
   aws-ssl-profiles@1.1.2: {}
 
-  baseline-browser-mapping@2.11.20: {}
+  baseline-browser-mapping@2.11.21: {}
 
-  bidi-js@1.0.3:
+  bidi-js@1.1.0:
     dependencies:
       require-from-string: 2.0.2
 
@@ -4688,11 +4650,9 @@ snapshots:
 
   dateformat@4.6.3: {}
 
-  debug@4.4.3(supports-color@7.2.0):
+  debug@4.4.3:
     dependencies:
       ms: 2.1.3
-    optionalDependencies:
-      supports-color: 7.2.0
 
   decimal.js@10.6.0: {}
 
@@ -4702,7 +4662,7 @@ snapshots:
 
   detect-node-es@1.1.0: {}
 
-  dompurify@3.4.14:
+  dompurify@3.4.15:
     optionalDependencies:
       '@types/trusted-types': 2.0.7
 
@@ -4794,7 +4754,7 @@ snapshots:
 
   expect-type@1.4.0: {}
 
-  fast-copy@4.1.0: {}
+  fast-copy@4.1.1: {}
 
   fast-safe-stringify@2.1.1: {}
 
@@ -4808,9 +4768,9 @@ snapshots:
     optionalDependencies:
       picomatch: 4.0.7
 
-  file-type@21.3.4(supports-color@7.2.0):
+  file-type@21.3.4:
     dependencies:
-      '@tokenizer/inflate': 0.4.1(supports-color@7.2.0)
+      '@tokenizer/inflate': 0.4.1
       strtok3: 10.3.5
       token-types: 6.1.2
       uint8array-extras: 1.5.0
@@ -4831,9 +4791,6 @@ snapshots:
       react: 19.2.8
       react-dom: 19.2.8(react@19.2.8)
 
-  fsevents@2.3.2:
-    optional: true
-
   fsevents@2.3.3:
     optional: true
 
@@ -4849,9 +4806,6 @@ snapshots:
 
   graceful-fs@4.2.11: {}
 
-  has-flag@4.0.0:
-    optional: true
-
   hash-wasm@4.12.0: {}
 
   help-me@5.0.0: {}
@@ -4883,11 +4837,11 @@ snapshots:
       '@formatjs/fast-memoize': 3.1.7
       '@formatjs/icu-messageformat-parser': 3.5.17
 
-  ioredis@6.0.0(supports-color@7.2.0):
+  ioredis@6.0.0:
     dependencies:
       '@ioredis/commands': 2.0.0
       cluster-key-slot: 1.1.1
-      debug: 4.4.3(supports-color@7.2.0)
+      debug: 4.4.3
       denque: 2.1.0
       redis-errors: 1.2.0
       standard-as-callback: 2.1.0
@@ -4908,7 +4862,7 @@ snapshots:
 
   isomorphic-dompurify@4.1.0(@noble/hashes@2.4.0):
     dependencies:
-      dompurify: 3.4.14
+      dompurify: 3.4.15
       jsdom: 30.0.1(@noble/hashes@2.4.0)
     transitivePeerDependencies:
       - '@noble/hashes'
@@ -4916,7 +4870,7 @@ snapshots:
 
   jiti@2.7.0: {}
 
-  jose@6.2.10: {}
+  jose@6.2.12: {}
 
   joycon@3.1.1: {}
 
@@ -4941,7 +4895,7 @@ snapshots:
       saxes: 6.0.0
       symbol-tree: 3.2.4
       tough-cookie: 6.0.2
-      undici: 8.10.1
+      undici: 8.10.2
       w3c-xmlserializer: 5.0.0
       webidl-conversions: 8.0.1
       whatwg-mimetype: 5.0.0
@@ -4971,7 +4925,7 @@ snapshots:
       smol-toml: 1.8.0
       strip-json-comments: 5.0.3
       tinyglobby: 0.2.17
-      unbash: 4.0.10
+      unbash: 4.0.11
       yaml: 2.9.0
       zod: 4.5.4
 
@@ -5081,7 +5035,7 @@ snapshots:
     dependencies:
       picomatch: 4.0.7
       string-argv: 0.3.2
-      tinyexec: 1.3.0
+      tinyexec: 1.3.1
     optionalDependencies:
       yaml: 2.9.0
 
@@ -5089,7 +5043,7 @@ snapshots:
 
   lru-cache@11.5.2: {}
 
-  lru.min@1.1.4: {}
+  lru.min@1.1.5: {}
 
   lucide-react@1.41.0(react@19.2.8):
     dependencies:
@@ -5135,13 +5089,13 @@ snapshots:
 
   ms@2.1.3: {}
 
-  music-metadata@11.15.0(supports-color@7.2.0):
+  music-metadata@11.15.0:
     dependencies:
       '@borewit/text-codec': 0.2.2
       '@tokenizer/token': 0.3.0
       content-type: 2.1.0
-      debug: 4.4.3(supports-color@7.2.0)
-      file-type: 21.3.4(supports-color@7.2.0)
+      debug: 4.4.3
+      file-type: 21.3.4
       media-typer: 2.0.0
       strtok3: 10.3.5
       token-types: 6.1.2
@@ -5157,13 +5111,13 @@ snapshots:
       generate-function: 2.3.1
       iconv-lite: 0.7.3
       long: 5.3.2
-      lru.min: 1.1.4
+      lru.min: 1.1.5
       named-placeholders: 1.1.6
       sql-escaper: 1.5.1
 
   named-placeholders@1.1.6:
     dependencies:
-      lru.min: 1.1.4
+      lru.min: 1.1.5
 
   nanoid@3.3.18: {}
 
@@ -5171,36 +5125,36 @@ snapshots:
     dependencies:
       content-type: 2.1.0
 
-  next-auth@5.0.0-beta.32(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8):
+  next-auth@5.0.0-beta.32(next@16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8):
     dependencies:
       '@auth/core': 0.41.3
-      next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+      next: 16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
       react: 19.2.8
 
   next-intl-swc-plugin-extractor@4.14.2: {}
 
-  next-intl@4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8):
+  next-intl@4.14.2(@swc/helpers@0.5.23)(next@16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8):
     dependencies:
       '@eloqnt/config': 0.1.0
       '@eloqnt/format-json': 0.1.0
       '@eloqnt/format-po': 0.1.0
       '@formatjs/intl-localematcher': 0.8.13
       '@parcel/watcher': 2.6.0
-      '@swc/core': 1.16.1(@swc/helpers@0.5.23)
+      '@swc/core': 1.16.2(@swc/helpers@0.5.23)
       icu-minify: 4.14.2
       negotiator: 1.1.0
-      next: 16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
+      next: 16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
       next-intl-swc-plugin-extractor: 4.14.2
       react: 19.2.8
       use-intl: 4.14.2(react@19.2.8)
     transitivePeerDependencies:
       - '@swc/helpers'
 
-  next@16.3.4(@playwright/test@1.62.1)(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
+  next@16.3.4(@types/node@26.4.1)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
     dependencies:
       '@next/env': 16.3.4
       '@swc/helpers': 0.5.23
-      baseline-browser-mapping: 2.11.20
+      baseline-browser-mapping: 2.11.21
       caniuse-lite: 1.0.30001810
       postcss: 8.5.23
       react: 19.2.8
@@ -5215,7 +5169,6 @@ snapshots:
       '@next/swc-linux-x64-musl': 16.3.4
       '@next/swc-win32-arm64-msvc': 16.3.4
       '@next/swc-win32-x64-msvc': 16.3.4
-      '@playwright/test': 1.62.1
       sharp: 0.35.4(@types/node@26.4.1)
     transitivePeerDependencies:
       - '@babel/core'
@@ -5224,7 +5177,7 @@ snapshots:
 
   node-addon-api@7.1.1: {}
 
-  oauth4webapi@3.8.7: {}
+  oauth4webapi@3.8.8: {}
 
   obug@2.1.4: {}
 
@@ -5309,7 +5262,7 @@ snapshots:
     dependencies:
       colorette: 2.0.20
       dateformat: 4.6.3
-      fast-copy: 4.1.0
+      fast-copy: 4.1.1
       fast-safe-stringify: 2.1.1
       help-me: 5.0.0
       joycon: 3.1.1
@@ -5337,14 +5290,6 @@ snapshots:
       sonic-boom: 4.2.1
       thread-stream: 4.2.0
 
-  playwright-core@1.62.1: {}
-
-  playwright@1.62.1:
-    dependencies:
-      playwright-core: 1.62.1
-    optionalDependencies:
-      fsevents: 2.3.2
-
   po-parser@2.2.0: {}
 
   postal-mime@2.7.5: {}
@@ -5450,26 +5395,26 @@ snapshots:
 
   resolve-pkg-maps@1.0.0: {}
 
-  rolldown@1.2.5:
+  rolldown@1.2.7:
     dependencies:
-      '@oxc-project/types': 0.146.0
+      '@oxc-project/types': 0.148.0
       '@rolldown/pluginutils': 1.0.1
     optionalDependencies:
-      '@rolldown/binding-android-arm-eabi': 1.2.5
-      '@rolldown/binding-android-arm64': 1.2.5
-      '@rolldown/binding-darwin-arm64': 1.2.5
-      '@rolldown/binding-darwin-x64': 1.2.5
-      '@rolldown/binding-freebsd-x64': 1.2.5
-      '@rolldown/binding-linux-arm-gnueabihf': 1.2.5
-      '@rolldown/binding-linux-arm64-gnu': 1.2.5
-      '@rolldown/binding-linux-arm64-musl': 1.2.5
-      '@rolldown/binding-linux-ppc64-gnu': 1.2.5
-      '@rolldown/binding-linux-s390x-gnu': 1.2.5
-      '@rolldown/binding-linux-x64-gnu': 1.2.5
-      '@rolldown/binding-linux-x64-musl': 1.2.5
-      '@rolldown/binding-openharmony-arm64': 1.2.5
-      '@rolldown/binding-win32-arm64-msvc': 1.2.5
-      '@rolldown/binding-win32-x64-msvc': 1.2.5
+      '@rolldown/binding-android-arm-eabi': 1.2.7
+      '@rolldown/binding-android-arm64': 1.2.7
+      '@rolldown/binding-darwin-arm64': 1.2.7
+      '@rolldown/binding-darwin-x64': 1.2.7
+      '@rolldown/binding-freebsd-x64': 1.2.7
+      '@rolldown/binding-linux-arm-gnueabihf': 1.2.7
+      '@rolldown/binding-linux-arm64-gnu': 1.2.7
+      '@rolldown/binding-linux-arm64-musl': 1.2.7
+      '@rolldown/binding-linux-ppc64-gnu': 1.2.7
+      '@rolldown/binding-linux-s390x-gnu': 1.2.7
+      '@rolldown/binding-linux-x64-gnu': 1.2.7
+      '@rolldown/binding-linux-x64-musl': 1.2.7
+      '@rolldown/binding-openharmony-arm64': 1.2.7
+      '@rolldown/binding-win32-arm64-msvc': 1.2.7
+      '@rolldown/binding-win32-x64-msvc': 1.2.7
 
   safe-buffer@5.1.2: {}
 
@@ -5580,11 +5525,6 @@ snapshots:
       client-only: 0.0.1
       react: 19.2.8
 
-  supports-color@7.2.0:
-    dependencies:
-      has-flag: 4.0.0
-    optional: true
-
   symbol-tree@3.2.4: {}
 
   tailwind-merge@3.6.0: {}
@@ -5601,6 +5541,8 @@ snapshots:
 
   tinyexec@1.3.0: {}
 
+  tinyexec@1.3.1: {}
+
   tinyglobby@0.2.17:
     dependencies:
       fdir: 6.5.0(picomatch@4.0.7)
@@ -5610,11 +5552,11 @@ snapshots:
 
   tinyrainbow@3.1.1: {}
 
-  tldts-core@7.4.11: {}
+  tldts-core@7.4.12: {}
 
-  tldts@7.4.11:
+  tldts@7.4.12:
     dependencies:
-      tldts-core: 7.4.11
+      tldts-core: 7.4.12
 
   token-types@6.1.2:
     dependencies:
@@ -5624,7 +5566,7 @@ snapshots:
 
   tough-cookie@6.0.2:
     dependencies:
-      tldts: 7.4.11
+      tldts: 7.4.12
 
   tr46@6.0.0:
     dependencies:
@@ -5663,11 +5605,11 @@ snapshots:
 
   uint8array-extras@1.5.0: {}
 
-  unbash@4.0.10: {}
+  unbash@4.0.11: {}
 
   undici-types@8.3.0: {}
 
-  undici@8.10.1: {}
+  undici@8.10.2: {}
 
   use-callback-ref@1.3.3(@types/react@19.2.18)(react@19.2.8):
     dependencies:
@@ -5703,7 +5645,7 @@ snapshots:
       lightningcss: 1.33.0
       picomatch: 4.0.7
       postcss: 8.5.28
-      rolldown: 1.2.5
+      rolldown: 1.2.7
       tinyglobby: 0.2.17
     optionalDependencies:
       '@types/node': 26.4.1
diff --git a/tsconfig.json b/tsconfig.json
index 5dc971ca..4f65630c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -34,5 +34,5 @@
 		".next-staging/types/**/*.ts",
 		".next-staging/dev/types/**/*.ts"
 	],
-	"exclude": ["node_modules", "e2e", "playwright.config.ts", "src/generated"]
+	"exclude": ["node_modules", "src/generated"]
 }

From 2a1aef1c1b3a6bc0dca6ed03d0b8790258eeed7b Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 16:47:39 +0200
Subject: [PATCH 04/10] fix: complete Playwright removal in CI deploy workflow

Drop the leftover Playwright browser install and e2e smoke test from the deployment script, and update the deployment contract tests to cover the verify-deployed-release smoke check instead.
---
 .dockerignore                            |  3 ---
 scripts/ci-deploy.sh                     |  5 +----
 src/lib/ci-deploy.test.ts                |  2 +-
 src/lib/ci-workflow-contract.test.ts     |  5 +++--
 src/lib/deploy-workflow-contract.test.ts |  2 +-
 src/test/ci-deploy-harness.sh            | 10 ++++++++--
 6 files changed, 14 insertions(+), 13 deletions(-)

diff --git a/.dockerignore b/.dockerignore
index 265d8fd4..0e0ef78b 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -34,8 +34,5 @@ public/cache
 public/tmp
 db_backup_*.sql
 *.log
-playwright-report
-test-results
-blob-report
 .codex
 .agents
diff --git a/scripts/ci-deploy.sh b/scripts/ci-deploy.sh
index 0f926123..c5b9753e 100644
--- a/scripts/ci-deploy.sh
+++ b/scripts/ci-deploy.sh
@@ -93,8 +93,6 @@ for managed_name in epicnext-cms epicnext-cms-app; do
 done
 cp "$deploy_dir/.env" .env
 pnpm install --frozen-lockfile
-# Prepare browser before cutover so installation failures cannot interrupt the site.
-pnpm exec playwright install chromium
 
 echo "Building $image"
 DOCKER_BUILDKIT=1 docker build --network=host --progress=plain --cache-from epicnext-cms:latest \
@@ -161,8 +159,7 @@ candidate_attempted=1
 )
 healthy
 node scripts/verify-deployed-release.mjs http://127.0.0.1:3002/api/health "$sha"
-PLAYWRIGHT_BASE_URL=http://127.0.0.1:3002 pnpm test:e2e
-# Publish the latest alias only after health and browser checks pass.
+# Publish the latest alias only after health and release checks pass.
 docker tag "$image" epicnext-cms:latest
 cutover_started=0
 if [ "$backup_created" -eq 1 ]; then docker rm "$backup_name" || true; fi
diff --git a/src/lib/ci-deploy.test.ts b/src/lib/ci-deploy.test.ts
index 60ee35f2..8339ea3e 100644
--- a/src/lib/ci-deploy.test.ts
+++ b/src/lib/ci-deploy.test.ts
@@ -77,7 +77,7 @@ describe("deployment transaction", () => {
 		expect(result.calls.indexOf("pnpm db:migrate")).toBeLessThan(
 			result.calls.indexOf("docker stop"),
 		);
-		expect(result.calls.indexOf("pnpm test:e2e")).toBeLessThan(
+		expect(result.calls.indexOf("verify-deployed-release.mjs")).toBeLessThan(
 			result.calls.indexOf(
 				`docker tag epicnext-cms:${sha} epicnext-cms:latest`,
 			),
diff --git a/src/lib/ci-workflow-contract.test.ts b/src/lib/ci-workflow-contract.test.ts
index 6d000c9f..750cfb39 100644
--- a/src/lib/ci-workflow-contract.test.ts
+++ b/src/lib/ci-workflow-contract.test.ts
@@ -61,7 +61,8 @@ describe("CI workflow", () => {
 	it("smoke-tests the deployed app within the deployment transaction", () => {
 		expect(workflow).toContain("bash scripts/ci-deploy.sh");
 		const deploy = readFileSync("scripts/ci-deploy.sh", "utf8");
-		expect(deploy).toContain("pnpm test:e2e");
-		expect(deploy).toContain("PLAYWRIGHT_BASE_URL");
+		expect(deploy).toContain(
+			"node scripts/verify-deployed-release.mjs http://127.0.0.1:3002/api/health",
+		);
 	});
 });
diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts
index b459136b..cf76594d 100644
--- a/src/lib/deploy-workflow-contract.test.ts
+++ b/src/lib/deploy-workflow-contract.test.ts
@@ -8,7 +8,7 @@ it("uses two test workers and runs smoke checks in the deployment transaction",
 	expect(workflow).not.toContain("\n  e2e:");
 	expect(workflow).toContain("bash scripts/ci-deploy.sh");
 	expect(deploy).toContain(
-		"PLAYWRIGHT_BASE_URL=http://127.0.0.1:3002 pnpm test:e2e",
+		"node scripts/verify-deployed-release.mjs http://127.0.0.1:3002/api/health",
 	);
 });
 it("locks CI and scheduled deployments using the same production lock", () => {
diff --git a/src/test/ci-deploy-harness.sh b/src/test/ci-deploy-harness.sh
index 61bdaae5..3ec66ee0 100644
--- a/src/test/ci-deploy-harness.sh
+++ b/src/test/ci-deploy-harness.sh
@@ -13,7 +13,6 @@ flock() { echo "lock" >> "$TEST_DIR/calls"; [ "$SCENARIO" != lock-failure ]; }
 pnpm() {
   echo "pnpm $*" >> "$TEST_DIR/calls"
   if [ "$1" = db:migrate ] && [ "$SCENARIO" = migration-failure ]; then return 1; fi
-  if [ "$1" = test:e2e ] && [ "$SCENARIO" = smoke-failure ]; then return 1; fi
   return 0
 }
 curl() {
@@ -43,5 +42,12 @@ docker() {
 }
 export -f git flock pnpm curl sleep docker
 
-node() { echo "node $*" >> "$TEST_DIR/calls"; [ "$SCENARIO" != release-failure ]; }
+node() {
+  echo "node $*" >> "$TEST_DIR/calls"
+  case "$SCENARIO" in
+    release-failure) return 1 ;;
+    smoke-failure) case "$*" in *verify-deployed-release.mjs*) return 1 ;; esac ;;
+  esac
+  return 0
+}
 export -f node

From ecadef904d01e7998fce9fd772ddb61958c5900a Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 16:56:55 +0200
Subject: [PATCH 05/10] chore: remove knip, husky and lint-staged

Drop the unused knip dead-code check and the husky+lint-staged pre-commit hook pipeline. All checks remain covered by the CI workflow (lint, typecheck, i18n, tests).
---
 .gitea/workflows/ci.yaml            |   3 -
 .husky/pre-commit                   |   1 -
 .husky/pre-push                     |   2 -
 docs/operations/cms-error-center.md |   2 +-
 knip.json                           |  17 -
 package.json                        |   9 -
 pnpm-lock.yaml                      | 553 +---------------------------
 scripts/prepare-hooks.mjs           |   5 -
 8 files changed, 3 insertions(+), 589 deletions(-)
 delete mode 100755 .husky/pre-commit
 delete mode 100755 .husky/pre-push
 delete mode 100644 knip.json
 delete mode 100644 scripts/prepare-hooks.mjs

diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index 7c7cc229..7af40b5d 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -58,9 +58,6 @@ jobs:
             time pnpm test --maxWorkers=2
           fi
 
-      - name: Knip (unused files/exports)
-        run: pnpm knip
-
   # ─────────────────────────────────────────────
   # Docker build & deploy
   # Draait op de host (self-hosted) zodat Docker
diff --git a/.husky/pre-commit b/.husky/pre-commit
deleted file mode 100755
index 5ee7abd8..00000000
--- a/.husky/pre-commit
+++ /dev/null
@@ -1 +0,0 @@
-pnpm exec lint-staged
diff --git a/.husky/pre-push b/.husky/pre-push
deleted file mode 100755
index 42267220..00000000
--- a/.husky/pre-push
+++ /dev/null
@@ -1,2 +0,0 @@
-pnpm typecheck
-pnpm test
diff --git a/docs/operations/cms-error-center.md b/docs/operations/cms-error-center.md
index 71d086d2..6b3458aa 100644
--- a/docs/operations/cms-error-center.md
+++ b/docs/operations/cms-error-center.md
@@ -44,7 +44,7 @@ stack is diagnostic evidence, not an automatic root-cause determination.
 ## Dependencies
 
 `pnpm install --frozen-lockfile`, `pnpm deps:audit`, `pnpm typecheck`, `pnpm test`,
-`pnpm knip`, `pnpm biome:lint`, and `pnpm build` are the verification sequence.
+`pnpm biome:lint`, and `pnpm build` are the verification sequence.
 `pnpm analyze --output` writes a Next.js bundle analysis (not an application build).
 
 TinyMCE 8.9 is pinned in pnpm. `pnpm assets:editor` copies its runtime files and
diff --git a/knip.json b/knip.json
deleted file mode 100644
index 2b614c66..00000000
--- a/knip.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
-	"$schema": "https://unpkg.com/knip@6/schema.json",
-	"entry": [
-		"src/app/**/page.{ts,tsx}",
-		"src/app/**/layout.{ts,tsx}",
-		"src/app/**/route.{ts,tsx}",
-		"src/app/**/{error,not-found,loading,template,default,global-error}.{ts,tsx}",
-		"scripts/migrate-aes-cbc-to-gcm.ts",
-		"scripts/build-languages-full.ts",
-		"scripts/translate-furnidata-full.ts",
-		"scripts/align-db-ids-with-furnidata.ts",
-		"scripts/furni-diagnose-now.ts"
-	],
-	"project": ["src/**/*.{ts,tsx,css}", "scripts/**/*.{ts,js}"],
-	"ignoreDependencies": ["pino-pretty"],
-	"ignoreBinaries": ["sendmail"]
-}
diff --git a/package.json b/package.json
index 3166b4ce..ed918b4e 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,6 @@
 		"lint": "biome check .",
 		"biome:lint": "biome check .",
 		"format": "biome format --write .",
-		"knip": "knip --include files,dependencies,devDependencies,unlisted,binaries",
 		"diag:permissions": "tsx scripts/diagnose-permission-page.ts",
 		"jobs:worker": "node --conditions=react-server --import tsx scripts/jobs-worker.ts",
 		"test": "vitest run",
@@ -31,17 +30,12 @@
 		"gamedata:compress": "node scripts/compress-gamedata.mjs",
 		"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts",
 		"test:housekeeping": "vitest run --coverage.enabled=false src/features/housekeeping src/lib/admin-theme-source-audit.test.ts src/lib/admin/authorization-contract.test.ts",
-		"prepare": "node scripts/prepare-hooks.mjs",
 		"assets:editor": "node scripts/copy-editor-assets.mjs",
 		"deps:audit": "pnpm audit --audit-level=high",
 		"analyze": "next experimental-analyze",
 		"i18n:check": "node scripts/audit-cms-translations.mjs --check",
 		"i18n:audit": "node scripts/audit-cms-translations.mjs"
 	},
-	"lint-staged": {
-		"*.{js,ts,jsx,tsx,json}": "biome check --write --no-errors-on-unmatched",
-		"*.{ts,tsx}": "node scripts/check-admin-colors.mjs"
-	},
 	"dependencies": {
 		"@base-ui/react": "1.8.0",
 		"@dnd-kit/core": "6.3.1",
@@ -93,9 +87,6 @@
 		"@types/react-dom": "19.2.7",
 		"@vitest/coverage-v8": "5.0.0",
 		"drizzle-kit": "0.31.10",
-		"husky": "9.1.7",
-		"knip": "6.34.0",
-		"lint-staged": "17.4.1",
 		"pino-pretty": "13.1.3",
 		"postcss": "8.5.28",
 		"tailwindcss": "4.3.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 322e4f7c..b16e52e7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -158,15 +158,6 @@ importers:
       drizzle-kit:
         specifier: 0.31.10
         version: 0.31.10
-      husky:
-        specifier: 9.1.7
-        version: 9.1.7
-      knip:
-        specifier: 6.34.0
-        version: 6.34.0
-      lint-staged:
-        specifier: 17.4.1
-        version: 17.4.1
       pino-pretty:
         specifier: 13.1.3
         version: 13.1.3
@@ -400,18 +391,9 @@ packages:
   '@eloqnt/format-po@0.1.0':
     resolution: {integrity: sha512-odgtuICWs67GdkNqY51/d2vaIukuOdlGalKASUGkI5c4cyWOmv/WIVyk5gZyn3xr614RzwWp57s9WB40lRLlBw==}
 
-  '@emnapi/core@1.11.2':
-    resolution: {integrity: sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==}
-
-  '@emnapi/runtime@1.11.2':
-    resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==}
-
   '@emnapi/runtime@1.11.3':
     resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==}
 
-  '@emnapi/wasi-threads@1.2.2':
-    resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==}
-
   '@esbuild-kit/core-utils@3.3.2':
     resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
     deprecated: 'Merged into tsx: https://tsx.hirok.io'
@@ -1027,13 +1009,6 @@ packages:
   '@jridgewell/trace-mapping@0.3.31':
     resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
 
-  '@napi-rs/wasm-runtime@1.2.3':
-    resolution: {integrity: sha512-UMduMbqO5s5zF2NkNacMT/yK5Y5QiKvWr2+50bzIIxFDwVJ2h49b+oyjaCGPhJxd2/gC2x39EHv/gHVuu36x2Q==}
-    engines: {node: ^20.19.0 || ^22.13.0 || >=23.5.0}
-    peerDependencies:
-      '@emnapi/core': ^1.7.1 || ^2.0.0-alpha.4
-      '@emnapi/runtime': ^1.7.1 || ^2.0.0-alpha.4
-
   '@next/env@16.3.4':
     resolution: {integrity: sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q==}
 
@@ -1111,237 +1086,9 @@ packages:
   '@otplib/uri@13.5.0':
     resolution: {integrity: sha512-LsL1hqTEgJHY40U2eb/Qp6OR1tKkNGwHQTrqqjIuJj0cawGWkLEmeUFK1MHzWBQ4bgy5IdeB5Et7gIDSAH/CRw==}
 
-  '@oxc-parser/binding-android-arm-eabi@0.147.0':
-    resolution: {integrity: sha512-fOtoGvIoirkvxQVw9J1WJPxz571XPgLsPf9uhRD+PJteUnvrJHMDmK9pw2yZEGGyismtRoEsp+JcXUdF/JDMDw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm]
-    os: [android]
-
-  '@oxc-parser/binding-android-arm64@0.147.0':
-    resolution: {integrity: sha512-emjQHOYJaomo4ykaXQ1EItunr/I94Nk01oqBmU4dSkKSTupIDx6OysVDf2e8Eytm77rb+4ZxzgElyWP7rcEX7A==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [android]
-
-  '@oxc-parser/binding-darwin-arm64@0.147.0':
-    resolution: {integrity: sha512-kXvBPJL7RmDPJ2mze/vXPPVQimCDtFr9OFLjf7dyhV5Dx64cgcXh9KKrA1sMWvCObvJll9CZZUO0FBlFwD0l6A==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [darwin]
-
-  '@oxc-parser/binding-darwin-x64@0.147.0':
-    resolution: {integrity: sha512-mgFF8pLU6R64LbT27lSrtVRspVC/3IcZ0qyIikzmi78Y3Ik2OPnlAHHI0UEBRcC3qmNgtjaef7zkFt7/uPxIcw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [x64]
-    os: [darwin]
-
-  '@oxc-parser/binding-freebsd-x64@0.147.0':
-    resolution: {integrity: sha512-v38aiF11qufOTBcCAKL4skgQf0zJ4NEvRlivq7B5kHrlyvjCLjvNrMtNWDTz1SDUL6/xVsJRmLDxv2e+Cp4oWw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [x64]
-    os: [freebsd]
-
-  '@oxc-parser/binding-linux-arm-gnueabihf@0.147.0':
-    resolution: {integrity: sha512-AeIiBbwUaP0H1+4/qGW9l5qHecS/+XA5iMuieVcGb1T+tyc2dVGspFW13BWk/XrLsiGP/CiDJTJqAPLLCzZHkw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm]
-    os: [linux]
-
-  '@oxc-parser/binding-linux-arm-musleabihf@0.147.0':
-    resolution: {integrity: sha512-/41MKPW4RgPY4DJco0NCF0RYX3IMZaVlRNMNzvhaxRavc7tN3Txm+qllZbh0aMRs0VHdgUlbI8TcAOiTai4TKg==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm]
-    os: [linux]
-
-  '@oxc-parser/binding-linux-arm64-gnu@0.147.0':
-    resolution: {integrity: sha512-bmpw/RPhVXgZbtb3xBDuwW5s8+LvZYdqcDSX/sP2ltL77aTio3DP/B5ZTwwgoJ6Mr9vJs4RrmgEKW9XkLNUU1g==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-parser/binding-linux-arm64-musl@0.147.0':
-    resolution: {integrity: sha512-gd7VX/FDVOw6mjQcu45iIcp4QkgybgJwh3a0OFG2NxmPCj628mQWD96QGu1kK8+mZF9qK4b/gIEyC63vQoB7+Q==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-parser/binding-linux-ppc64-gnu@0.147.0':
-    resolution: {integrity: sha512-HnAzcfki7dSUNHf510Q2NmbJlz8Ys7rn8l9l588Pkx0tYe1BHLZnmELIgqizJ4WPhHGSwN8Ce+B/menVxS3odA==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [ppc64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-parser/binding-linux-riscv64-gnu@0.147.0':
-    resolution: {integrity: sha512-qlkOL6wT44U+fT5s/+sR6Shx0OdwvQF83JyIPZUxG/ovqZF5/7atOtjH+JPZ5/7ATQLbFBBSmghcy/+2NVB/ew==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [riscv64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-parser/binding-linux-riscv64-musl@0.147.0':
-    resolution: {integrity: sha512-DlefD7L7sMXs/3hIBH23Egk0phj8kG0SA81dVGOQ3S1ekjOlmTLH2E+F2Thwfh1slKx6aH+lNc5fQYAw0GU7/g==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [riscv64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-parser/binding-linux-s390x-gnu@0.147.0':
-    resolution: {integrity: sha512-Xqpagk/031IvZ4svrk2FF01YEqM/iN3MJV3SVZadKg/CsGlDCGoREqKHXYnoV5+8SfGe/m6RM1szXFLusTu/Uw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [s390x]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-parser/binding-linux-x64-gnu@0.147.0':
-    resolution: {integrity: sha512-QioQOeUbI4ATUr0S2z88uA3Cds2R3Mm5Ge7U8XNYtlTb2GJF3rWlcj70z0AJhhOlbdm0YgVjqPBldUNbFylDIg==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [x64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-parser/binding-linux-x64-musl@0.147.0':
-    resolution: {integrity: sha512-NXy1tv/OdC+pPTwf9RiCZWPK53V/Xq/2cjSnjOSyKopajdaDqMIkgtDY+jXZemp2e8px5FeWfY2L2LwhKZQovg==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [x64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-parser/binding-openharmony-arm64@0.147.0':
-    resolution: {integrity: sha512-GpGWZ6oKz4bjCWW9Mz5pCaGPyk2Aaze6zEoaslIQqpSLtpx5pXj/ap5gUNb5Jn2LIbqWyjkGLX9yv3NMuNcBVQ==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [openharmony]
-
-  '@oxc-parser/binding-win32-arm64-msvc@0.147.0':
-    resolution: {integrity: sha512-a8mlt7CC8z7LUdCfaxhff4kCd+vSjE+NEFL0cxA8ukfuSnvAto/pWTjytW4BuVLnQGcCVdHJwcRKOfs++H+tjw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [arm64]
-    os: [win32]
-
-  '@oxc-parser/binding-win32-ia32-msvc@0.147.0':
-    resolution: {integrity: sha512-M5ViVDBcFLnl2632AuuWuP35zEL5oikK1jTx8r3+902VEDeSNHxFZAB6RZyfZ7MU6Oi5QTOG8MmMkIeHsudSaw==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [ia32]
-    os: [win32]
-
-  '@oxc-parser/binding-win32-x64-msvc@0.147.0':
-    resolution: {integrity: sha512-DUaE13OwnUSlHpLZNcC/nuT10ivlWqc5EZgsfgXuAmWYw0r3nDxGeLD1zlGwYwIgVk1/ZMAxoXpV+05stvbHaA==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    cpu: [x64]
-    os: [win32]
-
-  '@oxc-project/types@0.147.0':
-    resolution: {integrity: sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==}
-
   '@oxc-project/types@0.148.0':
     resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==}
 
-  '@oxc-resolver/binding-android-arm-eabi@11.24.2':
-    resolution: {integrity: sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==}
-    cpu: [arm]
-    os: [android]
-
-  '@oxc-resolver/binding-android-arm64@11.24.2':
-    resolution: {integrity: sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==}
-    cpu: [arm64]
-    os: [android]
-
-  '@oxc-resolver/binding-darwin-arm64@11.24.2':
-    resolution: {integrity: sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==}
-    cpu: [arm64]
-    os: [darwin]
-
-  '@oxc-resolver/binding-darwin-x64@11.24.2':
-    resolution: {integrity: sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==}
-    cpu: [x64]
-    os: [darwin]
-
-  '@oxc-resolver/binding-freebsd-x64@11.24.2':
-    resolution: {integrity: sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==}
-    cpu: [x64]
-    os: [freebsd]
-
-  '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2':
-    resolution: {integrity: sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==}
-    cpu: [arm]
-    os: [linux]
-
-  '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2':
-    resolution: {integrity: sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==}
-    cpu: [arm]
-    os: [linux]
-
-  '@oxc-resolver/binding-linux-arm64-gnu@11.24.2':
-    resolution: {integrity: sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==}
-    cpu: [arm64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-resolver/binding-linux-arm64-musl@11.24.2':
-    resolution: {integrity: sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==}
-    cpu: [arm64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2':
-    resolution: {integrity: sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==}
-    cpu: [ppc64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2':
-    resolution: {integrity: sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==}
-    cpu: [riscv64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-resolver/binding-linux-riscv64-musl@11.24.2':
-    resolution: {integrity: sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==}
-    cpu: [riscv64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-resolver/binding-linux-s390x-gnu@11.24.2':
-    resolution: {integrity: sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==}
-    cpu: [s390x]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-resolver/binding-linux-x64-gnu@11.24.2':
-    resolution: {integrity: sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==}
-    cpu: [x64]
-    os: [linux]
-    libc: [glibc]
-
-  '@oxc-resolver/binding-linux-x64-musl@11.24.2':
-    resolution: {integrity: sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==}
-    cpu: [x64]
-    os: [linux]
-    libc: [musl]
-
-  '@oxc-resolver/binding-openharmony-arm64@11.24.2':
-    resolution: {integrity: sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==}
-    cpu: [arm64]
-    os: [openharmony]
-
-  '@oxc-resolver/binding-wasm32-wasi@11.24.2':
-    resolution: {integrity: sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==}
-    engines: {node: '>=14.0.0'}
-    cpu: [wasm32]
-
-  '@oxc-resolver/binding-win32-arm64-msvc@11.24.2':
-    resolution: {integrity: sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==}
-    cpu: [arm64]
-    os: [win32]
-
-  '@oxc-resolver/binding-win32-x64-msvc@11.24.2':
-    resolution: {integrity: sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==}
-    cpu: [x64]
-    os: [win32]
-
   '@panva/hkdf@1.2.1':
     resolution: {integrity: sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==}
 
@@ -1917,9 +1664,6 @@ packages:
   '@tokenizer/token@0.3.0':
     resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
 
-  '@tybys/wasm-util@0.10.3':
-    resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==}
-
   '@types/chai@5.2.3':
     resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
 
@@ -2346,9 +2090,6 @@ packages:
   fast-sha256@1.3.0:
     resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
 
-  fd-package-json@2.0.0:
-    resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==}
-
   fdir@6.5.0:
     resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
     engines: {node: '>=12.0.0'}
@@ -2362,11 +2103,6 @@ packages:
     resolution: {integrity: sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==}
     engines: {node: '>=20'}
 
-  formatly@0.7.0:
-    resolution: {integrity: sha512-7CXJtIIA0zy/u12StsYk25qVKxvdLA2ep2sTNxK3ov0mGNIIDqIvAXDSgTnAfDJFsPfWjuz0WjfYSdpvnLA5Tg==}
-    engines: {node: '>=18.3.0'}
-    hasBin: true
-
   framer-motion@13.2.0:
     resolution: {integrity: sha512-9E33ebgMaO33w1nN/jEdW8z3/GO483fMi4rqbMG9rt83XgW9QLKRe4NcmJ8s+fQ3O34++UHrIQwlIWGIWTITjA==}
     peerDependencies:
@@ -2406,11 +2142,6 @@ packages:
     resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
     engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
 
-  husky@9.1.7:
-    resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
-    engines: {node: '>=18'}
-    hasBin: true
-
   iconv-lite@0.7.3:
     resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==}
     engines: {node: '>=0.10.0'}
@@ -2487,11 +2218,6 @@ packages:
   jszip@3.10.1:
     resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==}
 
-  knip@6.34.0:
-    resolution: {integrity: sha512-bbHIrnGspYwe4EBPjjx+lvkUor0F2qfKQc5BPzPI4SOAImYA+k2ueVpIcF7d/W1LEVT7XoJUPt6zELeGZhXBgA==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-    hasBin: true
-
   lie@3.3.0:
     resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==}
 
@@ -2643,11 +2369,6 @@ packages:
     resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==}
     engines: {node: '>= 12.0.0'}
 
-  lint-staged@17.4.1:
-    resolution: {integrity: sha512-FmJeudcalbSfg1du+JCfvi5vS6Qt08KgbfLWiHinbef+2JJwUZwAWVoaO1AcJVUTWPfk0t30PMQNwPAeCzYQ+Q==}
-    engines: {node: '>=22.22.1'}
-    hasBin: true
-
   long@5.3.2:
     resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
 
@@ -2800,16 +2521,6 @@ packages:
   otplib@13.5.0:
     resolution: {integrity: sha512-RpcC6aq4rANX6MverMuU3pqHVLgMPO7vl6qR8ga7YzoEHlPJknJ58cSVAaf3jBYnasiQVQHXXl2mj/4pYlsVEQ==}
 
-  oxc-parser@0.147.0:
-    resolution: {integrity: sha512-5xaug6t7GfV3BO5Iv+xHW1rmQkDEQ3BEu3L8g3InsvWO5i8CYGc4tCZ2X985QcwWNycFJam+aOns6Nr2XAThTA==}
-    engines: {node: ^20.19.0 || >=22.12.0}
-
-  oxc-resolver@11.24.2:
-    resolution: {integrity: sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==}
-
-  package-manager-detector@1.8.0:
-    resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==}
-
   pako@1.0.11:
     resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
 
@@ -3005,10 +2716,6 @@ packages:
   siginfo@2.0.0:
     resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
 
-  smol-toml@1.8.0:
-    resolution: {integrity: sha512-kCZr2V3ch9i00x8zXRhjUNVcjG9ijES5dDudkXvUVCT5QlJNQWElSJdZqyPemffHoLNUYwOcou0Fy+ojN0uHSQ==}
-    engines: {node: '>= 18'}
-
   sonic-boom@4.2.1:
     resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
 
@@ -3053,10 +2760,6 @@ packages:
   std-env@4.2.0:
     resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==}
 
-  string-argv@0.3.2:
-    resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
-    engines: {node: '>=0.6.19'}
-
   string_decoder@1.1.1:
     resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
 
@@ -3106,10 +2809,6 @@ packages:
     resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==}
     engines: {node: '>=18'}
 
-  tinyexec@1.3.1:
-    resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==}
-    engines: {node: '>=18'}
-
   tinyglobby@0.2.17:
     resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
     engines: {node: '>=12.0.0'}
@@ -3157,10 +2856,6 @@ packages:
     resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
     engines: {node: '>=18'}
 
-  unbash@4.0.11:
-    resolution: {integrity: sha512-FoSOKV7NEofQSkAefMVHam4ZPKYMxjAydxiV72UFEDNV/YofxjGfiZ2A9pZjdL/lRJzTjcu4PABo1JYJX8N5iQ==}
-    engines: {node: '>=14'}
-
   undici-types@8.3.0:
     resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==}
 
@@ -3289,10 +2984,6 @@ packages:
     resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
     engines: {node: '>=18'}
 
-  walk-up-path@4.0.0:
-    resolution: {integrity: sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==}
-    engines: {node: 20 || >=22}
-
   webidl-conversions@8.0.1:
     resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==}
     engines: {node: '>=20'}
@@ -3505,27 +3196,11 @@ snapshots:
       '@eloqnt/config': 0.1.0
       po-parser: 2.2.0
 
-  '@emnapi/core@1.11.2':
-    dependencies:
-      '@emnapi/wasi-threads': 1.2.2
-      tslib: 2.8.1
-    optional: true
-
-  '@emnapi/runtime@1.11.2':
-    dependencies:
-      tslib: 2.8.1
-    optional: true
-
   '@emnapi/runtime@1.11.3':
     dependencies:
       tslib: 2.8.1
     optional: true
 
-  '@emnapi/wasi-threads@1.2.2':
-    dependencies:
-      tslib: 2.8.1
-    optional: true
-
   '@esbuild-kit/core-utils@3.3.2':
     dependencies:
       esbuild: 0.25.12
@@ -3859,13 +3534,6 @@ snapshots:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.6.0
 
-  '@napi-rs/wasm-runtime@1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)':
-    dependencies:
-      '@emnapi/core': 1.11.2
-      '@emnapi/runtime': 1.11.2
-      '@tybys/wasm-util': 0.10.3
-    optional: true
-
   '@next/env@16.3.4': {}
 
   '@next/swc-darwin-arm64@16.3.4':
@@ -3921,128 +3589,8 @@ snapshots:
     dependencies:
       '@otplib/core': 13.5.0
 
-  '@oxc-parser/binding-android-arm-eabi@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-android-arm64@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-darwin-arm64@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-darwin-x64@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-freebsd-x64@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-arm-gnueabihf@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-arm-musleabihf@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-arm64-gnu@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-arm64-musl@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-ppc64-gnu@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-riscv64-gnu@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-riscv64-musl@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-s390x-gnu@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-x64-gnu@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-linux-x64-musl@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-openharmony-arm64@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-win32-arm64-msvc@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-win32-ia32-msvc@0.147.0':
-    optional: true
-
-  '@oxc-parser/binding-win32-x64-msvc@0.147.0':
-    optional: true
-
-  '@oxc-project/types@0.147.0': {}
-
   '@oxc-project/types@0.148.0': {}
 
-  '@oxc-resolver/binding-android-arm-eabi@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-android-arm64@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-darwin-arm64@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-darwin-x64@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-freebsd-x64@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-arm-gnueabihf@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-arm-musleabihf@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-arm64-gnu@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-arm64-musl@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-ppc64-gnu@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-riscv64-gnu@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-riscv64-musl@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-s390x-gnu@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-x64-gnu@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-linux-x64-musl@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-openharmony-arm64@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-wasm32-wasi@11.24.2':
-    dependencies:
-      '@emnapi/core': 1.11.2
-      '@emnapi/runtime': 1.11.2
-      '@napi-rs/wasm-runtime': 1.2.3(@emnapi/core@1.11.2)(@emnapi/runtime@1.11.2)
-    optional: true
-
-  '@oxc-resolver/binding-win32-arm64-msvc@11.24.2':
-    optional: true
-
-  '@oxc-resolver/binding-win32-x64-msvc@11.24.2':
-    optional: true
-
   '@panva/hkdf@1.2.1': {}
 
   '@parcel/watcher-android-arm64@2.6.0':
@@ -4456,11 +4004,6 @@ snapshots:
 
   '@tokenizer/token@0.3.0': {}
 
-  '@tybys/wasm-util@0.10.3':
-    dependencies:
-      tslib: 2.8.1
-    optional: true
-
   '@types/chai@5.2.3':
     dependencies:
       '@types/deep-eql': 4.0.2
@@ -4760,10 +4303,6 @@ snapshots:
 
   fast-sha256@1.3.0: {}
 
-  fd-package-json@2.0.0:
-    dependencies:
-      walk-up-path: 4.0.0
-
   fdir@6.5.0(picomatch@4.0.7):
     optionalDependencies:
       picomatch: 4.0.7
@@ -4777,11 +4316,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color
 
-  formatly@0.7.0:
-    dependencies:
-      fd-package-json: 2.0.0
-      package-manager-detector: 1.8.0
-
   framer-motion@13.2.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
     dependencies:
       motion-dom: 13.2.0
@@ -4816,8 +4350,6 @@ snapshots:
     transitivePeerDependencies:
       - '@noble/hashes'
 
-  husky@9.1.7: {}
-
   iconv-lite@0.7.3:
     dependencies:
       safer-buffer: 2.1.2
@@ -4913,22 +4445,6 @@ snapshots:
       readable-stream: 2.3.8
       setimmediate: 1.0.5
 
-  knip@6.34.0:
-    dependencies:
-      fdir: 6.5.0(picomatch@4.0.7)
-      formatly: 0.7.0
-      get-tsconfig: 4.14.3
-      jiti: 2.7.0
-      oxc-parser: 0.147.0
-      oxc-resolver: 11.24.2
-      picomatch: 4.0.7
-      smol-toml: 1.8.0
-      strip-json-comments: 5.0.3
-      tinyglobby: 0.2.17
-      unbash: 4.0.11
-      yaml: 2.9.0
-      zod: 4.5.4
-
   lie@3.3.0:
     dependencies:
       immediate: 3.0.6
@@ -5031,14 +4547,6 @@ snapshots:
       lightningcss-win32-arm64-msvc: 1.33.0
       lightningcss-win32-x64-msvc: 1.33.0
 
-  lint-staged@17.4.1:
-    dependencies:
-      picomatch: 4.0.7
-      string-argv: 0.3.2
-      tinyexec: 1.3.1
-    optionalDependencies:
-      yaml: 2.9.0
-
   long@5.3.2: {}
 
   lru-cache@11.5.2: {}
@@ -5196,54 +4704,6 @@ snapshots:
       '@otplib/totp': 13.5.0
       '@otplib/uri': 13.5.0
 
-  oxc-parser@0.147.0:
-    dependencies:
-      '@oxc-project/types': 0.147.0
-    optionalDependencies:
-      '@oxc-parser/binding-android-arm-eabi': 0.147.0
-      '@oxc-parser/binding-android-arm64': 0.147.0
-      '@oxc-parser/binding-darwin-arm64': 0.147.0
-      '@oxc-parser/binding-darwin-x64': 0.147.0
-      '@oxc-parser/binding-freebsd-x64': 0.147.0
-      '@oxc-parser/binding-linux-arm-gnueabihf': 0.147.0
-      '@oxc-parser/binding-linux-arm-musleabihf': 0.147.0
-      '@oxc-parser/binding-linux-arm64-gnu': 0.147.0
-      '@oxc-parser/binding-linux-arm64-musl': 0.147.0
-      '@oxc-parser/binding-linux-ppc64-gnu': 0.147.0
-      '@oxc-parser/binding-linux-riscv64-gnu': 0.147.0
-      '@oxc-parser/binding-linux-riscv64-musl': 0.147.0
-      '@oxc-parser/binding-linux-s390x-gnu': 0.147.0
-      '@oxc-parser/binding-linux-x64-gnu': 0.147.0
-      '@oxc-parser/binding-linux-x64-musl': 0.147.0
-      '@oxc-parser/binding-openharmony-arm64': 0.147.0
-      '@oxc-parser/binding-win32-arm64-msvc': 0.147.0
-      '@oxc-parser/binding-win32-ia32-msvc': 0.147.0
-      '@oxc-parser/binding-win32-x64-msvc': 0.147.0
-
-  oxc-resolver@11.24.2:
-    optionalDependencies:
-      '@oxc-resolver/binding-android-arm-eabi': 11.24.2
-      '@oxc-resolver/binding-android-arm64': 11.24.2
-      '@oxc-resolver/binding-darwin-arm64': 11.24.2
-      '@oxc-resolver/binding-darwin-x64': 11.24.2
-      '@oxc-resolver/binding-freebsd-x64': 11.24.2
-      '@oxc-resolver/binding-linux-arm-gnueabihf': 11.24.2
-      '@oxc-resolver/binding-linux-arm-musleabihf': 11.24.2
-      '@oxc-resolver/binding-linux-arm64-gnu': 11.24.2
-      '@oxc-resolver/binding-linux-arm64-musl': 11.24.2
-      '@oxc-resolver/binding-linux-ppc64-gnu': 11.24.2
-      '@oxc-resolver/binding-linux-riscv64-gnu': 11.24.2
-      '@oxc-resolver/binding-linux-riscv64-musl': 11.24.2
-      '@oxc-resolver/binding-linux-s390x-gnu': 11.24.2
-      '@oxc-resolver/binding-linux-x64-gnu': 11.24.2
-      '@oxc-resolver/binding-linux-x64-musl': 11.24.2
-      '@oxc-resolver/binding-openharmony-arm64': 11.24.2
-      '@oxc-resolver/binding-wasm32-wasi': 11.24.2
-      '@oxc-resolver/binding-win32-arm64-msvc': 11.24.2
-      '@oxc-resolver/binding-win32-x64-msvc': 11.24.2
-
-  package-manager-detector@1.8.0: {}
-
   pako@1.0.11: {}
 
   parse5@8.0.1:
@@ -5471,8 +4931,6 @@ snapshots:
 
   siginfo@2.0.0: {}
 
-  smol-toml@1.8.0: {}
-
   sonic-boom@4.2.1:
     dependencies:
       atomic-sleep: 1.0.0
@@ -5508,8 +4966,6 @@ snapshots:
 
   std-env@4.2.0: {}
 
-  string-argv@0.3.2: {}
-
   string_decoder@1.1.1:
     dependencies:
       safe-buffer: 5.1.2
@@ -5541,8 +4997,6 @@ snapshots:
 
   tinyexec@1.3.0: {}
 
-  tinyexec@1.3.1: {}
-
   tinyglobby@0.2.17:
     dependencies:
       fdir: 6.5.0(picomatch@4.0.7)
@@ -5605,8 +5059,6 @@ snapshots:
 
   uint8array-extras@1.5.0: {}
 
-  unbash@4.0.11: {}
-
   undici-types@8.3.0: {}
 
   undici@8.10.2: {}
@@ -5682,8 +5134,6 @@ snapshots:
     dependencies:
       xml-name-validator: 5.0.0
 
-  walk-up-path@4.0.0: {}
-
   webidl-conversions@8.0.1: {}
 
   whatwg-mimetype@5.0.0: {}
@@ -5717,6 +5167,7 @@ snapshots:
 
   xmlchars@2.2.0: {}
 
-  yaml@2.9.0: {}
+  yaml@2.9.0:
+    optional: true
 
   zod@4.5.4: {}
diff --git a/scripts/prepare-hooks.mjs b/scripts/prepare-hooks.mjs
deleted file mode 100644
index 8a7856c5..00000000
--- a/scripts/prepare-hooks.mjs
+++ /dev/null
@@ -1,5 +0,0 @@
-// Production builds and CI do not need Git hooks or dev-only executables.
-if (process.env.NODE_ENV !== "production" && !process.env.CI) {
-	const { default: husky } = await import("husky");
-	husky();
-}

From 54b7c67878d602c800c2a8ef3b790db4cb59ffc1 Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 18:12:34 +0200
Subject: [PATCH 06/10] perf: speed up local test runs by moving coverage to
 optional script

Run vitest without coverage by default (pnpm test) and add pnpm test:coverage which enforces the coverage thresholds. CI keeps using the coverage run so thresholds are still enforced on every push.
---
 .gitea/workflows/ci.yaml                 | 4 ++--
 .gitea/workflows/container.yaml          | 2 +-
 package.json                             | 3 ++-
 src/lib/ci-workflow-contract.test.ts     | 2 +-
 src/lib/deploy-workflow-contract.test.ts | 2 +-
 5 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index 7af40b5d..f363a383 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -53,9 +53,9 @@ jobs:
           BCRYPT_ROUNDS: 4
         run: |
           if [ -x /usr/bin/time ]; then
-            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %M KiB' pnpm test --maxWorkers=2
+            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %i KiB' pnpm test:coverage --maxWorkers=2
           else
-            time pnpm test --maxWorkers=2
+            time pnpm test:coverage --maxWorkers=2
           fi
 
   # ─────────────────────────────────────────────
diff --git a/.gitea/workflows/container.yaml b/.gitea/workflows/container.yaml
index e35cbcf7..771d95e6 100644
--- a/.gitea/workflows/container.yaml
+++ b/.gitea/workflows/container.yaml
@@ -18,7 +18,7 @@ jobs:
           pnpm install --frozen-lockfile
           pnpm typecheck
           pnpm biome:lint
-          pnpm test
+          pnpm test:coverage
         env:
           SKIP_ENV_VALIDATION: 1
           NODE_ENV: test
diff --git a/package.json b/package.json
index ed918b4e..5515aea6 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,8 @@
 		"format": "biome format --write .",
 		"diag:permissions": "tsx scripts/diagnose-permission-page.ts",
 		"jobs:worker": "node --conditions=react-server --import tsx scripts/jobs-worker.ts",
-		"test": "vitest run",
+		"test": "vitest run --coverage.enabled=false",
+		"test:coverage": "vitest run",
 		"typecheck": "tsc --noEmit",
 		"db:generate": "drizzle-kit generate",
 		"db:introspect": "drizzle-kit introspect",
diff --git a/src/lib/ci-workflow-contract.test.ts b/src/lib/ci-workflow-contract.test.ts
index 750cfb39..bd6b6c40 100644
--- a/src/lib/ci-workflow-contract.test.ts
+++ b/src/lib/ci-workflow-contract.test.ts
@@ -25,7 +25,7 @@ describe("CI workflow", () => {
 	it("check runs lint, typecheck, and test", () => {
 		expect(workflow).toContain("pnpm biome:lint");
 		expect(workflow).toContain("pnpm typecheck");
-		expect(workflow).toContain("pnpm test");
+		expect(workflow).toContain("pnpm test:coverage");
 	});
 
 	it("check job runs on self-hosted runner", () => {
diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts
index cf76594d..2fbba0e9 100644
--- a/src/lib/deploy-workflow-contract.test.ts
+++ b/src/lib/deploy-workflow-contract.test.ts
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
 const workflow = readFileSync(".gitea/workflows/ci.yaml", "utf8");
 const deploy = readFileSync("scripts/ci-deploy.sh", "utf8");
 it("uses two test workers and runs smoke checks in the deployment transaction", () => {
-	expect(workflow).toContain("pnpm test --maxWorkers=2");
+	expect(workflow).toContain("pnpm test:coverage --maxWorkers=2");
 	expect(workflow).not.toContain("\n  e2e:");
 	expect(workflow).toContain("bash scripts/ci-deploy.sh");
 	expect(deploy).toContain(

From cbffb565ec47724323729f8e2307ef82b8a92fc1 Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 18:22:49 +0200
Subject: [PATCH 07/10] perf: cut biome lint from 25s to 1s and fix
 pre-existing lint failures

Exclude generated drizzle-kit snapshot artifacts from formatting checks (drizzle/drafts/meta), which made biome scan a 360KB generated JSON for 23s. Fix the pre-existing lint errors in error-monitor, article-form and the admin-search-permissions mock so pnpm biome:lint is green in CI.
---
 biome.json                               |  2 +-
 src/components/admin/article-form.tsx    |  2 +-
 src/lib/admin-search-permissions.test.ts | 12 +++++++-----
 src/lib/error-monitor.ts                 |  4 ++--
 4 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/biome.json b/biome.json
index 51fa8719..b90c07c8 100644
--- a/biome.json
+++ b/biome.json
@@ -7,7 +7,7 @@
 	},
 	"files": {
 		"ignoreUnknown": false,
-		"includes": ["**", "!setup", "!*.cjs", "!coverage"]
+		"includes": ["**", "!setup", "!*.cjs", "!coverage", "!drizzle/drafts/meta"]
 	},
 	"formatter": {
 		"enabled": true,
diff --git a/src/components/admin/article-form.tsx b/src/components/admin/article-form.tsx
index 949b977a..44b03645 100644
--- a/src/components/admin/article-form.tsx
+++ b/src/components/admin/article-form.tsx
@@ -14,7 +14,7 @@ export function ArticleForm({
 	action,
 	defaultValues,
 }: {
-	action: (formData: FormData) => Promise;
+	action: (formData: FormData) => Promise;
 	edit?: boolean;
 	defaultValues?: {
 		title?: string;
diff --git a/src/lib/admin-search-permissions.test.ts b/src/lib/admin-search-permissions.test.ts
index 4bd53700..52b16131 100644
--- a/src/lib/admin-search-permissions.test.ts
+++ b/src/lib/admin-search-permissions.test.ts
@@ -5,11 +5,13 @@ const state = vi.hoisted(() => ({
 	queried: [] as unknown[],
 }));
 vi.mock("@/lib/api-handler", () => ({
-	withAdmin: (_: unknown, handler: Function) => (request: unknown) =>
-		handler(request, {
-			session: { user: { id: 1, rank: 7 } },
-			permissions: { isSuperAdmin: false },
-		}),
+	withAdmin:
+		(_: unknown, handler: (...args: unknown[]) => unknown) =>
+		(request: unknown) =>
+			handler(request, {
+				session: { user: { id: 1, rank: 7 } },
+				permissions: { isSuperAdmin: false },
+			}),
 }));
 vi.mock("@/lib/rate-limit", () => ({ rateLimit: async () => ({ ok: true }) }));
 vi.mock("@/lib/permissions", async () => ({
diff --git a/src/lib/error-monitor.ts b/src/lib/error-monitor.ts
index 6a4dc7c2..e35f2da4 100644
--- a/src/lib/error-monitor.ts
+++ b/src/lib/error-monitor.ts
@@ -115,12 +115,12 @@ export class ErrorStore {
 				name.slice(0, 10) < cutoff
 			)
 				await unlink(path.join(this.directory, name)).catch(() => {});
-		const file = path.join(this.directory, record.at.slice(0, 10) + ".jsonl");
+		const file = path.join(this.directory, `${record.at.slice(0, 10)}.jsonl`);
 		const size = await stat(file)
 			.then((s) => s.size)
 			.catch(() => 0);
 		if (size >= DAY_LIMIT) throw new Error("Daily error storage limit reached");
-		await appendFile(file, JSON.stringify(record) + "\n", { mode: 0o600 });
+		await appendFile(file, `${JSON.stringify(record)}\n`, { mode: 0o600 });
 	}
 	async resolve(fingerprint: string) {
 		if (!/^[a-f0-9]{16}$/.test(fingerprint))

From 5a790909429cf69935b7d54d870323765ee2228d Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 18:31:40 +0200
Subject: [PATCH 08/10] perf: use all 6 cores for CI test run

Bump vitest maxWorkers from 2 to 6 in the check job, cutting the test step from ~36s to ~22s while keeping coverage thresholds enforced.
---
 .gitea/workflows/ci.yaml                 | 4 ++--
 src/lib/deploy-workflow-contract.test.ts | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index f363a383..534221c9 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -53,9 +53,9 @@ jobs:
           BCRYPT_ROUNDS: 4
         run: |
           if [ -x /usr/bin/time ]; then
-            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %i KiB' pnpm test:coverage --maxWorkers=2
+            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %M KiB' pnpm test:coverage --maxWorkers=6
           else
-            time pnpm test:coverage --maxWorkers=2
+            time pnpm test:coverage --maxWorkers=6
           fi
 
   # ─────────────────────────────────────────────
diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts
index 2fbba0e9..97ab5f7e 100644
--- a/src/lib/deploy-workflow-contract.test.ts
+++ b/src/lib/deploy-workflow-contract.test.ts
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
 const workflow = readFileSync(".gitea/workflows/ci.yaml", "utf8");
 const deploy = readFileSync("scripts/ci-deploy.sh", "utf8");
 it("uses two test workers and runs smoke checks in the deployment transaction", () => {
-	expect(workflow).toContain("pnpm test:coverage --maxWorkers=2");
+	expect(workflow).toContain("pnpm test:coverage --maxWorkers=6");
 	expect(workflow).not.toContain("\n  e2e:");
 	expect(workflow).toContain("bash scripts/ci-deploy.sh");
 	expect(deploy).toContain(

From 8a911f2cf64e9d0a807476fa6ed04ca9cc790040 Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 18:34:08 +0200
Subject: [PATCH 09/10] perf: cap CI test workers at 4 to protect shared
 production host

---
 .gitea/workflows/ci.yaml                 | 4 ++--
 src/lib/deploy-workflow-contract.test.ts | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index 534221c9..0b443dcf 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -53,9 +53,9 @@ jobs:
           BCRYPT_ROUNDS: 4
         run: |
           if [ -x /usr/bin/time ]; then
-            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %M KiB' pnpm test:coverage --maxWorkers=6
+            /usr/bin/time -f 'Tests: %e seconds; peak process RSS: %M KiB' pnpm test:coverage --maxWorkers=4
           else
-            time pnpm test:coverage --maxWorkers=6
+            time pnpm test:coverage --maxWorkers=4
           fi
 
   # ─────────────────────────────────────────────
diff --git a/src/lib/deploy-workflow-contract.test.ts b/src/lib/deploy-workflow-contract.test.ts
index 97ab5f7e..7772744a 100644
--- a/src/lib/deploy-workflow-contract.test.ts
+++ b/src/lib/deploy-workflow-contract.test.ts
@@ -4,7 +4,7 @@ import { expect, it } from "vitest";
 const workflow = readFileSync(".gitea/workflows/ci.yaml", "utf8");
 const deploy = readFileSync("scripts/ci-deploy.sh", "utf8");
 it("uses two test workers and runs smoke checks in the deployment transaction", () => {
-	expect(workflow).toContain("pnpm test:coverage --maxWorkers=6");
+	expect(workflow).toContain("pnpm test:coverage --maxWorkers=4");
 	expect(workflow).not.toContain("\n  e2e:");
 	expect(workflow).toContain("bash scripts/ci-deploy.sh");
 	expect(deploy).toContain(

From b5ea8f1c0ef1b00137e53e5f37696e6bb9127f14 Mon Sep 17 00:00:00 2001
From: openhands 
Date: Tue, 8 Sep 2026 19:25:14 +0200
Subject: [PATCH 10/10] fix(catalog): add SodaStudios source, fix VirtualCity
 furnidata URL, ship curl in runtime

---
 Dockerfile                        |  2 +-
 src/lib/services/clone-sources.ts | 10 +++++++++-
 2 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index 2a9c0d45..dabed1c8 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -47,7 +47,7 @@ ENV NODE_ENV=production \
     NEXT_TELEMETRY_DISABLED=1 \
     PORT=3002 \
     HOSTNAME=0.0.0.0
-RUN apk add --no-cache tini \
+RUN apk add --no-cache tini curl \
     && addgroup -g 33 -S nextjs && adduser -u 33 -S -G nextjs nextjs \
     && mkdir -p /app/storage /app/public/nitro-assets /app/public/swf /var/www/Gamedata \
     && chown -R 33:33 /app/storage /app/public /var/www/Gamedata
diff --git a/src/lib/services/clone-sources.ts b/src/lib/services/clone-sources.ts
index db8d41f2..551382e2 100644
--- a/src/lib/services/clone-sources.ts
+++ b/src/lib/services/clone-sources.ts
@@ -142,12 +142,20 @@ export const DEFAULT_SOURCES: CloneSource[] = [
 	{
 		id: "default-virtualcity",
 		name: "VirtualCity",
-		furnidataUrl: "https://virtualc.nl/gamedata/furnidata_json/1",
+		furnidataUrl: "https://virtualc.nl/gamedata/FurnitureData.json",
 		nitroBaseUrl: "https://virtualc.nl/nitro/bundled/furniture",
 		iconBaseUrl: "https://virtualc.nl/nitro/icons",
 		hotel: "com",
 		sourceSwfBaseUrl: "https://virtualc.nl/dcr",
 	},
+	{
+		id: "default-sodastudios",
+		name: "SodaStudios",
+		furnidataUrl: "https://assets.sodaho.tel/gamedata/FurnitureData.json",
+		nitroBaseUrl: "https://assets.sodaho.tel/bundled/furniture",
+		iconBaseUrl: "https://assets.sodaho.tel/dcr/hof_furni/icons",
+		hotel: "com",
+	},
 	{
 		id: "default-habboon",
 		name: "Habboon",