From 2169fd8b50bd35a7900f76ac40da8e9d0e7f1087 Mon Sep 17 00:00:00 2001 From: 3252a8 <3252a8@proton.me> Date: Wed, 13 May 2026 15:05:30 +0300 Subject: [PATCH] chore: harden webapp auth and update deps --- .dockerignore | 5 + .env.example | 2 + README.md | 8 + bot/app/web/admin_api.py | 19 +- bot/app/web/frontend/src/App.svelte | 7 +- .../src/admin/sections/SettingsSection.svelte | 12 +- .../web/frontend/src/lib/webapp/mockApi.js | 8 +- .../web/frontend/src/lib/webapp/publicApi.js | 10 +- .../web/frontend/src/lib/webapp/session.js | 10 - .../src/lib/webapp/stores/accountStore.js | 4 +- .../src/lib/webapp/stores/authStore.js | 12 +- .../src/lib/webapp/stores/billingStore.js | 2 +- bot/app/web/subscription_webapp.py | 34 +- db/database_setup.py | 13 +- docker-compose.yml | 2 +- docs/configuration.md | 10 +- docs/deployment.md | 21 + eslint.config.mjs | 6 + locales/en.json | 2 + locales/ru.json | 2 + package-lock.json | 964 +++++------------- package.json | 18 +- requirements.txt | 16 +- tests/test_security.py | 143 ++- 24 files changed, 576 insertions(+), 754 deletions(-) diff --git a/.dockerignore b/.dockerignore index 43d619d..224cdb1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -8,8 +8,13 @@ README.md .env.* !.env.example scratch_*.py +scratch/ +.claude/ *.local.* node_modules/ +docker-compose-dev.yml +data/* +!data/tariffs.example.json # CI diff --git a/.env.example b/.env.example index 4a99862..7882f23 100644 --- a/.env.example +++ b/.env.example @@ -147,6 +147,8 @@ STARS_PRICE_12_MONTHS=0 TRAFFIC_PACKAGES=10:199,50:799 # Format: ":", comma-separated STARS_TRAFFIC_PACKAGES=10:2500 # Optional: traffic packages priced in Stars TARIFFS_CONFIG_PATH=data/tariffs.json # Optional Tariffs 2.0 JSON config. If missing, legacy .env pricing is used. +NEWT_ID= # Optional: local docker-compose-dev.yml Newt tunnel id +NEWT_SECRET= # Optional: local docker-compose-dev.yml Newt tunnel secret TARIFF_TRAFFIC_WARNING_LEVELS=85,90,95 # Tariffs 2.0 traffic warning levels, percent used # Subscription Notifications diff --git a/README.md b/README.md index f430941..d619f58 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,14 @@ docker compose logs -f remnawave-minishop Для каталога тарифов используется `TARIFFS_CONFIG_PATH` со значением по умолчанию `data/tariffs.json`. Пример формата лежит в [data/tariffs.example.json](data/tariffs.example.json), подробности - в [docs/tariffs.md](docs/tariffs.md). +Если в Docker Compose включаете bind mount `./data:/app/data`, заранее создайте каталог и отдайте его пользователю контейнера. Это нужно для сохранения `data/tariffs.json`, кеша логотипа Web App и animated emoji: + +```bash +mkdir -p data/webapp-logo data/webapp-emoji +chown -R 10001:10001 data +chmod -R u+rwX data +``` + ## Полезные команды ```bash diff --git a/bot/app/web/admin_api.py b/bot/app/web/admin_api.py index 277ab81..c8f19ad 100644 --- a/bot/app/web/admin_api.py +++ b/bot/app/web/admin_api.py @@ -1906,14 +1906,17 @@ async def admin_settings_get_route(request: web.Request) -> web.Response: "fields": [], } override = overrides_by_key.get(key) - sections[section_id]["fields"].append( - { - **field, - "value": current_value(settings, key), - "overridden": bool(override), - "updated_at": override.get("updated_at") if override else None, - } - ) + value = current_value(settings, key) + is_secret = bool(field.get("secret")) + response_field = { + **field, + "value": "" if is_secret else value, + "overridden": bool(override), + "updated_at": override.get("updated_at") if override else None, + } + if is_secret: + response_field["has_value"] = bool(value) + sections[section_id]["fields"].append(response_field) ordered_sections = sorted(sections.values(), key=lambda s: s["order"]) return _ok({"sections": ordered_sections}) diff --git a/bot/app/web/frontend/src/App.svelte b/bot/app/web/frontend/src/App.svelte index fc30c19..66b2c3a 100644 --- a/bot/app/web/frontend/src/App.svelte +++ b/bot/app/web/frontend/src/App.svelte @@ -53,9 +53,7 @@ CSRF_COOKIE_NAME, isManuallyLoggedOut as readManualLogoutFlag, markManualLogout as markManualLogoutInStorage, - persistToken, readCookie, - readStoredToken, } from "./lib/webapp/session.js"; import { createTelegramSdk } from "./lib/webapp/telegramSdk.js"; import { mockApi as runMockApi } from "./lib/webapp/mockApi.js"; @@ -105,7 +103,7 @@ let languageClickGuardArmTimer = null; let emailAvatarUrl = ""; let avatarHashToken = ""; - let token = MOCK ? "local-preview" : readStoredToken(); + let token = MOCK ? "local-preview" : ""; let csrfToken = MOCK ? "" : readCookie(CSRF_COOKIE_NAME) || ""; let scrollLockApplied = false; let tg = null; @@ -132,7 +130,6 @@ const apiClient = createApiClient({ apiBase: CFG.apiBase, csrfCookieName: CSRF_COOKIE_NAME, - getToken: () => token, getCsrfToken: () => csrfToken, onUnauthorized: () => { clearToken(); @@ -621,7 +618,7 @@ clearManualLogoutFlag(); token = nextToken || ""; csrfToken = nextCsrf || readCookie(CSRF_COOKIE_NAME) || ""; - if (token && !MOCK) persistToken(token); + if (!MOCK) clearStoredToken(); } function clearToken() { diff --git a/bot/app/web/frontend/src/admin/sections/SettingsSection.svelte b/bot/app/web/frontend/src/admin/sections/SettingsSection.svelte index 9217286..893157b 100644 --- a/bot/app/web/frontend/src/admin/sections/SettingsSection.svelte +++ b/bot/app/web/frontend/src/admin/sections/SettingsSection.svelte @@ -65,6 +65,12 @@ revealedSecrets = next; } + function secretPlaceholder(field) { + if (settingsDirty[field.key]?.deleted) return field.placeholder || "••••••••"; + if (field.has_value) return at("settings_secret_configured", {}, "Secret is set"); + return field.placeholder || at("settings_secret_empty", {}, "Not set"); + } + function groupSectionFields(section) { const groups = new Map(); for (const field of section.fields || []) { @@ -124,7 +130,9 @@
{fieldLabelText(field)} - {at("settings_badge_secret", {}, "Secret")} + {#if field.secret} + {at("settings_badge_secret", {}, "Secret")} + {/if} {#if isOverridden(field)} {at("settings_badge_override", {}, "Override")} {/if} @@ -189,7 +197,7 @@ settingsStore.markDirty(field.key, e.currentTarget.value)} diff --git a/bot/app/web/frontend/src/lib/webapp/mockApi.js b/bot/app/web/frontend/src/lib/webapp/mockApi.js index 600661f..f354743 100644 --- a/bot/app/web/frontend/src/lib/webapp/mockApi.js +++ b/bot/app/web/frontend/src/lib/webapp/mockApi.js @@ -201,10 +201,10 @@ export async function mockApi(path, options = {}, context = {}) { if (path === "/me") return clone(DEV_MOCK.data); if (path === "/auth/email/request") return { ok: true }; if (path === "/auth/email/verify" || path === "/auth/email/magic") { - return { ok: true, token: "local-preview", csrf_token: "local-preview-csrf" }; + return { ok: true, csrf_token: "local-preview-csrf" }; } if (path === "/auth/token") { - return { ok: true, token: "local-preview", csrf_token: "local-preview-csrf" }; + return { ok: true, csrf_token: "local-preview-csrf" }; } if (path === "/promo/apply") return { ok: true, end_date_text: "31.05.2026" }; if (path === "/devices") return clone(DEV_MOCK.data.devices); @@ -267,10 +267,10 @@ export async function mockApi(path, options = {}, context = {}) { return { ok: true }; } if (path === "/account/email/verify" && String(options.method || "").toUpperCase() === "POST") { - return { ok: true, token: "local-preview", csrf_token: "local-preview-csrf" }; + return { ok: true, csrf_token: "local-preview-csrf" }; } if (path === "/account/telegram/link" && String(options.method || "").toUpperCase() === "POST") { - return { ok: true, token: "local-preview", csrf_token: "local-preview-csrf" }; + return { ok: true, csrf_token: "local-preview-csrf" }; } if (path === "/payments" && String(options.method || "").toUpperCase() === "POST") { return { diff --git a/bot/app/web/frontend/src/lib/webapp/publicApi.js b/bot/app/web/frontend/src/lib/webapp/publicApi.js index b4ccf8e..69c0f57 100644 --- a/bot/app/web/frontend/src/lib/webapp/publicApi.js +++ b/bot/app/web/frontend/src/lib/webapp/publicApi.js @@ -3,7 +3,6 @@ import { readCookie } from "./session.js"; export function createApiClient({ apiBase = "", csrfCookieName = "rw_webapp_csrf", - getToken = () => "", getCsrfToken = () => "", onUnauthorized = () => {}, mockApi = null, @@ -14,8 +13,6 @@ export function createApiClient({ const method = String(options.method || "GET").toUpperCase(); const headers = { ...(options.headers || {}) }; - const token = getToken(); - if (token) headers.Authorization = `Bearer ${token}`; const csrf = getCsrfToken() || readCookie(csrfCookieName) || ""; if (csrf && ["POST", "PUT", "PATCH", "DELETE"].includes(method)) { @@ -23,7 +20,11 @@ export function createApiClient({ } if (options.body && !headers["Content-Type"]) headers["Content-Type"] = "application/json"; - const response = await fetch(`${apiBase}${path}`, { ...options, headers }); + const response = await fetch(`${apiBase}${path}`, { + ...options, + headers, + credentials: "same-origin", + }); const payload = await response.json().catch(() => ({})); if (response.status === 401) onUnauthorized(); return payload; @@ -38,6 +39,7 @@ export function createApiClient({ headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), signal: options.signal, + credentials: "same-origin", }); return response.json(); } diff --git a/bot/app/web/frontend/src/lib/webapp/session.js b/bot/app/web/frontend/src/lib/webapp/session.js index 478626c..57b9f95 100644 --- a/bot/app/web/frontend/src/lib/webapp/session.js +++ b/bot/app/web/frontend/src/lib/webapp/session.js @@ -13,16 +13,6 @@ export function readCookie(name) { return cookie ? decodeURIComponent(cookie.slice(prefix.length)) : ""; } -export function readStoredToken(storageKey = TOKEN_STORAGE_KEY) { - if (typeof localStorage === "undefined") return ""; - return localStorage.getItem(storageKey) || ""; -} - -export function persistToken(token, storageKey = TOKEN_STORAGE_KEY) { - if (!token || typeof localStorage === "undefined") return; - localStorage.setItem(storageKey, token); -} - export function clearStoredToken(storageKey = TOKEN_STORAGE_KEY) { if (typeof localStorage === "undefined") return; localStorage.removeItem(storageKey); diff --git a/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js b/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js index 45d73b2..3522865 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/accountStore.js @@ -139,7 +139,7 @@ export function createAccountStore({ body: JSON.stringify({ email: s.linkEmailPending, code }), }); if (!response?.ok) throw response; - if (response?.token) setToken(response.token, response.csrf_token); + if (response?.csrf_token) setToken("", response.csrf_token); await loadData(); closeLinkEmailDialog(); showToast(t("wa_settings_linked")); @@ -158,7 +158,7 @@ export function createAccountStore({ body: JSON.stringify(payload), }); if (!response?.ok) throw response; - if (response?.token) setToken(response.token, response.csrf_token); + if (response?.csrf_token) setToken("", response.csrf_token); await loadData(); showToast(t("wa_settings_linked")); } catch (error) { diff --git a/bot/app/web/frontend/src/lib/webapp/stores/authStore.js b/bot/app/web/frontend/src/lib/webapp/stores/authStore.js index 72e899b..8d42af7 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/authStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/authStore.js @@ -96,8 +96,8 @@ export function createAuthStore({ const referralParam = readReferralParam(getTg()); if (referralParam) payload.referral_code = referralParam; const response = await publicApi("/auth/email/magic", payload); - if (response.ok && response.token) { - setToken(response.token, response.csrf_token); + if (response.ok && response.csrf_token) { + setToken("", response.csrf_token); clearAuthQuery(); await loadData(); return true; @@ -126,8 +126,8 @@ export function createAuthStore({ const referralParam = readReferralParam(getTg()); if (referralParam) payload.referral_code = referralParam; const response = await publicApi("/auth/token", payload, { signal: options.signal }); - if (response.ok && response.token) { - setToken(response.token, response.csrf_token); + if (response.ok && response.csrf_token) { + setToken("", response.csrf_token); clearAuthQuery(); setAuthStatus(""); await loadData(); @@ -202,8 +202,8 @@ export function createAuthStore({ const referralParam = readReferralParam(getTg()); if (referralParam) payload.referral_code = referralParam; const response = await publicApi("/auth/email/verify", payload); - if (!response.ok || !response.token) throw response; - setToken(response.token, response.csrf_token); + if (!response.ok || !response.csrf_token) throw response; + setToken("", response.csrf_token); await loadData(); setAuthStatus(""); } catch (error) { diff --git a/bot/app/web/frontend/src/lib/webapp/stores/billingStore.js b/bot/app/web/frontend/src/lib/webapp/stores/billingStore.js index 357ecd8..b5e429f 100644 --- a/bot/app/web/frontend/src/lib/webapp/stores/billingStore.js +++ b/bot/app/web/frontend/src/lib/webapp/stores/billingStore.js @@ -34,7 +34,7 @@ export function createBillingStore({ billing, loadData, t, showToast, openExtern defaultMethod = "" ) { state.update((s) => { - let step = s.paymentStep; + let step; let plan = s.selectedPlan; let tariffKey = s.selectedTariffKey; diff --git a/bot/app/web/subscription_webapp.py b/bot/app/web/subscription_webapp.py index b15fe93..62b5702 100644 --- a/bot/app/web/subscription_webapp.py +++ b/bot/app/web/subscription_webapp.py @@ -51,7 +51,7 @@ from bot.services.severpay_service import SeverPayService from bot.services.subscription_service import SubscriptionService from bot.services.yookassa_service import YooKassaService from bot.utils.config_link import prepare_config_links -from bot.utils.request_security import request_client_ip +from bot.utils.request_security import parse_ip_entries, request_client_ip from bot.utils.text_sanitizer import sanitize_display_name, sanitize_username from config.settings import Settings from db.dal import payment_dal, subscription_dal, user_dal @@ -353,11 +353,34 @@ def _public_webapp_base_url(settings: Settings, request: web.Request) -> str: if parsed_url.scheme and parsed_url.netloc: return f"{parsed_url.scheme}://{parsed_url.netloc}" - scheme = request.headers.get("X-Forwarded-Proto") or request.scheme - host = request.headers.get("X-Forwarded-Host") or request.headers.get("Host") or request.host + headers = request.headers + if _request_remote_is_trusted_proxy(settings, request): + scheme = _first_header_value(headers.get("X-Forwarded-Proto")) or request.scheme + host = ( + _first_header_value(headers.get("X-Forwarded-Host")) + or headers.get("Host") + or request.host + ) + else: + scheme = request.scheme + host = headers.get("Host") or request.host return f"{scheme}://{host}".rstrip("/") +def _first_header_value(value: Optional[str]) -> str: + if not value: + return "" + return value.split(",", 1)[0].strip() + + +def _request_remote_is_trusted_proxy(settings: Settings, request: web.Request) -> bool: + try: + remote_ip = ipaddress.ip_address(str(request.remote or "").strip()) + except ValueError: + return False + return any(remote_ip in network for network in parse_ip_entries(settings.trusted_proxies)) + + def _telegram_oauth_callback_url(settings: Settings, request: web.Request) -> str: return f"{_public_webapp_base_url(settings, request)}/auth/telegram/callback" @@ -838,12 +861,12 @@ async def _security_headers_middleware(request: web.Request, handler): "Content-Security-Policy", ( "default-src 'self'; " - f"script-src 'self' 'nonce-{nonce}' 'unsafe-eval' https://telegram.org; " + f"script-src 'self' 'nonce-{nonce}' https://telegram.org; " "frame-src https://oauth.telegram.org; " "frame-ancestors https://web.telegram.org https://t.me; " "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net; " "font-src 'self' https://fonts.gstatic.com https://cdn.jsdelivr.net data:; " - "img-src 'self' data: https: http:; " + "img-src 'self' data: https:; " "connect-src 'self' https://oauth.telegram.org; " "object-src 'none'; " "base-uri 'self'; " @@ -2860,7 +2883,6 @@ def _build_webapp_auth_response( ) -> web.Response: response_payload = dict(payload) response_payload["ok"] = True - response_payload["token"] = token csrf_value = csrf_token or secrets.token_hex(32) response_payload["csrf_token"] = csrf_value response = web.json_response(response_payload) diff --git a/db/database_setup.py b/db/database_setup.py index 1b5666b..d8a401e 100644 --- a/db/database_setup.py +++ b/db/database_setup.py @@ -1,5 +1,6 @@ import logging +from sqlalchemy.engine import make_url from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import sessionmaker @@ -11,11 +12,21 @@ from .migrator import run_database_migrations async_engine = None +def redacted_database_url(database_url: str) -> str: + try: + return make_url(database_url).render_as_string(hide_password=True) + except Exception: + return "" + + def init_db_connection(settings: Settings) -> sessionmaker: global async_engine if async_engine is None: - logging.info(f"Attempting to create SQLAlchemy engine with URL: {settings.DATABASE_URL}") + logging.info( + "Attempting to create SQLAlchemy engine with URL: %s", + redacted_database_url(settings.DATABASE_URL), + ) async_engine = create_async_engine( settings.DATABASE_URL, echo=False, diff --git a/docker-compose.yml b/docker-compose.yml index 121defd..6c00281 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,7 @@ services: # volumes: # - ./data:/app/data healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1"] + test: ["CMD-SHELL", "python -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8080/healthz', timeout=3).read()\""] interval: 30s timeout: 5s retries: 5 diff --git a/docs/configuration.md b/docs/configuration.md index af299c2..34f916f 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -79,7 +79,15 @@ nano .env Если файл из `TARIFFS_CONFIG_PATH` существует, бот использует каталог тарифов. Если файла нет, применяется конфигурация из переменных `.env`. -В `docker-compose-dev.yml` каталог `./data` монтируется в контейнер как `/app/data`, чтобы Web App админка могла сохранять `data/tariffs.json`. После изменения compose-файла пересоздайте контейнер: +В `docker-compose-dev.yml` каталог `./data` монтируется в контейнер как `/app/data`, чтобы Web App админка могла сохранять `data/tariffs.json`. Тот же каталог используется для кеша логотипа Web App (`data/webapp-logo`) и animated emoji (`data/webapp-emoji`). Если этот bind mount включен на Ubuntu-сервере, создайте подкаталоги и отдайте `data` UID `10001`, под которым работает приложение внутри контейнера: + +```bash +mkdir -p data/webapp-logo data/webapp-emoji +chown -R 10001:10001 data +chmod -R u+rwX data +``` + +После изменения compose-файла или прав пересоздайте контейнер: ```bash docker compose -f docker-compose-dev.yml up -d --build --force-recreate diff --git a/docs/deployment.md b/docs/deployment.md index ddf8956..8c70610 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -19,6 +19,27 @@ IMAGE_TAG=3.1.0 docker compose -f docker-compose-remote-server.yml up -d `docker-compose-remote-server.yml` можно использовать как шаблон и заменить `image:` на нужный образ. По умолчанию используется `ghcr.io/3252a8/remnawave-minishop:latest`. +### Права на `./data` + +Если в Compose включен bind mount `./data:/app/data` или `./data:/app/data:rw`, каталог на хосте должен быть доступен на запись пользователю контейнера. Контейнер запускает приложение от `appuser` с UID `10001`; запуск Docker от `root` на Ubuntu не делает этот каталог writable внутри контейнера, если на хосте он принадлежит `root:root` с обычными правами `755`. + +Перед запуском или после добавления mount выполните на сервере из каталога проекта: + +```bash +mkdir -p data/webapp-logo data/webapp-emoji +chown -R 10001:10001 data +chmod -R u+rwX data +docker compose up -d --force-recreate remnawave-minishop +``` + +Проверка прав: + +```bash +docker compose exec remnawave-minishop sh -lc 'id; ls -ldn /app/data /app/data/webapp-emoji; touch /app/data/webapp-emoji/test && rm /app/data/webapp-emoji/test' +``` + +Если проверочный `touch` проходит без `Permission denied`, Web App сможет сохранять каталог тарифов, кеш `WEBAPP_LOGO_URL` в `/app/data/webapp-logo` и кеш animated emoji в `/app/data/webapp-emoji`. + ## Обновление версии Образ приложения: `ghcr.io/3252a8/remnawave-minishop`. Тег задаётся переменной окружения **`IMAGE_TAG`** (в Compose подставляется как `${IMAGE_TAG:-latest}`). Для продакшена разумно закрепить **конкретный тег релиза** вместо `latest`, чтобы обновляться осознанно и иметь откат. diff --git a/eslint.config.mjs b/eslint.config.mjs index 9a98bfb..a6f01e9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -24,6 +24,12 @@ export default [ "no-empty": "warn", }, }, + { + files: ["bot/app/web/frontend/**/*.svelte"], + rules: { + "no-useless-assignment": "off", + }, + }, { files: ["bot/app/web/frontend/**/*.{js,svelte}"], languageOptions: { diff --git a/locales/en.json b/locales/en.json index 18bc29d..ffb9ee9 100644 --- a/locales/en.json +++ b/locales/en.json @@ -1050,6 +1050,8 @@ "admin_tariff_squads": "Squads", "admin_tariff_premium": "Premium", "admin_settings_badge_secret": "Secret", + "admin_settings_secret_configured": "Secret is set", + "admin_settings_secret_empty": "Not set", "admin_settings_badge_override": "Override", "admin_status_limited": "Limited", "admin_status_expired": "Expired", diff --git a/locales/ru.json b/locales/ru.json index 1e0df55..29c3b67 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -1050,6 +1050,8 @@ "admin_tariff_squads": "Squads", "admin_tariff_premium": "Premium", "admin_settings_badge_secret": "Secret", + "admin_settings_secret_configured": "Секрет задан", + "admin_settings_secret_empty": "Не задан", "admin_settings_badge_override": "Override", "admin_status_limited": "Ограничен", "admin_status_expired": "Истёк", diff --git a/package-lock.json b/package-lock.json index e0a64d5..97635e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,28 +5,28 @@ "packages": { "": { "devDependencies": { - "@eslint/js": "^9.39.2", + "@eslint/js": "^10.0.1", "@internationalized/date": "^3.12.1", - "@sveltejs/vite-plugin-svelte": "^7.0.0", - "@tailwindcss/cli": "4.2.4", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/cli": "4.3.0", "@tailwindcss/vite": "^4.3.0", - "bits-ui": "^2.18.0", + "bits-ui": "^2.18.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "esbuild": "^0.28.0", - "eslint": "^9.39.2", + "eslint": "^10.3.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.14.0", - "globals": "^16.5.0", + "globals": "^17.6.0", "lucide-svelte": "^1.0.1", "prettier": "^3.7.4", "prettier-plugin-svelte": "^3.4.0", "svelte": "^5.55.5", "svelte-eslint-parser": "^1.4.1", - "tailwind-merge": "^3.5.0", - "tailwindcss": "4.2.4", + "tailwind-merge": "^3.6.0", + "tailwindcss": "4.3.0", "uplot": "^1.6.32", - "vite": "^8.0.10" + "vite": "^8.0.12" } }, "node_modules/@emnapi/core": { @@ -548,118 +548,89 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0" + "@eslint/core": "^1.2.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@floating-ui/core": { @@ -836,9 +807,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.127.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz", - "integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==", + "version": "0.129.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", + "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", "dev": true, "license": "MIT", "funding": { @@ -1173,9 +1144,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", + "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", "cpu": [ "arm64" ], @@ -1190,9 +1161,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", + "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", "cpu": [ "arm64" ], @@ -1207,9 +1178,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", + "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", "cpu": [ "x64" ], @@ -1224,9 +1195,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz", - "integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", + "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", "cpu": [ "x64" ], @@ -1241,9 +1212,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz", - "integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", + "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", "cpu": [ "arm" ], @@ -1258,9 +1229,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", + "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", "cpu": [ "arm64" ], @@ -1278,9 +1249,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", + "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", "cpu": [ "arm64" ], @@ -1298,9 +1269,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", + "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", "cpu": [ "ppc64" ], @@ -1318,9 +1289,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", + "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", "cpu": [ "s390x" ], @@ -1338,9 +1309,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz", - "integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", + "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", "cpu": [ "x64" ], @@ -1358,9 +1329,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz", - "integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", + "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", "cpu": [ "x64" ], @@ -1378,9 +1349,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz", - "integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", + "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", "cpu": [ "arm64" ], @@ -1395,9 +1366,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz", - "integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", + "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", "cpu": [ "wasm32" ], @@ -1414,9 +1385,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", + "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", "cpu": [ "arm64" ], @@ -1431,9 +1402,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz", - "integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", + "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", "cpu": [ "x64" ], @@ -1448,9 +1419,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz", - "integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", + "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", "dev": true, "license": "MIT" }, @@ -1465,9 +1436,9 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.0.0.tgz", - "integrity": "sha512-ILXmxC7HAsnkK2eslgPetrqqW1BKSL7LktsFgqzNj83MaivMGZzluWq32m25j2mDOjmSKX7GGWahePhuEs7P/g==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz", + "integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==", "dev": true, "license": "MIT", "dependencies": { @@ -1495,309 +1466,25 @@ } }, "node_modules/@tailwindcss/cli": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.2.4.tgz", - "integrity": "sha512-e87GGhuXxnyQPyA0TS8an/3wNpj+OUmx8u0F4BicYr48TF72032AIu5917rRYaWm7HorXi3GSZ/uG+ohqP6AKA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@tailwindcss/cli/-/cli-4.3.0.tgz", + "integrity": "sha512-X9kdlqyMopO9fewbgHsEeuy31YzMHbdZ9VsKt004tB+mxSg1CNbyhZYCzvhciN0AM4R4b5lvIprPjtNq7iQxpQ==", "dev": true, "license": "MIT", "dependencies": { "@parcel/watcher": "^2.5.1", - "@tailwindcss/node": "4.2.4", - "@tailwindcss/oxide": "4.2.4", - "enhanced-resolve": "^5.19.0", + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "enhanced-resolve": "^5.21.0", "mri": "^1.2.0", "picocolors": "^1.1.1", - "tailwindcss": "4.2.4" + "tailwindcss": "4.3.0" }, "bin": { "tailwindcss": "dist/index.mjs" } }, "node_modules/@tailwindcss/node": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz", - "integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "enhanced-resolve": "^5.19.0", - "jiti": "^2.6.1", - "lightningcss": "1.32.0", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.2.4" - } - }, - "node_modules/@tailwindcss/oxide": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz", - "integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 20" - }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-arm64": "4.2.4", - "@tailwindcss/oxide-darwin-x64": "4.2.4", - "@tailwindcss/oxide-freebsd-x64": "4.2.4", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", - "@tailwindcss/oxide-linux-x64-musl": "4.2.4", - "@tailwindcss/oxide-wasm32-wasi": "4.2.4", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" - } - }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz", - "integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz", - "integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz", - "integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz", - "integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz", - "integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz", - "integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz", - "integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz", - "integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz", - "integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz", - "integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.8.1", - "@emnapi/runtime": "^1.8.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.1", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz", - "integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz", - "integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 20" - } - }, - "node_modules/@tailwindcss/vite": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", - "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tailwindcss/node": "4.3.0", - "@tailwindcss/oxide": "4.3.0", - "tailwindcss": "4.3.0" - }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7 || ^8" - } - }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/node": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", @@ -1813,7 +1500,7 @@ "tailwindcss": "4.3.0" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide": { + "node_modules/@tailwindcss/oxide": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", @@ -1837,7 +1524,7 @@ "@tailwindcss/oxide-win32-x64-msvc": "4.3.0" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-android-arm64": { + "node_modules/@tailwindcss/oxide-android-arm64": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.0.tgz", "integrity": "sha512-TJPiq67tKlLuObP6RkwvVGDoxCMBVtDgKkLfa/uyj7/FyxvQwHS+UOnVrXXgbEsfUaMgiVvC4KbJnRr26ho4Ng==", @@ -1854,7 +1541,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-arm64": { + "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.0.tgz", "integrity": "sha512-oMN/WZRb+SO37BmUElEgeEWuU8E/HXRkiODxJxLe1UTHVXLrdVSgfaJV7pSlhRGMSOiXLuxTIjfsF3wYvz8cgQ==", @@ -1871,7 +1558,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-darwin-x64": { + "node_modules/@tailwindcss/oxide-darwin-x64": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.0.tgz", "integrity": "sha512-N6CUmu4a6bKVADfw77p+iw6Yd9Q3OBhe0veaDX+QazfuVYlQsHfDgxBrsjQ/IW+zywL8mTrNd0SdJT/zgtvMdA==", @@ -1888,7 +1575,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-freebsd-x64": { + "node_modules/@tailwindcss/oxide-freebsd-x64": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.0.tgz", "integrity": "sha512-zDL5hBkQdH5C6MpqbK3gQAgP80tsMwSI26vjOzjJtNCMUo0lFgOItzHKBIupOZNQxt3ouPH7RPhvNhiTfCe5CQ==", @@ -1905,7 +1592,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.0.tgz", "integrity": "sha512-R06HdNi7A7OEoMsf6d4tjZ71RCWnZQPHj2mnotSFURjNLdBC+cIgXQ7l81CqeoiQftjf6OOblxXMInMgN2VzMA==", @@ -1922,7 +1609,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.0.tgz", "integrity": "sha512-qTJHELX8jetjhRQHCLilkVLmybpzNQAtaI/gaoVoidn/ufbNDbAo8KlK2J+yPoc8wQxvDxCmh/5lr8nC1+lTbg==", @@ -1942,7 +1629,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.0.tgz", "integrity": "sha512-Z6sukiQsngnWO+l39X4pPbiWT81IC+PLKF+PHxIlyZbGNb9MODfYlXEVlFvej5BOZInWX01kVyzeLvHsXhfczQ==", @@ -1962,7 +1649,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.0.tgz", "integrity": "sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==", @@ -1982,7 +1669,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-linux-x64-musl": { + "node_modules/@tailwindcss/oxide-linux-x64-musl": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.0.tgz", "integrity": "sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==", @@ -2002,7 +1689,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-wasm32-wasi": { + "node_modules/@tailwindcss/oxide-wasm32-wasi": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.0.tgz", "integrity": "sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==", @@ -2032,7 +1719,7 @@ "node": ">=14.0.0" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.0.tgz", "integrity": "sha512-Pe+RPVTi1T+qymuuRpcdvwSVZjnll/f7n8gBxMMh3xLTctMDKqpdfGimbMyioqtLhUYZxdJ9wGNhV7MKHvgZsQ==", @@ -2049,7 +1736,7 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.0.tgz", "integrity": "sha512-Mvrf2kXW/yeW/OTezZlCGOirXRcUuLIBx/5Y12BaPM7wJoryG6dfS/NJL8aBPqtTEx/Vm4T4vKzFUcKDT+TKUA==", @@ -2066,17 +1753,25 @@ "node": ">= 20" } }, - "node_modules/@tailwindcss/vite/node_modules/tailwindcss": { + "node_modules/@tailwindcss/vite": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", - "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", + "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.3.0", + "@tailwindcss/oxide": "4.3.0", + "tailwindcss": "4.3.0" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7 || ^8" + } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", "dev": true, "license": "MIT", "optional": true, @@ -2084,6 +1779,13 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2145,29 +1847,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, "node_modules/aria-query": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", @@ -2189,16 +1868,19 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/bits-ui": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.0.tgz", - "integrity": "sha512-GLOBZRVy3hxNHIQ2MpD/+5aK9KcBFZRhUJtZ1UDABXdlVR4K6zFpgt4T+Rwuhf2sQzlc6yK1q/DprHPjwT4Pjw==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-2.18.1.tgz", + "integrity": "sha512-KkemzKFH4T3gt3H+P86JcnAWExjByv/6vlwjm/BoCwTPHu03yiCdxbghdJLvFReQTe0acCAiRcKfmixxD6XvlA==", "dev": true, "license": "MIT", "dependencies": { @@ -2221,41 +1903,16 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": "18 || 20 || >=22" } }, "node_modules/class-variance-authority": { @@ -2281,33 +1938,6 @@ "node": ">=6" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2468,33 +2098,30 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", + "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", @@ -2504,8 +2131,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2513,7 +2139,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -2577,6 +2203,19 @@ } } }, + "node_modules/eslint-plugin-svelte/node_modules/globals": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -2607,6 +2246,56 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/esm-env": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", @@ -2815,9 +2504,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -2834,16 +2523,6 @@ "dev": true, "license": "ISC" }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -2854,23 +2533,6 @@ "node": ">= 4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2938,19 +2600,6 @@ "jiti": "lib/jiti-cli.mjs" } }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3309,13 +2958,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/lucide-svelte": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-1.0.1.tgz", @@ -3347,16 +2989,19 @@ } }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/mri": { @@ -3470,19 +3115,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3524,9 +3156,9 @@ } }, "node_modules/postcss": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz", - "integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==", + "version": "8.5.14", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", "dev": true, "funding": [ { @@ -3707,25 +3339,15 @@ "node": ">=6" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/rolldown": { - "version": "1.0.0-rc.17", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz", - "integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", + "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.127.0", - "@rolldown/pluginutils": "1.0.0-rc.17" + "@oxc-project/types": "=0.129.0", + "@rolldown/pluginutils": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -3734,21 +3356,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", - "@rolldown/binding-darwin-x64": "1.0.0-rc.17", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" + "@rolldown/binding-android-arm64": "1.0.0", + "@rolldown/binding-darwin-arm64": "1.0.0", + "@rolldown/binding-darwin-x64": "1.0.0", + "@rolldown/binding-freebsd-x64": "1.0.0", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", + "@rolldown/binding-linux-arm64-gnu": "1.0.0", + "@rolldown/binding-linux-arm64-musl": "1.0.0", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0", + "@rolldown/binding-linux-s390x-gnu": "1.0.0", + "@rolldown/binding-linux-x64-gnu": "1.0.0", + "@rolldown/binding-linux-x64-musl": "1.0.0", + "@rolldown/binding-openharmony-arm64": "1.0.0", + "@rolldown/binding-wasm32-wasi": "1.0.0", + "@rolldown/binding-win32-arm64-msvc": "1.0.0", + "@rolldown/binding-win32-x64-msvc": "1.0.0" } }, "node_modules/runed": { @@ -3822,19 +3444,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/style-to-object": { "version": "1.0.14", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", @@ -3845,19 +3454,6 @@ "inline-style-parser": "0.2.7" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/svelte": { "version": "5.55.5", "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.5.tgz", @@ -3946,9 +3542,9 @@ "license": "MIT" }, "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", "dev": true, "license": "MIT", "funding": { @@ -3957,9 +3553,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz", - "integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", + "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", "dev": true, "license": "MIT" }, @@ -4039,16 +3635,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.10", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz", - "integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==", + "version": "8.0.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", + "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", - "postcss": "^8.5.10", - "rolldown": "1.0.0-rc.17", + "postcss": "^8.5.14", + "rolldown": "1.0.0", "tinyglobby": "^0.2.16" }, "bin": { @@ -4065,7 +3661,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", + "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index c3f43c3..1b5a7c5 100644 --- a/package.json +++ b/package.json @@ -21,27 +21,27 @@ "fix": "npm run format && npm run lint:fix" }, "devDependencies": { - "@eslint/js": "^9.39.2", + "@eslint/js": "^10.0.1", "@internationalized/date": "^3.12.1", - "@sveltejs/vite-plugin-svelte": "^7.0.0", - "@tailwindcss/cli": "4.2.4", + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tailwindcss/cli": "4.3.0", "@tailwindcss/vite": "^4.3.0", - "bits-ui": "^2.18.0", + "bits-ui": "^2.18.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "esbuild": "^0.28.0", - "eslint": "^9.39.2", + "eslint": "^10.3.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-svelte": "^3.14.0", - "globals": "^16.5.0", + "globals": "^17.6.0", "lucide-svelte": "^1.0.1", "prettier": "^3.7.4", "prettier-plugin-svelte": "^3.4.0", "svelte": "^5.55.5", "svelte-eslint-parser": "^1.4.1", - "tailwind-merge": "^3.5.0", - "tailwindcss": "4.2.4", + "tailwind-merge": "^3.6.0", + "tailwindcss": "4.3.0", "uplot": "^1.6.32", - "vite": "^8.0.10" + "vite": "^8.0.12" } } diff --git a/requirements.txt b/requirements.txt index fdf3584..9ce503e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ -aiogram==3.24.0 -python-dotenv==1.2.1 -aiohttp==3.13.3 -pydantic==2.12.5 -yookassa==3.9.0 +aiogram==3.28.2 +python-dotenv==1.2.2 +aiohttp==3.13.5 +pydantic==2.13.4 +yookassa==3.10.1 httpx>=0.27.0 -pydantic_settings==2.12.0 +pydantic_settings==2.14.1 email-validator==2.3.0 -sqlalchemy[asyncio]==2.0.45 +sqlalchemy[asyncio]==2.0.49 asyncpg==0.31.0 aiocryptopay==0.4.8 -PyJWT[crypto]==2.10.1 +PyJWT[crypto]==2.12.1 diff --git a/tests/test_security.py b/tests/test_security.py index 91127c9..611fc6b 100644 --- a/tests/test_security.py +++ b/tests/test_security.py @@ -1,13 +1,14 @@ import hashlib import hmac +import json import unittest from types import SimpleNamespace -from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, patch from urllib.parse import parse_qs, urlsplit from aiohttp import web -from bot.app.web import subscription_webapp +from bot.app.web import admin_api, subscription_webapp from bot.app.web.webapp_auth import ( create_telegram_oauth_nonce, create_webapp_session_token, @@ -17,6 +18,8 @@ from bot.handlers.user.payment import yookassa_webhook_route from bot.services.crypto_pay_service import CryptoPayService from bot.services.freekassa_service import FreeKassaService from bot.utils.request_security import request_client_ip +from config.settings import Settings +from db.database_setup import redacted_database_url class RequestSecurityTests(unittest.IsolatedAsyncioTestCase): @@ -131,6 +134,28 @@ class CryptoPayServiceTests(unittest.TestCase): class WebAppSecurityTests(unittest.IsolatedAsyncioTestCase): + def test_auth_response_sets_cookies_and_does_not_return_session_token(self): + settings = SimpleNamespace( + WEBAPP_SESSION_SECRET="session-secret", + WEBAPP_SESSION_TTL_SECONDS=3600, + ) + + response = subscription_webapp._build_webapp_auth_response( + settings, + {"user_id": 321}, + token="raw-session-token", + csrf_token="csrf-token", + ) + payload = json.loads(response.text) + + self.assertTrue(payload["ok"]) + self.assertEqual(payload["csrf_token"], "csrf-token") + self.assertNotIn("token", payload) + self.assertEqual(response.cookies["rw_webapp_session"].value, "raw-session-token") + self.assertTrue(response.cookies["rw_webapp_session"]["httponly"]) + self.assertEqual(response.cookies["rw_webapp_csrf"].value, "csrf-token") + self.assertFalse(response.cookies["rw_webapp_csrf"]["httponly"]) + def test_require_user_id_falls_back_to_cookie_session(self): settings = SimpleNamespace( WEBAPP_SESSION_SECRET="session-secret", @@ -297,6 +322,120 @@ class WebAppSecurityTests(unittest.IsolatedAsyncioTestCase): ["write", "phone"], ) + def test_public_webapp_base_url_ignores_forwarded_headers_from_untrusted_remote(self): + settings = SimpleNamespace( + SUBSCRIPTION_MINI_APP_URL="", + trusted_proxies=["127.0.0.1"], + ) + request = SimpleNamespace( + remote="203.0.113.10", + scheme="http", + host="internal.local", + headers={ + "Host": "internal.local", + "X-Forwarded-Proto": "https", + "X-Forwarded-Host": "evil.example.com", + }, + ) + + self.assertEqual( + subscription_webapp._public_webapp_base_url(settings, request), + "http://internal.local", + ) + + def test_public_webapp_base_url_accepts_forwarded_headers_from_trusted_proxy(self): + settings = SimpleNamespace( + SUBSCRIPTION_MINI_APP_URL="", + trusted_proxies=["127.0.0.1"], + ) + request = SimpleNamespace( + remote="127.0.0.1", + scheme="http", + host="internal.local", + headers={ + "Host": "internal.local", + "X-Forwarded-Proto": "https", + "X-Forwarded-Host": "app.example.com", + }, + ) + + self.assertEqual( + subscription_webapp._public_webapp_base_url(settings, request), + "https://app.example.com", + ) + + async def test_security_headers_csp_excludes_unsafe_eval_and_plain_http_images(self): + request = {"settings": SimpleNamespace()} + handler = AsyncMock(return_value=web.Response(text="ok")) + + response = await subscription_webapp._security_headers_middleware(request, handler) + csp = response.headers["Content-Security-Policy"] + + self.assertNotIn("'unsafe-eval'", csp) + self.assertIn("img-src 'self' data: https:;", csp) + self.assertNotIn("img-src 'self' data: https: http:;", csp) + + +class AdminSettingsSecurityTests(unittest.IsolatedAsyncioTestCase): + async def test_admin_settings_masks_secret_values_and_exposes_has_value(self): + class AsyncSessionFactory: + def __call__(self): + return self + + async def __aenter__(self): + return object() + + async def __aexit__(self, exc_type, exc, tb): + return False + + settings = Settings( + _env_file=None, + BOT_TOKEN="token", + POSTGRES_USER="app_user", + POSTGRES_PASSWORD="app_password", + YOOKASSA_SECRET_KEY="super-secret", + SHOP_NAME="Visible shop", + ) + request = SimpleNamespace( + app={"settings": settings, "async_session_factory": AsyncSessionFactory()}, + headers={}, + cookies={}, + admin_telegram_id=1, + ) + request.get = lambda key, default=None: getattr(request, key, default) + + with ( + patch.object(admin_api, "_require_admin_user_id", return_value=1), + patch.object( + admin_api.app_settings_dal, + "get_overrides_with_meta", + AsyncMock(return_value=[]), + ), + ): + response = await admin_api.admin_settings_get_route(request) + + payload = json.loads(response.text) + secret_field = next( + field + for section in payload["sections"] + for field in section["fields"] + if field["key"] == "YOOKASSA_SECRET_KEY" + ) + + self.assertEqual(secret_field["value"], "") + self.assertTrue(secret_field["has_value"]) + self.assertNotIn("super-secret", response.text) + + +class DatabaseLoggingSecurityTests(unittest.TestCase): + def test_database_url_redaction_hides_password(self): + raw_url = "postgresql+asyncpg://user:raw-password@db.example.com:5432/app" + + redacted = redacted_database_url(raw_url) + + self.assertIn("user:***@", redacted) + self.assertNotIn("raw-password", redacted) + def asyncio_run(coro): import asyncio