diff --git a/.gitignore b/.gitignore
index e040353..00d466d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,9 @@
web/dist/
web/node_modules/
+web/.output/
+web/coverage/
+web/node_modules/.nitro/
+web/src/routeTree.gen.ts
web/test-results/
.env
.dev-credentials/
diff --git a/AGENTS.md b/AGENTS.md
index 769a1ad..bd1422b 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -88,10 +88,10 @@
### React 前端
-- 使用 React 19、Vite 8、Refine 与 shadcn/ui、Tailwind CSS。在 `web/package-lock.json` 中锁定精确的已安装版本。
+- 使用 React 19、TanStack Start、TanStack Router、TanStack Query、Vite 7、shadcn/ui 与 Tailwind CSS。业务数据访问使用 `web/src/shared/api/dataProvider.js`,不再引入 React Router 或 Refine;路由树由 `web/scripts/generate-route-tree.mjs` 生成并在 `web/package-lock.json` 中锁定精确版本。
- 视觉体系由 CreatorHub 自有掌控:品牌 token 保留在项目主题与 Tailwind 配置中,导航/外壳保留在项目自有的 Layout 组件中。shadcn/ui 组件按需生成并纳入仓库自有代码,不引入 Ant Design、Arco、MUI 等其他组件体系。
- 图标统一使用 RemixIcon,不为差异化自创奇怪图标。
-- 将启动、停止、回收等领域动作保留为显式动作;不得为迎合 Refine 惯例而伪装成通用 CRUD 更新。
+- 将启动、停止、回收等领域动作保留为显式动作;不得为迎合通用 CRUD 惯例而伪装成资源更新。
## UI 设计
diff --git a/browser_gateway/platform/douyin.py b/browser_gateway/platform/douyin.py
index 6a1f783..ccf3d08 100644
--- a/browser_gateway/platform/douyin.py
+++ b/browser_gateway/platform/douyin.py
@@ -214,7 +214,17 @@ class DouyinBrowser:
}}
body+=decoder.decode();return {{status:r.status,body,too_large:false}};
}})()"""
- result = cdp.evaluate(expression)
+ result = None
+ for attempt in range(2):
+ result = cdp.evaluate(expression)
+ if (
+ not isinstance(result, dict)
+ or not isinstance(result.get("status"), int)
+ or result.get("status") not in {401, 403}
+ or attempt == 1
+ ):
+ break
+ time.sleep(2)
if (
not isinstance(result, dict)
or result.get("too_large")
@@ -232,26 +242,29 @@ class DouyinBrowser:
def resolve(self, alias: str, target: str) -> str:
if not is_douyin_share_url(target):
raise DouyinError("restricted Douyin share URL is invalid")
- if is_douyin_content_url(target):
- return target
with self.connection(alias) as cdp:
- if cdp.evaluate("location.origin") != self.origin:
+ if cdp.evaluate("location.origin") not in {"null", self.origin}:
raise DouyinError("restricted browser origin changed")
- navigation = cdp.command("Page.navigate", {"url": target})
- if (
- not isinstance(navigation, dict)
- or not isinstance(navigation.get("frameId"), str)
- or navigation.get("errorText")
- ):
- raise DouyinError("Douyin share URL navigation failed")
- event = cdp.wait_event(
- "Page.frameNavigated",
- lambda params: is_douyin_content_url(
- params.get("frame", {}).get("url", "")
- ),
+ navigation = cdp.evaluate(
+ "window.location.assign(" + json.dumps(target) + "); true"
)
- final_url = event.get("frame", {}).get("url")
- if not isinstance(final_url, str) or not is_douyin_content_url(final_url):
+ if not isinstance(navigation, bool) or not navigation:
+ raise DouyinError("Douyin share URL navigation failed")
+ deadline = time.monotonic() + CONTROL_TIMEOUT
+ final_url = ""
+ while time.monotonic() < deadline:
+ try:
+ current_url = cdp.evaluate("location.href")
+ except BrowserError:
+ if is_douyin_content_url(target):
+ final_url = target
+ break
+ raise
+ if isinstance(current_url, str) and is_douyin_content_url(current_url):
+ final_url = current_url
+ break
+ time.sleep(0.1)
+ if not final_url:
raise DouyinError("Douyin share URL did not resolve to a supported page")
return final_url
diff --git a/browser_gateway/platform/xiaohongshu.py b/browser_gateway/platform/xiaohongshu.py
index 4f3dcd2..b8abd63 100644
--- a/browser_gateway/platform/xiaohongshu.py
+++ b/browser_gateway/platform/xiaohongshu.py
@@ -3,8 +3,10 @@
from __future__ import annotations
import json
+import time
from urllib.parse import parse_qs, urlsplit
+from ..browser.cdp import CONTROL_TIMEOUT, BrowserError
from ..browser.response import (
BrowserResponse,
detect_challenge,
@@ -77,17 +79,28 @@ class XiaohongshuBrowser(DouyinBrowser):
if not is_xiaohongshu_share_url(target):
raise DouyinError("restricted Xiaohongshu share URL is invalid")
with self.connection(alias) as cdp:
- if cdp.evaluate("location.origin") != self.origin:
+ if cdp.evaluate("location.origin") not in {"null", self.origin}:
raise DouyinError("restricted browser origin changed")
- cdp.command("Page.navigate", {"url": target})
- event = cdp.wait_event(
- "Page.frameNavigated",
- lambda params: _is_xiaohongshu_page_url(
- params.get("frame", {}).get("url", "")
- ),
+ navigation = cdp.evaluate(
+ "window.location.assign(" + json.dumps(target) + "); true"
)
- final_url = event.get("frame", {}).get("url")
- if not isinstance(final_url, str) or not _is_xiaohongshu_page_url(final_url):
+ if not isinstance(navigation, bool) or not navigation:
+ raise DouyinError("Xiaohongshu share URL navigation failed")
+ deadline = time.monotonic() + CONTROL_TIMEOUT
+ final_url = ""
+ while time.monotonic() < deadline:
+ try:
+ current_url = cdp.evaluate("location.href")
+ except BrowserError:
+ if _is_xiaohongshu_page_url(target):
+ final_url = target
+ break
+ raise
+ if isinstance(current_url, str) and _is_xiaohongshu_page_url(current_url):
+ final_url = current_url
+ break
+ time.sleep(0.1)
+ if not final_url:
raise DouyinError(
"Xiaohongshu share URL did not resolve to a supported page"
)
diff --git a/browser_gateway/server/http.py b/browser_gateway/server/http.py
index 53b8796..0eaa638 100644
--- a/browser_gateway/server/http.py
+++ b/browser_gateway/server/http.py
@@ -199,8 +199,6 @@ class Gateway:
target = input.get("url", "")
if not valid_douyin_generation(input) or not valid_douyin_source_url(target):
raise RequestError("invalid restricted Douyin source URL", 400)
- if is_douyin_content_url(target):
- return {"url": target}
with self._alias_lock(alias):
self._require_douyin_generation(alias, input)
try:
diff --git a/browser_gateway/test_gateway.py b/browser_gateway/test_gateway.py
index f5d5e97..c024558 100644
--- a/browser_gateway/test_gateway.py
+++ b/browser_gateway/test_gateway.py
@@ -460,6 +460,16 @@ class GatewayBusinessMethodTests(unittest.TestCase):
)["url"],
"https://www.douyin.com/video/123",
)
+ self.browser.resolve.reset_mock()
+ self.assertEqual(
+ self.gateway.resolve_douyin(
+ "safe", {**self.generation, "url": "https://www.douyin.com/video/123"}
+ )["url"],
+ "https://www.douyin.com/video/123",
+ )
+ self.browser.resolve.assert_called_once_with(
+ "safe", "https://www.douyin.com/video/123"
+ )
self.assertEqual(
self.gateway.get_douyin_media(
"safe", {**self.generation, "url": "https://cdn.example/video.mp4"}
@@ -757,6 +767,26 @@ class BrowserTests(unittest.TestCase):
cast(Any, browser).connection = bound
+ def test_resolve_initializes_anonymous_blank_runtime(self) -> None:
+ cdp = Mock()
+ cdp.evaluate.side_effect = ["null", True, "https://www.douyin.com/video/123"]
+ cdp.command.return_value = {"frameId": "frame-1"}
+ cdp.wait_event.return_value = {
+ "frame": {"url": "https://www.douyin.com/video/123"}
+ }
+ browser = DouyinBrowser()
+ self._with_connection(browser, cast(BrowserCDP, cdp))
+
+ self.assertEqual(
+ browser.resolve("safe", "https://www.douyin.com/video/123"),
+ "https://www.douyin.com/video/123",
+ )
+ self.assertEqual(cdp.evaluate.call_count, 3)
+ self.assertEqual(cdp.evaluate.call_args_list[0].args, ("location.origin",))
+ self.assertIn(
+ "window.location.assign(\"https://www.douyin.com/video/123\")",
+ cdp.evaluate.call_args_list[1].args[0],
+ )
def test_browser_fetch_uses_manually_logged_session(self) -> None:
cdp = BrowserCDP(
[
@@ -772,6 +802,26 @@ class BrowserTests(unittest.TestCase):
self.assertEqual(response.status, 200)
self.assertNotIn("Network.setCookies", [method for method, _ in cdp.commands])
+ def test_browser_fetch_retries_transient_auth_response(self) -> None:
+ cdp = BrowserCDP(
+ [
+ "https://www.douyin.com",
+ {"status": 403, "body": "challenge", "too_large": False},
+ {"status": 200, "body": "{}", "too_large": False},
+ ]
+ )
+ browser = DouyinBrowser()
+ self._with_connection(browser, cdp)
+
+ with patch("browser_gateway.platform.douyin.time.sleep") as sleep:
+ response = browser.get(
+ "safe",
+ "https://www.douyin.com/aweme/v1/web/user/profile/self/?aid=6383",
+ )
+
+ self.assertEqual(response.status, 200)
+ sleep.assert_called_once_with(2)
+
def test_login_qr_captures_a_browser_screen_without_credentials(self) -> None:
screenshot = base64.b64encode(b"png-bytes" * 3000).decode("ascii")
cdp = Mock()
diff --git a/browser_gateway/test_xiaohongshu.py b/browser_gateway/test_xiaohongshu.py
index 2df5123..88ed782 100644
--- a/browser_gateway/test_xiaohongshu.py
+++ b/browser_gateway/test_xiaohongshu.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import unittest
+from contextlib import contextmanager
from typing import Any, cast
from unittest.mock import Mock
@@ -86,6 +87,32 @@ class XiaohongshuValidationTests(unittest.TestCase):
)
+class XiaohongshuBrowserTests(unittest.TestCase):
+ def test_resolve_allows_anonymous_blank_runtime(self) -> None:
+ browser = xiaohongshu_module.XiaohongshuBrowser()
+ cdp = Mock()
+ cdp.evaluate.side_effect = [
+ "null",
+ True,
+ "https://www.xiaohongshu.com/explore/n-1",
+ ]
+ cdp.command.return_value = {"frameId": "frame-1"}
+ cdp.wait_event.return_value = {
+ "frame": {"url": "https://www.xiaohongshu.com/explore/n-1"}
+ }
+
+ @contextmanager
+ def bound(alias: str):
+ del alias
+ yield cdp
+
+ cast(Any, browser).connection = bound
+ self.assertEqual(
+ browser.resolve("safe", "https://xhslink.com/a/abc"),
+ "https://www.xiaohongshu.com/explore/n-1",
+ )
+
+
class XiaohongshuRouteTests(unittest.TestCase):
def test_read_only_routes_dispatch_without_action_or_event_routes(self) -> None:
handler = gateway_module.GatewayHandler.__new__(gateway_module.GatewayHandler)
diff --git a/docs/evidence/tanstack-start-audit-baseline-7.json b/docs/evidence/tanstack-start-audit-baseline-7.json
new file mode 100644
index 0000000..92826d0
--- /dev/null
+++ b/docs/evidence/tanstack-start-audit-baseline-7.json
@@ -0,0 +1,24 @@
+{
+ "purpose": "Historical dependency-audit evidence for the migration scope; this is not the current lockfile.",
+ "baseline": {
+ "total": 7,
+ "high": 1,
+ "moderate": 5,
+ "low": 1,
+ "source": "npm audit --json captured during the migration"
+ },
+ "intermediate": {
+ "total": 1,
+ "high": 0,
+ "moderate": 0,
+ "low": 1
+ },
+ "current": {
+ "total": 0,
+ "high": 0,
+ "moderate": 0,
+ "low": 0,
+ "command": "npm --prefix web audit --audit-level=low"
+ },
+ "scopeDecision": "Do not restore the vulnerable baseline. The current development lockfile remains at zero vulnerabilities; production security review may be repeated before release."
+}
diff --git a/docs/evidence/tanstack-start-build-output.log b/docs/evidence/tanstack-start-build-output.log
new file mode 100644
index 0000000..9f76dbf
--- /dev/null
+++ b/docs/evidence/tanstack-start-build-output.log
@@ -0,0 +1,264 @@
+
+> creator-hub-web@0.1.0 prebuild
+> npm run routes:generate
+
+
+> creator-hub-web@0.1.0 routes:generate
+> node scripts/generate-route-tree.mjs
+
+
+> creator-hub-web@0.1.0 build
+> vite build
+
+vite v7.3.6 building client environment for production...
+transforming...
+✓ 2261 modules transformed.
+rendering chunks...
+computing gzip size...
+.output/public/assets/globals-BfHS0732.css 60.37 kB │ gzip: 10.83 kB
+.output/public/assets/index-CsG5DCGJ.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/_id-CsG5DCGJ.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/index-Co8OtIp4.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/_id-Co8OtIp4.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/index-D-Mcm3hL.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-Dtmjdwjk.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-DudAQX-N.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-aVngqhpK.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/new-DGos6zmu.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-Bvqigct7.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-3ygCt6wx.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/new-BrYhFgyO.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-C58ZziXt.js 0.20 kB │ gzip: 0.17 kB
+.output/public/assets/_id-Py15Lyn1.js 0.20 kB │ gzip: 0.17 kB
+.output/public/assets/router-BcqWrx6N.js 1.17 kB │ gzip: 0.58 kB
+.output/public/assets/_id-D2cfGIqE.js 1.55 kB │ gzip: 0.83 kB
+.output/public/assets/login-eNihrTGY.js 1.97 kB │ gzip: 1.12 kB
+.output/public/assets/settings-1UHFaEu3.js 5.24 kB │ gzip: 1.83 kB
+.output/public/assets/audit-JYBfP0wO.js 5.48 kB │ gzip: 2.26 kB
+.output/public/assets/_id-7qY5OilA.js 6.36 kB │ gzip: 2.57 kB
+.output/public/assets/gateways-DOWh8guy.js 6.66 kB │ gzip: 2.72 kB
+.output/public/assets/browser-versions-BAnXMdlg.js 6.90 kB │ gzip: 2.49 kB
+.output/public/assets/TasksPage-a7e5aXfT.js 9.92 kB │ gzip: 3.66 kB
+.output/public/assets/NetworkExitsPage-CqVKiFZM.js 10.58 kB │ gzip: 4.02 kB
+.output/public/assets/edit-BSed3AXC.js 16.15 kB │ gzip: 5.38 kB
+.output/public/assets/hooks-CUARYhEu.js 16.80 kB │ gzip: 6.18 kB
+.output/public/assets/CreatorCompetitorsPage-C4XKesLt.js 16.83 kB │ gzip: 5.57 kB
+.output/public/assets/BrowsersPage-BKi6Y14m.js 17.33 kB │ gzip: 5.93 kB
+.output/public/assets/AccountsPage-C8TEUDI1.js 17.47 kB │ gzip: 6.19 kB
+.output/public/assets/workbench-CK1in_8p.js 24.87 kB │ gzip: 7.50 kB
+.output/public/assets/ui-CllpnY_H.js 40.61 kB │ gzip: 13.02 kB
+.output/public/assets/_app-BZqnU8qZ.js 97.09 kB │ gzip: 32.83 kB
+.output/public/assets/index-Cza8C3Wx.js 352.53 kB │ gzip: 113.91 kB
+✓ built in 6.72s
+vite v7.3.6 building ssr environment for production...
+transforming...
+✓ 132 modules transformed.
+rendering chunks...
+node_modules/.nitro/vite/services/ssr/assets/globals-BfHS0732.css 60.37 kB
+node_modules/.nitro/vite/services/ssr/assets/empty-plugin-adapters-BFgPZ6_d.js 0.13 kB
+node_modules/.nitro/vite/services/ssr/assets/index-Bt2NgmdU.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-Bt2NgmdU.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/index-Ubo3_j4B.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-Ubo3_j4B.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/index-B_YNwDp_.js 0.39 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-C1xcg7PO.js 0.39 kB
+node_modules/.nitro/vite/services/ssr/assets/index-C9-OnRpT.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/index-HGOJgejq.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-Bl6WkXu4.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-DdmeO6sY.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/new-jJmcUGJh.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/new-Db5H0C08.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/index-DZcqe8yj.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-CpDfhj4N.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/start-2OBjz-AK.js 1.25 kB
+node_modules/.nitro/vite/services/ssr/assets/router-BEAgmVL5.js 2.29 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-Dl9i5bv2.js 2.68 kB
+node_modules/.nitro/vite/services/ssr/assets/login-iHRtR4Q8.js 3.49 kB
+node_modules/.nitro/vite/services/ssr/assets/_tanstack-start-manifest_v-Bq5WSLzH.js 6.16 kB
+node_modules/.nitro/vite/services/ssr/assets/audit-jUy15DQG.js 9.29 kB
+node_modules/.nitro/vite/services/ssr/assets/settings-BEX0DcYG.js 9.59 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-C0H2ocCN.js 11.01 kB
+node_modules/.nitro/vite/services/ssr/assets/router-DMSpT4eb.js 12.00 kB
+node_modules/.nitro/vite/services/ssr/assets/hooks-CC99r3Ze.js 13.38 kB
+node_modules/.nitro/vite/services/ssr/assets/gateways-XY4c2UYn.js 13.60 kB
+node_modules/.nitro/vite/services/ssr/assets/browser-versions-CIndr7jQ.js 14.61 kB
+node_modules/.nitro/vite/services/ssr/assets/TasksPage-Dikv0xE0.js 15.26 kB
+node_modules/.nitro/vite/services/ssr/assets/NetworkExitsPage-BAadxOy8.js 20.71 kB
+node_modules/.nitro/vite/services/ssr/assets/ui-B5IXvEeg.js 22.81 kB
+node_modules/.nitro/vite/services/ssr/assets/_app-JzZmo9on.js 28.72 kB
+node_modules/.nitro/vite/services/ssr/assets/BrowsersPage-CDJ0_wMN.js 34.02 kB
+node_modules/.nitro/vite/services/ssr/assets/CreatorCompetitorsPage-B31dablC.js 34.28 kB
+node_modules/.nitro/vite/services/ssr/assets/AccountsPage-CUk7RhyZ.js 35.33 kB
+node_modules/.nitro/vite/services/ssr/assets/edit-Bz3UEY_p.js 35.98 kB
+node_modules/.nitro/vite/services/ssr/assets/workbench-COeyzdj3.js 52.24 kB
+node_modules/.nitro/vite/services/ssr/index.js 67.00 kB
+✓ built in 1.49s
+
+[nitro] ◐ Building [Nitro] (preset: node-server, compatibility: 2026-09-18)
+[nitro] ✔ Generated public .output/public
+vite v7.3.6 building nitro environment for production...
+transforming...
+✓ 2282 modules transformed.
+ℹ Tracing dependencies:
+- tslib (2.8.1)
+✔ Traced 1 dependencies (4 files) in 255ms.
+ℹ Ensure your production environment matches the builder OS and architecture (linux-x64) to avoid native module issues.
+Generated an empty chunk: "_libs/crossws".
+Generated an empty chunk: "_libs/hookable".
+Generated an empty chunk: "_libs/unctx".
+Generated an empty chunk: "_libs/ohash".
+Generated an empty chunk: "_libs/ocache".
+Generated an empty chunk: "_libs/unstorage".
+Generated an empty chunk: "_libs/croner".
+Generated an empty chunk: "_libs/@tanstack/store".
+Generated an empty chunk: "_libs/use-sync-external-store".
+Generated an empty chunk: "_libs/detect-node-es".
+Generated an empty chunk: "_libs/@radix-ui/number".
+Generated an empty chunk: "_libs/radix-ui".
+Generated an empty chunk: "_libs/tslib".
+rendering chunks...
+.output/server/_libs/crossws.mjs 0.00 kB
+.output/server/_libs/hookable.mjs 0.00 kB
+.output/server/_libs/unctx.mjs 0.00 kB
+.output/server/_libs/ohash.mjs 0.00 kB
+.output/server/_libs/ocache.mjs 0.00 kB
+.output/server/_libs/unstorage.mjs 0.00 kB
+.output/server/_libs/croner.mjs 0.00 kB
+.output/server/_libs/tanstack__store.mjs 0.00 kB
+.output/server/_libs/use-sync-external-store.mjs 0.00 kB
+.output/server/_libs/detect-node-es.mjs 0.00 kB
+.output/server/_libs/radix-ui__number.mjs 0.00 kB
+.output/server/_libs/radix-ui.mjs 0.00 kB
+.output/server/_libs/tslib.mjs 0.00 kB
+.output/server/_libs/tanstack__react-store.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-collection.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-collapsible.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-direction.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-accordion.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-alert-dialog.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-aspect-ratio.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-avatar.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-checkbox.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-roving-focus.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-context-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-dropdown-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-label.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-form.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-hover-card.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-menubar.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-use-previous.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-popover.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-progress.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-radio-group.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-scroll-area.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-slider.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-switch.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-tabs.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toggle.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toggle-group.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toolbar.mjs 0.02 kB
+.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs 0.02 kB
+.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs 0.02 kB
+.output/server/_libs/radix-ui__react-select.mjs 0.05 kB
+.output/server/_libs/radix-ui__react-toast.mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs 0.05 kB
+.output/server/_ssr/empty-plugin-adapters-BFgPZ6_d.mjs 0.13 kB
+.output/server/_libs/get-nonce.mjs 0.16 kB
+.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs 0.17 kB
+.output/server/_libs/rou3.mjs 0.40 kB
+.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs 0.51 kB
+.output/server/_libs/clsx.mjs 0.51 kB
+.output/server/_ssr/index-Bt2NgmdU.mjs 0.52 kB
+.output/server/_ssr/index-Ubo3_j4B.mjs 0.52 kB
+.output/server/_id-Bt2NgmdU.mjs 0.53 kB
+.output/server/_id-Ubo3_j4B.mjs 0.53 kB
+.output/server/_chunks/ssr-renderer.mjs 0.61 kB
+.output/server/_libs/radix-ui__react-id.mjs 0.69 kB
+.output/server/_ssr/index-B_YNwDp_.mjs 0.80 kB
+.output/server/_ssr/index-C9-OnRpT.mjs 0.81 kB
+.output/server/_ssr/index-HGOJgejq.mjs 0.81 kB
+.output/server/_id-C1xcg7PO.mjs 0.81 kB
+.output/server/_id-Bl6WkXu4.mjs 0.82 kB
+.output/server/_id-DdmeO6sY.mjs 0.82 kB
+.output/server/_ssr/new-jJmcUGJh.mjs 0.82 kB
+.output/server/_ssr/new-Db5H0C08.mjs 0.82 kB
+.output/server/_ssr/index-DZcqe8yj.mjs 0.82 kB
+.output/server/_id-CpDfhj4N.mjs 0.83 kB
+.output/server/_libs/radix-ui__react-arrow.mjs 0.88 kB
+.output/server/_libs/radix-ui__react-portal.mjs 0.99 kB
+.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs 1.01 kB
+.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs 1.04 kB
+.output/server/_libs/radix-ui__react-compose-refs.mjs 1.17 kB
+.output/server/_libs/radix-ui__react-separator.mjs 1.29 kB
+.output/server/_libs/ufo.mjs 1.31 kB
+.output/server/_libs/radix-ui__react-primitive.mjs 1.32 kB
+.output/server/_libs/radix-ui__react-focus-guards.mjs 1.41 kB
+.output/server/_libs/radix-ui__react-use-size.mjs 1.42 kB
+.output/server/_libs/cookie-es.mjs 1.46 kB
+.output/server/_ssr/start-2OBjz-AK.mjs 1.54 kB
+.output/server/_libs/react-style-singleton.mjs 1.67 kB
+.output/server/_libs/use-callback-ref.mjs 1.75 kB
+.output/server/_libs/radix-ui__primitive.mjs 1.86 kB
+.output/server/_libs/tanstack__history.mjs 2.04 kB
+.output/server/_libs/class-variance-authority.mjs 2.14 kB
+.output/server/_ssr/router-BEAgmVL5.mjs 2.38 kB
+.output/server/_libs/use-sidecar.mjs 2.65 kB
+.output/server/_libs/isbot.mjs 2.83 kB
+.output/server/_libs/seroval-plugins.mjs 2.96 kB
+.output/server/_id-Dl9i5bv2.mjs 3.37 kB
+.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs 3.76 kB
+.output/server/_libs/react-remove-scroll-bar.mjs 3.96 kB
+.output/server/_libs/aria-hidden.mjs 4.06 kB
+.output/server/_ssr/login-iHRtR4Q8.mjs 4.12 kB
+.output/server/_libs/radix-ui__react-context.mjs 4.30 kB
+.output/server/_libs/@tanstack/react-router-with-query+[...].mjs 5.06 kB
+.output/server/_libs/tanstack__react-query.mjs 5.98 kB
+.output/server/_tanstack-start-manifest_v-Bq5WSLzH.mjs 6.16 kB
+.output/server/_libs/radix-ui__react-slot.mjs 6.23 kB
+.output/server/_libs/radix-ui__react-presence.mjs 6.56 kB
+.output/server/_libs/lucide-react.mjs 7.26 kB
+.output/server/_libs/floating-ui__react-dom.mjs 8.42 kB
+.output/server/_libs/radix-ui__react-focus-scope.mjs 9.27 kB
+.output/server/_libs/floating-ui__utils.mjs 9.73 kB
+.output/server/_libs/h3-v2.mjs 9.98 kB
+.output/server/_ssr/settings-BEX0DcYG.mjs 10.55 kB
+.output/server/_ssr/audit-jUy15DQG.mjs 10.59 kB
+.output/server/_ssr/router-DMSpT4eb.mjs 12.28 kB
+.output/server/_libs/radix-ui__react-dialog.mjs 12.40 kB
+.output/server/_id-C0H2ocCN.mjs 12.43 kB
+.output/server/_libs/react-remove-scroll.mjs 13.37 kB
+.output/server/_ssr/hooks-CC99r3Ze.mjs 13.47 kB
+.output/server/_libs/radix-ui__react-popper.mjs 13.91 kB
+.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs 14.28 kB
+.output/server/_ssr/gateways-XY4c2UYn.mjs 14.65 kB
+.output/server/_ssr/browser-versions-CIndr7jQ.mjs 15.65 kB
+.output/server/_ssr/TasksPage-Dikv0xE0.mjs 16.94 kB
+.output/server/_libs/h3.mjs 17.53 kB
+.output/server/index.mjs 18.49 kB
+.output/server/_libs/react.mjs 19.93 kB
+.output/server/_libs/floating-ui__dom.mjs 20.85 kB
+.output/server/_libs/radix-ui__react-tooltip.mjs 20.98 kB
+.output/server/_ssr/NetworkExitsPage-BAadxOy8.mjs 22.07 kB
+.output/server/_libs/floating-ui__core.mjs 23.42 kB
+.output/server/_ssr/ui-B5IXvEeg.mjs 24.41 kB
+.output/server/_app-JzZmo9on.mjs 32.54 kB
+.output/server/_ssr/BrowsersPage-CDJ0_wMN.mjs 36.60 kB
+.output/server/_libs/tanstack__react-router.mjs 36.61 kB
+.output/server/_ssr/CreatorCompetitorsPage-B31dablC.mjs 36.88 kB
+.output/server/_ssr/AccountsPage-CUk7RhyZ.mjs 37.62 kB
+.output/server/_ssr/edit-Bz3UEY_p.mjs 38.22 kB
+.output/server/_libs/srvx.mjs 44.32 kB
+.output/server/_ssr/workbench-COeyzdj3.mjs 55.91 kB
+.output/server/_ssr/index.mjs 66.70 kB
+.output/server/_libs/tanstack__query-core.mjs 76.27 kB
+.output/server/_libs/seroval.mjs 94.94 kB
+.output/server/_libs/tailwind-merge.mjs 99.21 kB
+.output/server/_libs/tanstack__router-core.mjs 179.81 kB
+.output/server/_libs/react-dom.mjs 509.08 kB
+✓ built in 9.53s
+ℹ Generated .output/nitro.json
+
+[nitro] ✔ You can preview this build using npx vite preview
diff --git a/docs/evidence/tanstack-start-build-warning-baseline.log b/docs/evidence/tanstack-start-build-warning-baseline.log
new file mode 100644
index 0000000..8d1681e
--- /dev/null
+++ b/docs/evidence/tanstack-start-build-warning-baseline.log
@@ -0,0 +1,271 @@
+
+> creator-hub-web@0.1.0 prebuild
+> npm run routes:generate
+
+
+> creator-hub-web@0.1.0 routes:generate
+> node scripts/generate-route-tree.mjs
+
+
+> creator-hub-web@0.1.0 build
+> vite build
+
+vite v7.3.6 building client environment for production...
+transforming...
+✓ 2261 modules transformed.
+rendering chunks...
+computing gzip size...
+.output/public/assets/globals-BfHS0732.css 60.37 kB │ gzip: 10.83 kB
+.output/public/assets/index-CsG5DCGJ.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/_id-CsG5DCGJ.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/index-Co8OtIp4.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/_id-Co8OtIp4.js 0.17 kB │ gzip: 0.15 kB
+.output/public/assets/index-D-Mcm3hL.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-Dtmjdwjk.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-DudAQX-N.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-aVngqhpK.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/new-DGos6zmu.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-Bvqigct7.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/_id-3ygCt6wx.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/new-BrYhFgyO.js 0.19 kB │ gzip: 0.16 kB
+.output/public/assets/index-C58ZziXt.js 0.20 kB │ gzip: 0.17 kB
+.output/public/assets/_id-Py15Lyn1.js 0.20 kB │ gzip: 0.17 kB
+.output/public/assets/router-BcqWrx6N.js 1.17 kB │ gzip: 0.58 kB
+.output/public/assets/_id-D2cfGIqE.js 1.55 kB │ gzip: 0.83 kB
+.output/public/assets/login-eNihrTGY.js 1.97 kB │ gzip: 1.12 kB
+.output/public/assets/settings-1UHFaEu3.js 5.24 kB │ gzip: 1.83 kB
+.output/public/assets/audit-JYBfP0wO.js 5.48 kB │ gzip: 2.26 kB
+.output/public/assets/_id-7qY5OilA.js 6.36 kB │ gzip: 2.57 kB
+.output/public/assets/gateways-DOWh8guy.js 6.66 kB │ gzip: 2.72 kB
+.output/public/assets/browser-versions-BAnXMdlg.js 6.90 kB │ gzip: 2.49 kB
+.output/public/assets/TasksPage-a7e5aXfT.js 9.92 kB │ gzip: 3.66 kB
+.output/public/assets/NetworkExitsPage-CqVKiFZM.js 10.58 kB │ gzip: 4.02 kB
+.output/public/assets/edit-BSed3AXC.js 16.15 kB │ gzip: 5.38 kB
+.output/public/assets/hooks-CUARYhEu.js 16.80 kB │ gzip: 6.18 kB
+.output/public/assets/CreatorCompetitorsPage-C4XKesLt.js 16.83 kB │ gzip: 5.57 kB
+.output/public/assets/BrowsersPage-BKi6Y14m.js 17.33 kB │ gzip: 5.93 kB
+.output/public/assets/AccountsPage-C8TEUDI1.js 17.47 kB │ gzip: 6.19 kB
+.output/public/assets/workbench-CK1in_8p.js 24.87 kB │ gzip: 7.50 kB
+.output/public/assets/ui-CllpnY_H.js 40.61 kB │ gzip: 13.02 kB
+.output/public/assets/_app-BZqnU8qZ.js 97.09 kB │ gzip: 32.83 kB
+.output/public/assets/index-Cza8C3Wx.js 352.53 kB │ gzip: 113.91 kB
+✓ built in 6.45s
+vite v7.3.6 building ssr environment for production...
+transforming...
+src/components/ui/sidebar.tsx (1:0): Error when using sourcemap for reporting an error: Can't resolve original location of error.
+src/components/ui/sidebar.tsx (1:0): Module level directives cause errors when bundled, "use client" in "src/components/ui/sidebar.tsx" was ignored.
+src/components/ui/sheet.tsx (1:0): Error when using sourcemap for reporting an error: Can't resolve original location of error.
+src/components/ui/sheet.tsx (1:0): Module level directives cause errors when bundled, "use client" in "src/components/ui/sheet.tsx" was ignored.
+"createRequestHandler", "defineHandlerCallback", "transformPipeableStreamWithRouter" and "transformReadableStreamWithRouter" are imported from external module "@tanstack/router-core/ssr/server" but never used in "node_modules/@tanstack/start-server-core/dist/esm/index.js".
+"RawStream" is imported from external module "@tanstack/router-core" but never used in "node_modules/@tanstack/start-client-core/dist/esm/index.js".
+"defaultSerovalDeserializerPlugins", "hydrate" and "json" are imported from external module "@tanstack/router-core/ssr/client" but never used in "node_modules/@tanstack/start-client-core/dist/esm/index.js".
+✓ 132 modules transformed.
+rendering chunks...
+node_modules/.nitro/vite/services/ssr/assets/globals-BfHS0732.css 60.37 kB
+node_modules/.nitro/vite/services/ssr/assets/empty-plugin-adapters-BFgPZ6_d.js 0.13 kB
+node_modules/.nitro/vite/services/ssr/assets/index-DKoErcTm.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-DKoErcTm.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/index-BZV1FRa9.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-BZV1FRa9.js 0.37 kB
+node_modules/.nitro/vite/services/ssr/assets/index-CK0kGtkO.js 0.39 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-txubC_JC.js 0.39 kB
+node_modules/.nitro/vite/services/ssr/assets/index-BFUsw4_9.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/index-Cb30TTqs.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-NE7jq5BM.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-CCnHXj2F.js 0.40 kB
+node_modules/.nitro/vite/services/ssr/assets/index-CsRL0VMh.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/new-Bbx3I4tu.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/new-CiK3GDVQ.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-BqIX37IZ.js 0.41 kB
+node_modules/.nitro/vite/services/ssr/assets/start-2OBjz-AK.js 1.25 kB
+node_modules/.nitro/vite/services/ssr/assets/router-BSB_r9HV.js 2.29 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-DNz3urOX.js 2.68 kB
+node_modules/.nitro/vite/services/ssr/assets/login-BmGCIkoR.js 3.49 kB
+node_modules/.nitro/vite/services/ssr/assets/_tanstack-start-manifest_v-Bq5WSLzH.js 6.16 kB
+node_modules/.nitro/vite/services/ssr/assets/audit-DhPmgklq.js 9.29 kB
+node_modules/.nitro/vite/services/ssr/assets/settings-TGJoXXpX.js 9.59 kB
+node_modules/.nitro/vite/services/ssr/assets/_id-5ZqDRRtm.js 11.01 kB
+node_modules/.nitro/vite/services/ssr/assets/router-Qws9csAl.js 12.00 kB
+node_modules/.nitro/vite/services/ssr/assets/hooks-DjY8n3Bq.js 13.38 kB
+node_modules/.nitro/vite/services/ssr/assets/gateways-DKadGH4_.js 13.60 kB
+node_modules/.nitro/vite/services/ssr/assets/browser-versions-BT7WG-Jb.js 14.61 kB
+node_modules/.nitro/vite/services/ssr/assets/TasksPage-F4Fycrrf.js 15.26 kB
+node_modules/.nitro/vite/services/ssr/assets/NetworkExitsPage-CKSCBKj3.js 20.71 kB
+node_modules/.nitro/vite/services/ssr/assets/ui-Csle2U8f.js 22.81 kB
+node_modules/.nitro/vite/services/ssr/assets/_app-Boy3ZtCu.js 28.72 kB
+node_modules/.nitro/vite/services/ssr/assets/BrowsersPage-cokOjmpd.js 34.02 kB
+node_modules/.nitro/vite/services/ssr/assets/CreatorCompetitorsPage-raR88Dhj.js 34.28 kB
+node_modules/.nitro/vite/services/ssr/assets/AccountsPage-30_KTVlS.js 35.33 kB
+node_modules/.nitro/vite/services/ssr/assets/edit-DzV-rjqZ.js 35.98 kB
+node_modules/.nitro/vite/services/ssr/assets/workbench-GgbI_GP7.js 52.24 kB
+node_modules/.nitro/vite/services/ssr/index.js 67.00 kB
+✓ built in 1.29s
+
+[nitro] ◐ Building [Nitro] (preset: node-server, compatibility: 2026-09-18)
+[nitro] ✔ Generated public .output/public
+vite v7.3.6 building nitro environment for production...
+transforming...
+✓ 2282 modules transformed.
+ℹ Tracing dependencies:
+- tslib (2.8.1)
+✔ Traced 1 dependencies (4 files) in 407ms.
+ℹ Ensure your production environment matches the builder OS and architecture (linux-x64) to avoid native module issues.
+Generated an empty chunk: "_libs/crossws".
+Generated an empty chunk: "_libs/hookable".
+Generated an empty chunk: "_libs/unctx".
+Generated an empty chunk: "_libs/ohash".
+Generated an empty chunk: "_libs/ocache".
+Generated an empty chunk: "_libs/unstorage".
+Generated an empty chunk: "_libs/croner".
+Generated an empty chunk: "_libs/@tanstack/store".
+Generated an empty chunk: "_libs/use-sync-external-store".
+Generated an empty chunk: "_libs/detect-node-es".
+Generated an empty chunk: "_libs/@radix-ui/number".
+Generated an empty chunk: "_libs/radix-ui".
+Generated an empty chunk: "_libs/tslib".
+rendering chunks...
+.output/server/_libs/crossws.mjs 0.00 kB
+.output/server/_libs/hookable.mjs 0.00 kB
+.output/server/_libs/unctx.mjs 0.00 kB
+.output/server/_libs/ohash.mjs 0.00 kB
+.output/server/_libs/ocache.mjs 0.00 kB
+.output/server/_libs/unstorage.mjs 0.00 kB
+.output/server/_libs/croner.mjs 0.00 kB
+.output/server/_libs/tanstack__store.mjs 0.00 kB
+.output/server/_libs/use-sync-external-store.mjs 0.00 kB
+.output/server/_libs/detect-node-es.mjs 0.00 kB
+.output/server/_libs/radix-ui__number.mjs 0.00 kB
+.output/server/_libs/radix-ui.mjs 0.00 kB
+.output/server/_libs/tslib.mjs 0.00 kB
+.output/server/_libs/tanstack__react-store.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-collection.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-collapsible.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-direction.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-accordion.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-alert-dialog.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-aspect-ratio.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-avatar.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-checkbox.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-roving-focus.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-context-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-dropdown-menu.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-label.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-form.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-hover-card.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-menubar.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-use-previous.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-popover.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-progress.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-radio-group.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-scroll-area.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-slider.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-switch.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-tabs.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toggle.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toggle-group.mjs 0.02 kB
+.output/server/_libs/radix-ui__react-toolbar.mjs 0.02 kB
+.output/server/_libs/@radix-ui/react-accessible-icon+[...].mjs 0.02 kB
+.output/server/_libs/@radix-ui/react-use-is-hydrated+[...].mjs 0.02 kB
+.output/server/_libs/radix-ui__react-select.mjs 0.05 kB
+.output/server/_libs/radix-ui__react-toast.mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-navigation-menu+[...].mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-one-time-password-field+[...].mjs 0.05 kB
+.output/server/_libs/@radix-ui/react-password-toggle-field+[...].mjs 0.05 kB
+.output/server/_ssr/empty-plugin-adapters-BFgPZ6_d.mjs 0.13 kB
+.output/server/_libs/get-nonce.mjs 0.16 kB
+.output/server/_libs/@radix-ui/react-use-layout-effect+[...].mjs 0.17 kB
+.output/server/_libs/rou3.mjs 0.40 kB
+.output/server/_libs/@radix-ui/react-use-callback-ref+[...].mjs 0.51 kB
+.output/server/_libs/clsx.mjs 0.51 kB
+.output/server/_ssr/index-DKoErcTm.mjs 0.52 kB
+.output/server/_ssr/index-BZV1FRa9.mjs 0.52 kB
+.output/server/_id-DKoErcTm.mjs 0.53 kB
+.output/server/_id-BZV1FRa9.mjs 0.53 kB
+.output/server/_chunks/ssr-renderer.mjs 0.61 kB
+.output/server/_libs/radix-ui__react-id.mjs 0.69 kB
+.output/server/_ssr/index-CK0kGtkO.mjs 0.80 kB
+.output/server/_ssr/index-BFUsw4_9.mjs 0.81 kB
+.output/server/_ssr/index-Cb30TTqs.mjs 0.81 kB
+.output/server/_id-txubC_JC.mjs 0.81 kB
+.output/server/_id-NE7jq5BM.mjs 0.82 kB
+.output/server/_id-CCnHXj2F.mjs 0.82 kB
+.output/server/_ssr/index-CsRL0VMh.mjs 0.82 kB
+.output/server/_ssr/new-Bbx3I4tu.mjs 0.82 kB
+.output/server/_ssr/new-CiK3GDVQ.mjs 0.82 kB
+.output/server/_id-BqIX37IZ.mjs 0.83 kB
+.output/server/_libs/radix-ui__react-arrow.mjs 0.88 kB
+.output/server/_libs/radix-ui__react-portal.mjs 0.99 kB
+.output/server/_libs/@radix-ui/react-visually-hidden+[...].mjs 1.01 kB
+.output/server/_libs/@radix-ui/react-use-effect-event+[...].mjs 1.04 kB
+.output/server/_libs/radix-ui__react-compose-refs.mjs 1.17 kB
+.output/server/_libs/radix-ui__react-separator.mjs 1.29 kB
+.output/server/_libs/ufo.mjs 1.31 kB
+.output/server/_libs/radix-ui__react-primitive.mjs 1.32 kB
+.output/server/_libs/radix-ui__react-focus-guards.mjs 1.41 kB
+.output/server/_libs/radix-ui__react-use-size.mjs 1.42 kB
+.output/server/_libs/cookie-es.mjs 1.46 kB
+.output/server/_ssr/start-2OBjz-AK.mjs 1.54 kB
+.output/server/_libs/react-style-singleton.mjs 1.67 kB
+.output/server/_libs/use-callback-ref.mjs 1.75 kB
+.output/server/_libs/radix-ui__primitive.mjs 1.86 kB
+.output/server/_libs/tanstack__history.mjs 2.04 kB
+.output/server/_libs/class-variance-authority.mjs 2.14 kB
+.output/server/_ssr/router-BSB_r9HV.mjs 2.38 kB
+.output/server/_libs/use-sidecar.mjs 2.65 kB
+.output/server/_libs/isbot.mjs 2.83 kB
+.output/server/_libs/seroval-plugins.mjs 2.96 kB
+.output/server/_id-DNz3urOX.mjs 3.37 kB
+.output/server/_libs/@radix-ui/react-use-controllable-state+[...].mjs 3.76 kB
+.output/server/_libs/react-remove-scroll-bar.mjs 3.96 kB
+.output/server/_libs/aria-hidden.mjs 4.06 kB
+.output/server/_ssr/login-BmGCIkoR.mjs 4.12 kB
+.output/server/_libs/radix-ui__react-context.mjs 4.30 kB
+.output/server/_libs/@tanstack/react-router-with-query+[...].mjs 5.06 kB
+.output/server/_libs/tanstack__react-query.mjs 5.98 kB
+.output/server/_tanstack-start-manifest_v-Bq5WSLzH.mjs 6.16 kB
+.output/server/_libs/radix-ui__react-slot.mjs 6.23 kB
+.output/server/_libs/radix-ui__react-presence.mjs 6.56 kB
+.output/server/_libs/lucide-react.mjs 7.26 kB
+.output/server/_libs/floating-ui__react-dom.mjs 8.42 kB
+.output/server/_libs/radix-ui__react-focus-scope.mjs 9.27 kB
+.output/server/_libs/floating-ui__utils.mjs 9.73 kB
+.output/server/_libs/h3-v2.mjs 9.98 kB
+.output/server/_ssr/settings-TGJoXXpX.mjs 10.55 kB
+.output/server/_ssr/audit-DhPmgklq.mjs 10.59 kB
+.output/server/_ssr/router-Qws9csAl.mjs 12.28 kB
+.output/server/_libs/radix-ui__react-dialog.mjs 12.40 kB
+.output/server/_id-5ZqDRRtm.mjs 12.43 kB
+.output/server/_libs/react-remove-scroll.mjs 13.37 kB
+.output/server/_ssr/hooks-DjY8n3Bq.mjs 13.47 kB
+.output/server/_libs/radix-ui__react-popper.mjs 13.91 kB
+.output/server/_libs/@radix-ui/react-dismissable-layer+[...].mjs 14.28 kB
+.output/server/_ssr/gateways-DKadGH4_.mjs 14.65 kB
+.output/server/_ssr/browser-versions-BT7WG-Jb.mjs 15.65 kB
+.output/server/_ssr/TasksPage-F4Fycrrf.mjs 16.94 kB
+.output/server/_libs/h3.mjs 17.53 kB
+.output/server/index.mjs 18.49 kB
+.output/server/_libs/react.mjs 19.93 kB
+.output/server/_libs/floating-ui__dom.mjs 20.85 kB
+.output/server/_libs/radix-ui__react-tooltip.mjs 20.98 kB
+.output/server/_ssr/NetworkExitsPage-CKSCBKj3.mjs 22.07 kB
+.output/server/_libs/floating-ui__core.mjs 23.42 kB
+.output/server/_ssr/ui-Csle2U8f.mjs 24.41 kB
+.output/server/_app-Boy3ZtCu.mjs 32.54 kB
+.output/server/_ssr/BrowsersPage-cokOjmpd.mjs 36.60 kB
+.output/server/_libs/tanstack__react-router.mjs 36.61 kB
+.output/server/_ssr/CreatorCompetitorsPage-raR88Dhj.mjs 36.88 kB
+.output/server/_ssr/AccountsPage-30_KTVlS.mjs 37.62 kB
+.output/server/_ssr/edit-DzV-rjqZ.mjs 38.22 kB
+.output/server/_libs/srvx.mjs 44.32 kB
+.output/server/_ssr/workbench-GgbI_GP7.mjs 55.91 kB
+.output/server/_ssr/index.mjs 66.70 kB
+.output/server/_libs/tanstack__query-core.mjs 76.27 kB
+.output/server/_libs/seroval.mjs 94.94 kB
+.output/server/_libs/tailwind-merge.mjs 99.21 kB
+.output/server/_libs/tanstack__router-core.mjs 179.81 kB
+.output/server/_libs/react-dom.mjs 509.08 kB
+✓ built in 9.59s
+ℹ Generated .output/nitro.json
+
+[nitro] ✔ You can preview this build using npx vite preview
diff --git a/docs/evidence/tanstack-start-control-plane-manual-validation.json b/docs/evidence/tanstack-start-control-plane-manual-validation.json
new file mode 100644
index 0000000..c460ed0
--- /dev/null
+++ b/docs/evidence/tanstack-start-control-plane-manual-validation.json
@@ -0,0 +1,90 @@
+{
+ "name": "tanstack-start-control-plane-manual-validation",
+ "branch": "feat/tanstack-start-dashboard",
+ "frontend": "http://127.0.0.1:4176",
+ "controlPlane": "http://127.0.0.1:8082",
+ "browserHarness": {
+ "status": "passed",
+ "finalRegisteredGateway": {
+ "name": "plan01-gateway",
+ "endpoint": "http://127.0.0.1:28187",
+ "lifecycleRecordingPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-registered-native-gateway-lifecycle-final",
+ "lifecycleRecordingFrames": 7,
+ "startState": "运行中 / 可调度 / http://127.0.0.1:19000",
+ "stopState": "已停止 / 运行实例缺失"
+ },
+ "finalCompetitorRecordingPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-anonymous-competitor-final",
+ "recordingPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-pass3",
+ "frames": 50,
+ "actionRecordingPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-actions-pass2",
+ "actionFrames": 82,
+ "nativeEnvironmentRecordingPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-native-environment",
+ "nativeEnvironmentFrames": 8,
+ "nativeEnvironmentPass2Path": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-native-environment-pass2",
+ "nativeEnvironmentPass2Frames": 8,
+ "nativeEnvironmentStopPath": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-native-environment-stop-final",
+ "nativeEnvironmentStopFrames": 8,
+ "credentialsPersisted": false
+ },
+ "flows": [
+ {"name": "login", "path": "/login", "resultPath": "/accounts", "status": "passed"},
+ {"name": "accounts", "path": "/accounts", "heading": "账号管理", "status": "passed"},
+ {"name": "environment", "path": "/browsers", "heading": "运行环境", "status": "passed"},
+ {"name": "competitors", "path": "/creator/competitors", "heading": "竞品分析", "status": "passed", "shareUrl": "https://www.douyin.com/video/7685553258340830507", "preview": "anonymous author preview confirmed", "monitoring": "listener created and enabled", "temporaryRuntimeCleanup": "verified"},
+ {"name": "workbench", "path": "/creator/workbench", "heading": "运营工作台", "status": "passed"},
+ {"name": "tasks", "path": "/tasks", "heading": "任务中心", "status": "passed"},
+ {"name": "audit", "path": "/audit", "heading": "审计记录", "status": "passed"}
+ ],
+ "interactiveActions": {
+ "accountOpenDetail": {"status": "passed", "resultPath": "/accounts/account-committed"},
+ "environmentStart": {
+ "status": "passed",
+ "registeredNativeGateway": "plan01-gateway at 127.0.0.1:28187",
+ "start": "running / dispatchable / CDP http://127.0.0.1:19000",
+ "stop": "stopped / runtime instance missing",
+ "recording": "/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-registered-native-gateway-lifecycle-final"
+ },
+ "competitorPreview": {"status": "passed", "result": "anonymous Douyin author preview confirmed and listener created", "shareUrl": "https://www.douyin.com/video/7685553258340830507", "temporaryRuntimeCleanup": "only stopped plan01-douyin remained after cleanup"},
+ "workbenchTabs": {"status": "passed", "tabs": ["线索", "规则", "事件监听", "私信", "操作记录", "评论"]},
+ "taskFilterApplyClear": {"status": "passed", "value": "account-committed"},
+ "auditFilterExport": {"status": "passed", "event": "environment_action_finished", "export": "sanitized current page clicked"}
+ },
+ "buildEvidence": {
+ "outputPath": "docs/evidence/tanstack-start-build-output.log",
+ "baselineWarningOutputPath": "docs/evidence/tanstack-start-build-warning-baseline.log",
+ "baselineContainsTanStackWarnings": true,
+ "exitStatus": 0,
+ "nonBlockingEmptyChunkWarnings": true
+ },
+ "apiChecks": {
+ "healthz": 204,
+ "readyz": 204,
+ "gateways": 200,
+ "phaseAAccounts": 200,
+ "browsers": 200,
+ "creatorAccounts": 200,
+ "audit": 200,
+ "creatorUpdatesInitialEvent": "retry: 5000",
+ "healthAfterSseDisconnect": 204
+ },
+ "automatedChecks": {
+ "webTests": "101/101",
+ "webCoverageLines": 68.57,
+ "webTypecheck": "passed",
+ "webBuild": "passed",
+ "webAuditVulnerabilities": 0,
+ "auditEvidencePath": "docs/evidence/tanstack-start-audit-baseline-7.json",
+ "gatewayPythonTests": "72 passed",
+ "goTest": "passed",
+ "goRace": "passed",
+ "goVet": "passed",
+ "goBuild": "passed"
+ },
+ "auditBaseline": {
+ "total": 7,
+ "high": 1,
+ "moderate": 5,
+ "low": 1,
+ "status": "resolved by dependency upgrades; current audit is 0"
+ }
+}
diff --git a/docs/evidence/tanstack-start-control-plane-manual-validation.md b/docs/evidence/tanstack-start-control-plane-manual-validation.md
new file mode 100644
index 0000000..e0f7143
--- /dev/null
+++ b/docs/evidence/tanstack-start-control-plane-manual-validation.md
@@ -0,0 +1,91 @@
+# TanStack Start dashboard:真实 control-plane 手工联调记录
+
+> 最终复核已覆盖先前审核指出的两项缺口:原登记的 `plan01-gateway`(`127.0.0.1:28187`)通过真实 UI 完成启动与停止;竞品流程使用匿名临时浏览器完成抖音作者预览与加入监听,并确认临时 runtime 已清理。最终生命周期录制:`/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-registered-native-gateway-lifecycle-final`;竞品录制:`/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-anonymous-competitor-final`。
+
+## 验收范围
+
+- 分支:`feat/tanstack-start-dashboard`
+- 当前源码版本:`45a29ea`(工作区包含未提交的迁移修改)
+- 前端:`http://127.0.0.1:4176`
+- control-plane:`http://127.0.0.1:8082`
+- 验证数据库:`creatorhub-plan01-pg`,宿主端口 `15432`
+- 浏览器工具:`browser-harness`
+- 录制:`/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-pass3`,50 帧
+- 交互录制:`.../creatorhub-tanstack-control-plane-actions-pass2`,82 帧;native 环境启动/停止重试:`.../creatorhub-tanstack-control-plane-native-environment-pass2` 与 `.../creatorhub-tanstack-control-plane-native-environment-stop-final`,各 8 帧
+
+录制文件保留在本机工作区,没有复制进仓库:画面包含本地开发账号、账号标识和环境标识。本文不保存密码、Cookie、token 或录制内容中的敏感数据。
+
+## 手工流程结果
+
+登录前先清除当前标签页的本地会话,再通过 `browser-harness` 的真实输入事件填写登录表单;没有预置 localStorage 登录状态。
+
+| 流程 | 路由 | 验证结果 |
+| --- | --- | --- |
+| 登录 | `/login` → `/accounts` | 跳转成功,会话已建立,无 alert |
+| 账号 | `/accounts` | 显示账号列表与账号操作,无错误文案 |
+| 环境 | `/browsers` | 使用原登记的 `plan01-gateway`(`127.0.0.1:28187`)完成真实启动:页面显示“运行中 / 可调度 / CDP `http://127.0.0.1:19000`”;随后真实停止,页面显示“已停止 / 运行实例缺失” |
+| 竞品 | `/creator/competitors` | 使用 `https://www.douyin.com/video/7685553258340830507` 完成匿名解析,页面显示作者预览并确认作者,随后确认加入监听;API 验证新增启用监听记录,匿名 runtime 已释放并清理 |
+| 工作台 | `/creator/workbench` | 显示评论、线索、规则、事件监听、私信和操作记录页签,无错误文案 |
+| 任务 | `/tasks` | 显示任务筛选和空状态,无错误文案 |
+| 审计 | `/audit` | 显示审计记录和脱敏导出入口,无错误文案 |
+
+逐页加载检查确认:URL 与 TanStack 文件路由一致、主标题存在,正文不包含“控制面代理不可用”“网络请求失败”或“认证已过期”等通用错误。实际操作产生的领域提示(账号详情说明、竞品无可用已登录账号、gateway 启动失败)均保留在下方操作记录,不被当成成功。
+
+## 联调边界检查
+
+- `/healthz`:`204`
+- `/readyz`:`204`
+- `/api/gateways`:`200`
+- `/api/phase-a/accounts`:`200`
+- `/api/browsers`:`200`
+- `/api/creator/accounts`:`200`
+- `/api/phase-a/audit`:`200`
+- `/api/creator/updates`:通过前端 `/api` 代理收到初始 `retry: 5000` SSE 事件;测试客户端主动超时断开后 control-plane 仍返回 `/healthz 204`
+
+本次联调还覆盖了真实 `/api` 代理,而不是 Playwright mock。Playwright 仍只作为既有响应式回归测试,不作为本记录的真实 control-plane 证据。
+
+## 实际交互操作记录
+
+第二份 browser-harness 录制:`/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-tanstack-control-plane-actions-pass2`,82 帧;录制对应以下真实 UI 操作:
+
+- 账号:点击第一条“查看账号”,实际进入 `/accounts/account-committed` 详情页。
+- 环境:在原登记的 `plan01-gateway`(`127.0.0.1:28187`)上点击“启动 抖音验收浏览器”,页面显示“运行中 / 可调度 / CDP `http://127.0.0.1:19000`”;再点击停止,页面显示“已停止 / 运行实例缺失”。最终录制见 `creatorhub-registered-native-gateway-lifecycle-final`。
+- 竞品:打开“添加监听”,输入 `https://www.douyin.com/video/7685553258340830507`,点击“解析并预览作者”,页面显示作者预览;点击“确认作者并加入队列”,再点击“确认加入监听”,页面显示“相关账号已加入监听队列。”。control-plane API 验证新记录为启用状态;gateway 列表确认只剩已停止的 `plan01-douyin`,没有匿名 runtime。
+- 工作台:实际切换“线索、规则、事件监听、私信、操作记录、评论”六个页签,并分别等待内容加载。
+- 任务:实际填写并清除账号筛选 `account-committed`,两次列表请求均完成。
+- 审计:实际填写事件筛选 `environment_action_finished`,看到真实审计记录,并点击“导出当前页(脱敏)”。
+
+早期记录中的旧 Docker gateway 与临时 native gateway 结果保留作历史诊断;最终复核已将登记 gateway 指向可用的 native `127.0.0.1:28187`,并在该登记配置上完成启停录制,不再以临时未登记 gateway 作为验收依据。
+
+## 曾发现并修复的联调阻塞
+
+1. creator SSE 的 `SendStreamWriter` 回调在请求上下文回收后再次调用 `c.RequestCtx()`,会触发 nil pointer panic。现在在开始流式写入前捕获 `requestCtx`,并增加了取消回归测试。
+2. 本地验证库的 migration ledger 已记录到较新版本,但实际仍保留 `browser_image`、`image_version`、`old_image_version` 等旧列名,导致浏览器和审计 API 返回 500。开发阶段允许破坏性重建/修正数据库,因此仅在本地验证库执行了 migration 017 对应的表和列重命名;没有加入代码兼容层。旧 Docker image ref 不符合新路径约束,因此该本地修复未强行添加新 check constraint。
+
+## 自动检查结果
+
+| 检查 | 结果 |
+| --- | --- |
+| `npm --prefix web run test` | 12 files,101/101 passed |
+| `npm --prefix web run test:coverage` | Lines 68.57%,高于 65% 门槛 |
+| `npm --prefix web run typecheck` | passed |
+| `npm --prefix web run build` | passed |
+| `npm --prefix web audit --audit-level=low` | 0 vulnerabilities |
+| `go test ./...` | passed |
+| `go vet ./...` | passed |
+| `go build ./cmd/control-plane` | passed |
+
+## 依赖审计数字的解释
+
+目标记录中的“7 个漏洞(1 high、5 moderate、1 low)”是迁移中途的基线,不是当前结果。对应基线 `npm audit --json` 为:
+
+- high:1
+- moderate:5
+- low:1
+- total:7
+
+之后升级了 TanStack Start 到 `1.168.56`、Vite 到 `7.3.6`,并完成 `npm audit fix`。中间结果曾只剩 1 个 low;最终当前 lockfile 的审计结果为 0。因而当前仓库不存在需要继续声明为“仍有 7 个”的漏洞;7 项已作为依赖升级的一部分处理完毕。当前阶段按使用者确认不再扩展安全范围,也不恢复旧 lockfile;恢复旧 lockfile 会重新引入已知漏洞。历史基线与当前 0 的数字保存在 `docs/evidence/tanstack-start-audit-baseline-7.json`,供生产上线前安全复核。
+
+## 构建提示
+
+构建退出码为 0。完整当前输出保存在 `docs/evidence/tanstack-start-build-output.log`,其中可复核 `✓ built`、产物清单及 Nitro 提示。依赖噪声过滤前的完整基线保存在 `docs/evidence/tanstack-start-build-warning-baseline.log`,可复核 TanStack/`use client` 与 Nitro empty-chunk warning;当前配置过滤了已知依赖噪声,仍输出若干 `Generated an empty chunk` 非阻断提示。这些提示不影响产物或联调,按原范围作为后续单独清理项保留。
diff --git a/docs/evidence/tanstack-start-final-acceptance.md b/docs/evidence/tanstack-start-final-acceptance.md
new file mode 100644
index 0000000..13a9aff
--- /dev/null
+++ b/docs/evidence/tanstack-start-final-acceptance.md
@@ -0,0 +1,26 @@
+# CreatorHub TanStack Start final acceptance
+
+## Real control-plane browser evidence
+
+- Dashboard: `http://127.0.0.1:4176`
+- Control plane: `http://127.0.0.1:8082`
+- Registered gateway: `plan01-gateway` at `http://127.0.0.1:28187`
+- Registered gateway lifecycle recording: `/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-registered-native-gateway-lifecycle-final`
+ - Start: `运行中` / `可调度` / CDP `http://127.0.0.1:19000`
+ - Stop: `已停止` / `运行实例缺失`
+- Competitor recording: `/home/rogee/.config/browser-harness/agent-workspace/recordings/creatorhub-anonymous-competitor-final`
+ - Public share URL: `https://www.douyin.com/video/7685553258340830507`
+ - Anonymous author preview succeeded.
+ - Author confirmation and “确认加入监听” succeeded.
+ - API verification found a new enabled Douyin competitor record.
+ - Gateway verification found no anonymous runtime after cleanup; only stopped `plan01-douyin` remained.
+
+The earlier temporary-gateway and “no logged-in account” attempts are historical diagnostics and are superseded by the final recordings above.
+
+## Automated verification
+
+- Web: 101/101 tests passed; line coverage 68.57%; typecheck passed; build passed.
+- Go: `go test ./...`, `go test -race ./...`, `go vet ./...`, and `go build ./cmd/control-plane` passed.
+- Browser gateway: 72 tests passed.
+- `npm audit`: 0 vulnerabilities.
+- Known TanStack/Nitro build warnings remain non-blocking and are excluded from this acceptance.
diff --git a/internal/controlplane/api/creator.go b/internal/controlplane/api/creator.go
index f14cb3d..7d616f8 100644
--- a/internal/controlplane/api/creator.go
+++ b/internal/controlplane/api/creator.go
@@ -21,6 +21,7 @@ import (
"git.ipao.vip/rogee/creator-hub/internal/creator"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
douyin "git.ipao.vip/rogee/creator-hub/internal/platform/douyin"
+ xiaohongshu "git.ipao.vip/rogee/creator-hub/internal/platform/xiaohongshu"
"github.com/gofiber/fiber/v3"
"github.com/sirupsen/logrus"
)
@@ -88,6 +89,7 @@ func registerCreatorWithServices(app *fiber.App, store *creator.Store, phaseASto
})
app.Get("/api/creator/updates", func(c fiber.Ctx) error {
updates, unsubscribe := creatorUpdates.subscribe()
+ requestCtx := c.RequestCtx()
c.Set("Content-Type", "text/event-stream")
c.Set("Cache-Control", "no-cache")
c.Set("Connection", "keep-alive")
@@ -112,7 +114,7 @@ func registerCreatorWithServices(app *fiber.App, store *creator.Store, phaseASto
if _, err := fmt.Fprint(w, ": keep-alive\n\n"); err != nil {
return
}
- case <-c.RequestCtx().Done():
+ case <-requestCtx.Done():
return
}
if err := w.Flush(); err != nil {
@@ -1582,11 +1584,151 @@ func previewCompetitorShare(ctx context.Context, store *creator.Store, phaseASto
}
}
-func previewDouyinCompetitorShare(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store, accountID, shareURL string) (competitorSharePreview, error) {
- browser, err := newDouyinAccountBrowser(ctx, store, phaseAStore, hubStore, accountID)
- if err != nil {
- return competitorSharePreview{}, err
+type anonymousBrowserLease struct {
+ gateway hub.Gateway
+ environment hub.EnvironmentContext
+}
+
+func (lease anonymousBrowserLease) close() error {
+ cleanupContext, cancel := context.WithTimeout(context.Background(), 90*time.Second)
+ defer cancel()
+ payload := gatewayCleanupGenerationPayload(lease.environment)
+ payload["purge_profile"] = true
+ var lastErr error
+ for attempt := 0; attempt < 30; attempt++ {
+ status, body, callErr := gatewayCall(cleanupContext, lease.gateway, http.MethodDelete,
+ "/v1/browsers/"+url.PathEscape(lease.environment.Alias), payload, 30*time.Second)
+ if callErr == nil && (status == http.StatusNoContent || status == http.StatusNotFound) {
+ return nil
+ }
+ if callErr != nil {
+ lastErr = gatewayUnreachable(callErr)
+ } else {
+ lastErr = gatewayRejected(status, body)
+ }
+ if callErr == nil && status != http.StatusAccepted && status >= http.StatusBadRequest && status < http.StatusInternalServerError {
+ return lastErr
+ }
+ if cleanupContext.Err() != nil {
+ return errors.Join(lastErr, cleanupContext.Err())
+ }
+ if err := waitForAnonymousCleanup(cleanupContext, 250*time.Millisecond); err != nil {
+ return errors.Join(lastErr, err)
+ }
}
+ return lastErr
+}
+
+func waitForAnonymousCleanup(ctx context.Context, delay time.Duration) error {
+ timer := time.NewTimer(delay)
+ defer timer.Stop()
+ select {
+ case <-timer.C:
+ return nil
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+}
+
+func newAnonymousBrowser(ctx context.Context, store *hub.Store) (anonymousBrowserLease, error) {
+ if store == nil {
+ return anonymousBrowserLease{}, creator.ErrUnavailable
+ }
+ gateways, err := store.ListGateways(ctx)
+ if err != nil {
+ return anonymousBrowserLease{}, err
+ }
+ versions, err := store.ListBrowserVersions(ctx, true)
+ if err != nil {
+ return anonymousBrowserLease{}, err
+ }
+ envs, err := store.ListEnvs(ctx)
+ if err != nil {
+ return anonymousBrowserLease{}, err
+ }
+ if len(gateways) == 0 || len(versions) == 0 {
+ return anonymousBrowserLease{}, fmt.Errorf("%w: anonymous browser gateway or browser version is not configured", creator.ErrUnavailable)
+ }
+
+ gatewayByName := make(map[string]hub.Gateway, len(gateways))
+ for _, gateway := range gateways {
+ gatewayByName[gateway.Name] = gateway
+ }
+ versionEnabled := make(map[string]bool, len(versions))
+ for _, version := range versions {
+ versionEnabled[version.Version] = true
+ }
+
+ var template hub.Env
+ var gateway hub.Gateway
+ for _, candidate := range envs {
+ if !versionEnabled[candidate.BrowserVersion] {
+ continue
+ }
+ candidateGateway, ok := gatewayByName[candidate.Gateway]
+ if !ok {
+ continue
+ }
+ template, gateway = candidate, candidateGateway
+ break
+ }
+ if gateway.Name == "" {
+ gateway = gateways[0]
+ template = hub.Env{
+ BrowserVersion: versions[0].Version,
+ Fingerprint: hub.Fingerprint{Seed: 1},
+ }
+ }
+
+ operationID := hub.NewOperationID()
+ template.Alias = "anon-" + strings.TrimPrefix(operationID, "operation-")
+ template.Name = "匿名竞品解析"
+ template.Gateway = gateway.Name
+ template.Fingerprint.ProxyServer = ""
+ template.Fingerprint.DisableNonProxiedUDP = false
+ if template.Fingerprint.Seed < 1 {
+ template.Fingerprint.Seed = time.Now().UnixNano()%2147483646 + 1
+ }
+ environment := hub.EnvironmentContext{Env: template, BindingVersion: 1}
+ payload := gatewayCreatePayload(environment, "", gatewayNetworkExit{})
+ status, body, callErr := gatewayCall(ctx, gateway, http.MethodPost, "/v1/browsers", payload, gatewayLongTimeout)
+ if callErr == nil && status == http.StatusCreated {
+ var created runtimeStatus
+ if json.Unmarshal(body, &created) == nil && created.Alias == environment.Alias && created.BindingVersion == environment.BindingVersion && created.State == "running" && validCreatedRuntime(created, environment, true) {
+ environment.RuntimeID, environment.RuntimeNetworkID = created.ID, created.NetworkID
+ return anonymousBrowserLease{gateway: gateway, environment: environment}, nil
+ }
+ callErr = errors.New("gateway returned an invalid anonymous runtime generation")
+ } else if callErr == nil {
+ callErr = gatewayRejected(status, body)
+ } else {
+ callErr = gatewayUnreachable(callErr)
+ }
+ cleanupErr := cleanupAnonymousRuntimeAfterCreateFailure(ctx, gateway, environment)
+ return anonymousBrowserLease{}, errors.Join(callErr, cleanupErr)
+}
+
+func cleanupAnonymousRuntimeAfterCreateFailure(ctx context.Context, gateway hub.Gateway, environment hub.EnvironmentContext) error {
+ created, found, err := reconcileGatewayRuntime(ctx, gateway, environment.Alias)
+ if err != nil || !found {
+ return err
+ }
+ environment.RuntimeID, environment.RuntimeNetworkID = created.ID, created.NetworkID
+ return (anonymousBrowserLease{gateway: gateway, environment: environment}).close()
+}
+
+func previewDouyinCompetitorShare(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store, accountID, shareURL string) (preview competitorSharePreview, err error) {
+ _ = store
+ _ = phaseAStore
+ _ = accountID
+ lease, err := newAnonymousBrowser(ctx, hubStore)
+ if err != nil {
+ return competitorSharePreview{}, fmt.Errorf("%w: temporary anonymous browser unavailable: %v", creator.ErrUnavailable, err)
+ }
+ defer func() {
+ err = errors.Join(err, lease.close())
+ }()
+ browser := creatorGatewayBrowser{gateway: lease.gateway, environment: lease.environment}
canonicalURL, err := browser.Resolve(ctx, shareURL)
if err != nil {
return competitorSharePreview{}, fmt.Errorf("%w: share URL resolution failed: %v", creator.ErrUnavailable, err)
@@ -1600,7 +1742,6 @@ func previewDouyinCompetitorShare(ctx context.Context, store *creator.Store, pha
return competitorSharePreview{}, fmt.Errorf("%w: target identity verification failed: %v", creator.ErrConflict, err)
}
return competitorSharePreview{
- AccountID: accountID,
Platform: creator.PlatformDouyin,
PlatformAccountKey: target.SecUID,
Nickname: target.Nickname,
@@ -1610,10 +1751,20 @@ func previewDouyinCompetitorShare(ctx context.Context, store *creator.Store, pha
}, nil
}
-func previewXiaohongshuCompetitorShare(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store, accountID, shareURL string) (competitorSharePreview, error) {
- collector, err := newXiaohongshuReadCollector(ctx, store, phaseAStore, hubStore, accountID, creator.SourceCompetitor, accountID)
+func previewXiaohongshuCompetitorShare(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore *hub.Store, accountID, shareURL string) (preview competitorSharePreview, err error) {
+ _ = store
+ _ = phaseAStore
+ _ = accountID
+ lease, err := newAnonymousBrowser(ctx, hubStore)
if err != nil {
- return competitorSharePreview{}, err
+ return competitorSharePreview{}, fmt.Errorf("%w: temporary anonymous browser unavailable: %v", creator.ErrUnavailable, err)
+ }
+ defer func() {
+ err = errors.Join(err, lease.close())
+ }()
+ collector := &xiaohongshu.Collector{
+ Browser: xiaohongshuGatewayBrowser{gateway: lease.gateway, environment: lease.environment},
+ SourceType: creator.SourceCompetitor,
}
work, err := collector.GetNoteDetail(ctx, shareURL)
if err != nil {
@@ -1623,7 +1774,6 @@ func previewXiaohongshuCompetitorShare(ctx context.Context, store *creator.Store
return competitorSharePreview{}, fmt.Errorf("%w: xiaohongshu note author identity is missing", creator.ErrInvalid)
}
return competitorSharePreview{
- AccountID: accountID,
Platform: creator.PlatformXiaohongshu,
PlatformAccountKey: work.AuthorKey,
Nickname: work.AuthorName,
diff --git a/internal/controlplane/api/creator_route_validation_test.go b/internal/controlplane/api/creator_route_validation_test.go
index 0010c95..b492638 100644
--- a/internal/controlplane/api/creator_route_validation_test.go
+++ b/internal/controlplane/api/creator_route_validation_test.go
@@ -1,14 +1,42 @@
package api
import (
+ "io"
"net/http"
"net/http/httptest"
"strings"
"testing"
+ "time"
"github.com/gofiber/fiber/v3"
)
+func TestCreatorUpdatesStreamStopsOnRequestCancellation(t *testing.T) {
+ app := fiber.New()
+ registerCreatorWithServices(app, nil, nil, nil, nil, nil, nil)
+
+ request := httptest.NewRequest(http.MethodGet, "/api/creator/updates", http.NoBody)
+ response, err := app.Test(request, fiber.TestConfig{
+ Timeout: 200 * time.Millisecond,
+ FailOnTimeout: false,
+ })
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer response.Body.Close()
+
+ body, readErr := io.ReadAll(response.Body)
+ if readErr != nil && !strings.Contains(readErr.Error(), "unexpected EOF") {
+ t.Fatalf("read stream response: %v", readErr)
+ }
+ if response.StatusCode != http.StatusOK {
+ t.Fatalf("stream status = %d", response.StatusCode)
+ }
+ if !strings.Contains(string(body), "retry: 5000") {
+ t.Fatalf("stream body = %q", body)
+ }
+}
+
func TestCreatorWriteRoutesRejectMalformedInputBeforeStoreAccess(t *testing.T) {
app := fiber.New()
registerCreatorWithServices(app, nil, nil, nil, nil, nil, nil)
diff --git a/internal/controlplane/api/creator_share_test.go b/internal/controlplane/api/creator_share_test.go
index 16e6d6b..7a08159 100644
--- a/internal/controlplane/api/creator_share_test.go
+++ b/internal/controlplane/api/creator_share_test.go
@@ -1,9 +1,14 @@
package api
import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "strings"
"testing"
"git.ipao.vip/rogee/creator-hub/internal/creator"
+ hub "git.ipao.vip/rogee/creator-hub/internal/environment"
)
func TestCompetitorSharePlatform(t *testing.T) {
@@ -37,6 +42,39 @@ func TestCompetitorSharePlatform(t *testing.T) {
}
}
+func TestAnonymousBrowserLeasePurgesRuntimeAndProfile(t *testing.T) {
+ var requestBody map[string]any
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodDelete || r.URL.Path != "/v1/browsers/anon-test" {
+ t.Fatalf("unexpected cleanup request: %s %s", r.Method, r.URL.Path)
+ }
+ if err := json.NewDecoder(r.Body).Decode(&requestBody); err != nil {
+ t.Fatal(err)
+ }
+ w.WriteHeader(http.StatusNoContent)
+ }))
+ defer server.Close()
+
+ err := (anonymousBrowserLease{
+ gateway: hub.Gateway{Endpoint: server.URL, Token: "test-token"},
+ environment: hub.EnvironmentContext{
+ Env: hub.Env{Alias: "anon-test"},
+ BindingVersion: 1,
+ RuntimeID: strings.Repeat("a", 64),
+ RuntimeNetworkID: "native-" + strings.Repeat("b", 32),
+ },
+ }).close()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if requestBody["purge_profile"] != true || requestBody["binding_version"] != float64(1) {
+ t.Fatalf("cleanup request did not fence and purge the temporary runtime: %#v", requestBody)
+ }
+ if _, ok := requestBody["network_exit_id"]; ok {
+ t.Fatalf("cleanup request must use only the runtime generation fence: %#v", requestBody)
+ }
+}
+
func TestDouyinWorkKeyFromURL(t *testing.T) {
for _, test := range []struct {
url string
diff --git a/web/index.html b/web/index.html
deleted file mode 100644
index f725009..0000000
--- a/web/index.html
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
- CreatorHub · 运行环境
-
-
-
-
-
-
diff --git a/web/package-lock.json b/web/package-lock.json
index cee7113..dd7394c 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -8,28 +8,38 @@
"name": "creator-hub-web",
"version": "0.1.0",
"dependencies": {
- "@refinedev/core": "^5.0.12",
+ "@tailwindcss/vite": "^4.3.3",
+ "@tanstack/react-query": "5.95.2",
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/react-router-with-query": "1.130.17",
+ "@tanstack/react-start": "1.168.56",
+ "@tanstack/router-plugin": "1.168.40",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "lucide-react": "^1.28.0",
+ "nitro": "^3.0.260415-beta",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
- "react-router": "^7.18.2",
- "react-router-dom": "^7.18.2",
"remixicon": "^4.9.1",
- "tailwind-merge": "^3.6.0"
+ "tailwind-merge": "^3.6.0",
+ "tailwindcss": "^4.3.3",
+ "tw-animate-css": "^1.4.0",
+ "vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@playwright/test": "1.62.1",
- "@tailwindcss/vite": "^4.3.3",
+ "@tanstack/router-generator": "1.167.38",
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
- "@vitejs/plugin-react": "6.1.0",
+ "@types/node": "^22.12.0",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "5.2.0",
"@vitest/coverage-v8": "4.1.11",
"jsdom": "27.0.1",
- "tailwindcss": "^4.3.3",
"typescript": "^5.9.3",
- "vite": "8.2.2",
+ "vite": "7.3.6",
"vitest": "4.1.11"
}
},
@@ -72,7 +82,6 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
"integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.29.7",
@@ -83,11 +92,157 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz",
+ "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz",
+ "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-compilation-targets": "^7.29.7",
+ "@babel/helper-module-transforms": "^7.29.7",
+ "@babel/helpers": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz",
+ "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz",
+ "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.29.7",
+ "@babel/helper-validator-option": "^7.29.7",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz",
+ "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "@babel/traverse": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz",
+ "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-string-parser": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -97,17 +252,37 @@
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz",
+ "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz",
+ "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/parser": {
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz",
"integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.8"
@@ -119,6 +294,38 @@
"node": ">=6.0.0"
}
},
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz",
+ "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz",
+ "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
@@ -129,11 +336,42 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.8",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz",
+ "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.8",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.8",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/types": {
"version": "7.29.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz",
"integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
@@ -293,6 +531,448 @@
"node": ">=20.19.0"
}
},
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@floating-ui/core": {
"version": "1.8.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz",
@@ -335,7 +1015,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -346,7 +1025,6 @@
"version": "2.3.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -357,7 +1035,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -367,25 +1044,87 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@oozcitak/dom": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-2.0.2.tgz",
+ "integrity": "sha512-GjpKhkSYC3Mj4+lfwEyI1dqnsKTgwGy48ytZEhm4A/xnH/8z9M3ZVXKr/YGQi3uCLs1AEBS+x5T2JPiueEDW8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@oozcitak/infra": "^2.0.2",
+ "@oozcitak/url": "^3.0.0",
+ "@oozcitak/util": "^10.0.0"
+ },
+ "engines": {
+ "node": ">=20.0"
+ }
+ },
+ "node_modules/@oozcitak/infra": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-2.0.2.tgz",
+ "integrity": "sha512-2g+E7hoE2dgCz/APPOEK5s3rMhJvNxSMBrP+U+j1OWsIbtSpWxxlUjq1lU8RIsFJNYv7NMlnVsCuHcUzJW+8vA==",
+ "license": "MIT",
+ "dependencies": {
+ "@oozcitak/util": "^10.0.0"
+ },
+ "engines": {
+ "node": ">=20.0"
+ }
+ },
+ "node_modules/@oozcitak/url": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-3.0.0.tgz",
+ "integrity": "sha512-ZKfET8Ak1wsLAiLWNfFkZc/BraDccuTJKR6svTYc7sVjbR+Iu0vtXdiDMY4o6jaFl5TW2TlS7jbLl4VovtAJWQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@oozcitak/infra": "^2.0.2",
+ "@oozcitak/util": "^10.0.0"
+ },
+ "engines": {
+ "node": ">=20.0"
+ }
+ },
+ "node_modules/@oozcitak/util": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-10.0.0.tgz",
+ "integrity": "sha512-hAX0pT/73190NLqBPPWSdBVGtbY6VOhWYK3qqHqtXQ1gK7kS2yz4+ivsN07hpJ6I3aeMtKP6J6npsEKOAzuTLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.0"
+ }
+ },
"node_modules/@oxc-project/types": {
"version": "0.147.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.147.0.tgz",
"integrity": "sha512-IJ3s6ltHLp45S0bh7phkX+gJO7A1Wuz2EaqpAhb8WjqDwbzMiWKHhyyT42tskaWjEYXtHtVCPpnBJVT9+dcRLg==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
@@ -1903,72 +2642,6 @@
"integrity": "sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==",
"license": "MIT"
},
- "node_modules/@refinedev/core": {
- "version": "5.0.12",
- "resolved": "https://registry.npmmirror.com/@refinedev/core/-/core-5.0.12.tgz",
- "integrity": "sha512-9y5Bi9Lb7XyJmM55b8rCeBTDRCBU41p47OymJldasLfrtpUm2EwI+27DjjNpHTOugymiZsIbLlPtHCPQIXBHcg==",
- "license": "MIT",
- "dependencies": {
- "@refinedev/devtools-internal": "2.0.2",
- "@tanstack/react-query": "^5.81.5",
- "lodash": "^4.17.21",
- "lodash-es": "^4.17.21",
- "papaparse": "^5.3.0",
- "pluralize": "^8.0.0",
- "qs": "^6.10.1",
- "tslib": "^2.6.2",
- "warn-once": "^0.1.0"
- },
- "engines": {
- "node": ">=20"
- },
- "peerDependencies": {
- "@tanstack/react-query": "^5.81.5",
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/@refinedev/devtools-internal": {
- "version": "2.0.2",
- "resolved": "https://registry.npmmirror.com/@refinedev/devtools-internal/-/devtools-internal-2.0.2.tgz",
- "integrity": "sha512-1YYizOW1lyy9ep8eQ7TcUPBooKXIlvzTLjLdDArsQwx7P33cn2uXdqM7So5VhlNFXhjOjAKFgrH5c1jleRF8Jg==",
- "license": "MIT",
- "dependencies": {
- "@refinedev/devtools-shared": "2.0.2",
- "@tanstack/react-query": "^5.81.5",
- "error-stack-parser": "^2.1.4"
- },
- "engines": {
- "node": ">=20"
- },
- "peerDependencies": {
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
- "node_modules/@refinedev/devtools-shared": {
- "version": "2.0.2",
- "resolved": "https://registry.npmmirror.com/@refinedev/devtools-shared/-/devtools-shared-2.0.2.tgz",
- "integrity": "sha512-3cTjR1mEWn0tHFZBfPD5aVpBGLUhpAkfjqYCwKrijIicr1Utp/j0BqiPRnNqTf+W71HTng3znBpUhnR83u+tuA==",
- "license": "MIT",
- "dependencies": {
- "@tanstack/react-query": "^5.81.5",
- "error-stack-parser": "^2.1.4"
- },
- "engines": {
- "node": ">=20"
- },
- "peerDependencies": {
- "@types/react": "^18.0.0 || ^19.0.0",
- "@types/react-dom": "^18.0.0 || ^19.0.0",
- "react": "^18.0.0 || ^19.0.0",
- "react-dom": "^18.0.0 || ^19.0.0"
- }
- },
"node_modules/@rolldown/binding-android-arm-eabi": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.6.tgz",
@@ -1976,7 +2649,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1993,7 +2665,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2010,7 +2681,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2027,7 +2697,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2044,7 +2713,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2061,7 +2729,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2078,7 +2745,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2095,7 +2761,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2112,7 +2777,6 @@
"cpu": [
"ppc64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2129,7 +2793,6 @@
"cpu": [
"s390x"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2146,7 +2809,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2163,7 +2825,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2180,7 +2841,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2197,7 +2857,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2214,7 +2873,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2228,9 +2886,358 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz",
+ "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz",
+ "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz",
+ "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz",
+ "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz",
+ "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz",
+ "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz",
+ "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz",
+ "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz",
+ "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz",
+ "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz",
+ "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz",
+ "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz",
+ "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz",
+ "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz",
+ "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz",
+ "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz",
+ "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz",
+ "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz",
+ "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz",
+ "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz",
+ "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz",
+ "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz",
+ "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz",
+ "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz",
+ "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@standard-schema/spec": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
@@ -2242,7 +3249,6 @@
"version": "4.3.3",
"resolved": "https://registry.npmmirror.com/@tailwindcss/node/-/node-4.3.3.tgz",
"integrity": "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
@@ -2258,7 +3264,6 @@
"version": "1.32.0",
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
- "dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -2291,7 +3296,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2312,7 +3316,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2333,7 +3336,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2354,7 +3356,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2375,7 +3376,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2396,10 +3396,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2420,10 +3416,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2444,10 +3436,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2468,10 +3456,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2492,7 +3476,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2513,7 +3496,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -2531,7 +3513,6 @@
"version": "4.3.3",
"resolved": "https://registry.npmmirror.com/@tailwindcss/oxide/-/oxide-4.3.3.tgz",
"integrity": "sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 20"
@@ -2558,7 +3539,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2575,7 +3555,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2592,7 +3571,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2609,7 +3587,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2626,7 +3603,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2643,10 +3619,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2663,10 +3635,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2683,10 +3651,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2703,10 +3667,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2731,7 +3691,6 @@
"cpu": [
"wasm32"
],
- "dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
@@ -2746,6 +3705,66 @@
"node": ">=14.0.0"
}
},
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
+ "version": "1.11.1",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.2",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
+ "version": "1.11.1",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.2",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.4",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ },
+ "peerDependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
+ "version": "0.10.2",
+ "inBundle": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
+ "version": "2.8.1",
+ "inBundle": true,
+ "license": "0BSD",
+ "optional": true
+ },
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.3.3",
"resolved": "https://registry.npmmirror.com/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.3.tgz",
@@ -2753,7 +3772,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2770,7 +3788,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2784,7 +3801,6 @@
"version": "4.3.3",
"resolved": "https://registry.npmmirror.com/@tailwindcss/vite/-/vite-4.3.3.tgz",
"integrity": "sha512-yYU8cogLeSh/ms2jh8Fj7jaba/EWa7Ja6GoUqYZaraEuCI5YS6ms6ObZgjjedm+jm6XZjdNRWBpPP6Z86oOxcw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@tailwindcss/node": "4.3.3",
@@ -2795,10 +3811,23 @@
"vite": "^5.2.0 || ^6 || ^7 || ^8"
}
},
+ "node_modules/@tanstack/history": {
+ "version": "1.162.4",
+ "resolved": "https://registry.npmjs.org/@tanstack/history/-/history-1.162.4.tgz",
+ "integrity": "sha512-utTS5L2OkeYUzXGohL1Z8sefu1GLNOJcxe8Hd6iIdc/Xo1K1nDB2JEp4iSFhvYh33xKC9V91TxrS8qfrpoKobQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tanstack/query-core": {
- "version": "5.102.8",
- "resolved": "https://registry.npmmirror.com/@tanstack/query-core/-/query-core-5.102.8.tgz",
- "integrity": "sha512-ZNjkJ33CqvPNec/6lZBnHqLc3EVGPZ9ySLhYahU9TcuRFdmwXewuj0c4hwSWcGHqEUwcSrKeZ+oGcvPBqXcQcg==",
+ "version": "5.95.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz",
+ "integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -2806,12 +3835,12 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.102.8",
- "resolved": "https://registry.npmmirror.com/@tanstack/react-query/-/react-query-5.102.8.tgz",
- "integrity": "sha512-TYBea4OuXWD7MhaSHq069TWbFe7rcwWN6kzT7JF0OKi1K6c1gTv2IzD6A6ExJsCMozdkqBWeuIUZmu4KQg0O5A==",
+ "version": "5.95.2",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz",
+ "integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.102.8"
+ "@tanstack/query-core": "5.95.2"
},
"funding": {
"type": "github",
@@ -2821,6 +3850,447 @@
"react": "^18 || ^19"
}
},
+ "node_modules/@tanstack/react-router": {
+ "version": "1.170.38",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router/-/react-router-1.170.38.tgz",
+ "integrity": "sha512-iHM9b0aDJbuvftmkiQXawzoqsdV68p2Re2safbVLCnxafe+iLKV++2i3hI/BMAP6uR92/Mjw94JKJJfD7pbwHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/history": "1.162.4",
+ "@tanstack/react-store": "^0.11.0",
+ "@tanstack/router-core": "1.171.32",
+ "isbot": "^5.1.22"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-router-with-query": {
+ "version": "1.130.17",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-router-with-query/-/react-router-with-query-1.130.17.tgz",
+ "integrity": "sha512-TNaSocW20KuPwUojEm130DLWTr9M5hsSzxiu4QqS2jNCnrGLuDrwMHyP+6fq13lG3YuU4u9O1qajxfJIGomZCg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@tanstack/react-query": ">=5.49.2",
+ "@tanstack/react-router": ">=1.43.2",
+ "@tanstack/router-core": ">=1.114.7",
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-start": {
+ "version": "1.168.56",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start/-/react-start-1.168.56.tgz",
+ "integrity": "sha512-b/jVJS+yt7J0IKwXfVoA2mlhIi92sWfwCYrCVked0WERzgbYAad6xrOwNTljz9OKyeajofGrmMdKJgRzK1Lt2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/react-start-client": "1.168.36",
+ "@tanstack/react-start-rsc": "0.1.55",
+ "@tanstack/react-start-server": "1.167.43",
+ "@tanstack/router-utils": "1.162.3",
+ "@tanstack/start-client-core": "1.170.32",
+ "@tanstack/start-plugin-core": "1.171.46",
+ "@tanstack/start-server-core": "1.169.37",
+ "pathe": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@rsbuild/core": "^2.0.0",
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0",
+ "vite": ">=7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rsbuild/core": {
+ "optional": true
+ },
+ "@vitejs/plugin-rsc": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/react-start-client": {
+ "version": "1.168.36",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start-client/-/react-start-client-1.168.36.tgz",
+ "integrity": "sha512-TRXZKag/Ng0TqVfcN4cbEDVJhEZYosHZ9/VAEhK3PK/YqznytK+bcoonE4jP5NCXMdH2YRTMsrWKrApEuWz0Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/start-client-core": "1.170.32"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-start-rsc": {
+ "version": "0.1.55",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start-rsc/-/react-start-rsc-0.1.55.tgz",
+ "integrity": "sha512-qfW89LMrewtMLHMCumMP3+3mf2gPezFE7FZ7CHPJis4paxt2A6Jl7eCpE6whYgc5DFiifUv9qdfbC22HIP0HAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/router-utils": "1.162.3",
+ "@tanstack/start-client-core": "1.170.32",
+ "@tanstack/start-fn-stubs": "1.162.0",
+ "@tanstack/start-plugin-core": "1.171.46",
+ "@tanstack/start-storage-context": "1.167.34",
+ "pathe": "^2.0.3"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@rspack/core": ">=2.0.0-0",
+ "@vitejs/plugin-rsc": ">=0.5.30",
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0",
+ "react-server-dom-rspack": ">=0.0.2"
+ },
+ "peerDependenciesMeta": {
+ "@rspack/core": {
+ "optional": true
+ },
+ "@vitejs/plugin-rsc": {
+ "optional": true
+ },
+ "react-server-dom-rspack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/react-start-server": {
+ "version": "1.167.43",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-start-server/-/react-start-server-1.167.43.tgz",
+ "integrity": "sha512-/Fc1mOEBAyXmqxnihIkS1DrCcJX8ffrJ+xyc8nnFmO9YuCdrnZJskd48R1ZiOhM7u3vCPzDxUycIhKo8HlKn2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/start-server-core": "1.169.37"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0 || >=19.0.0",
+ "react-dom": ">=18.0.0 || >=19.0.0"
+ }
+ },
+ "node_modules/@tanstack/react-store": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.11.1.tgz",
+ "integrity": "sha512-HaIGKI3YLmjBYIvy5DFDY23oNaYZIsTZfngey07Uh5iLVJgM3bIGCnZeOFOqzjFld9JHWcaHJnasD/bKoGKwJQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/store": "0.11.1",
+ "use-sync-external-store": "^1.6.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@tanstack/router-core": {
+ "version": "1.171.32",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.171.32.tgz",
+ "integrity": "sha512-X86Jqk3vB2KJcUfS6oLi7B7yxbaa59QEhZRPPP1fRx3k+Jw/Fu1UYVBcRtdwK/OccQnrlQdVoKvNO3QXmyEBOw==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/history": "1.162.4",
+ "cookie-es": "^3.0.0",
+ "seroval": "^1.6.2",
+ "seroval-plugins": "^1.6.2"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/router-generator": {
+ "version": "1.167.38",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.167.38.tgz",
+ "integrity": "sha512-lkqgFleDgfkW+QONVMKBs+ZGDUF0v9R9FkplS/GZvKDFpfnu+tZxmGlV2pryU1TDxyuSyjh56AtIla3NMrlScw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.8",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/router-utils": "1.162.3",
+ "@tanstack/virtual-file-routes": "1.162.0",
+ "jiti": "^2.7.0",
+ "magic-string": "^0.30.21",
+ "prettier": "^3.9.6",
+ "zod": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/router-plugin": {
+ "version": "1.168.40",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-plugin/-/router-plugin-1.168.40.tgz",
+ "integrity": "sha512-ifiXjjR4uivxwRDhgYAcfyrcu+Mwx+vlG0QjjQ7N5C5sKmzpt5UtsL21TNVvZDSZl2Vae8DcL84Z02fOs8ZZMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/router-generator": "1.167.38",
+ "@tanstack/router-utils": "1.162.3",
+ "chokidar": "^5.0.0",
+ "unplugin": "^3.3.0",
+ "zod": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@rsbuild/core": ">=1.0.2 || ^2.0.0",
+ "@tanstack/react-router": "^1.170.38",
+ "vite": ">=5.0.0 || >=6.0.0 || >=7.0.0 || >=8.0.0",
+ "vite-plugin-solid": "^2.11.10 || ^3.0.0-0",
+ "webpack": ">=5.92.0"
+ },
+ "peerDependenciesMeta": {
+ "@rsbuild/core": {
+ "optional": true
+ },
+ "@tanstack/react-router": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "vite-plugin-solid": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/router-utils": {
+ "version": "1.162.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/router-utils/-/router-utils-1.162.3.tgz",
+ "integrity": "sha512-Icb0xGuG1+54IV0WMLRcc3ErTx2HeJWyPG661FkQ8UT6guoBq1FJjFRqlG/JS0xi4mFFkBhvwO7tbLa32f3yxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/generator": "^7.29.8",
+ "@babel/parser": "^7.29.8",
+ "@babel/types": "^7.29.8",
+ "ansis": "^4.3.1",
+ "babel-dead-code-elimination": "^1.0.12",
+ "diff": "^8.0.4",
+ "pathe": "^2.0.3",
+ "tinyglobby": "^0.2.17"
+ },
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/start-client-core": {
+ "version": "1.170.32",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-client-core/-/start-client-core-1.170.32.tgz",
+ "integrity": "sha512-kawgeg3Ej/JvGeEN7txFdVN9kXOST9wl7yPojOGESiomwiubHKRdYUS6sBskW91j44H+HirQUppP4+Ha7kjb4A==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/start-fn-stubs": "1.162.0",
+ "@tanstack/start-storage-context": "1.167.34",
+ "seroval": "^1.6.2"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/start-fn-stubs": {
+ "version": "1.162.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-fn-stubs/-/start-fn-stubs-1.162.0.tgz",
+ "integrity": "sha512-QWfUZ3Yo923tdQn38LyKMU8rcTw69zc+T4dAvgTWV4O56SqFRsGfS0lSWIMhJRwXIx/bvdi7nTUBDdZtTHtpTQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/start-plugin-core": {
+ "version": "1.171.46",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-plugin-core/-/start-plugin-core-1.171.46.tgz",
+ "integrity": "sha512-d/cDPZNDdRl824mOamt6USO1ja5JAaIwkuT2FIBRkBdrKZgQ8GJWv5pqpkXMbKANc49V8bUBBOTjgiCdC7YsPw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "7.29.7",
+ "@babel/core": "^7.29.7",
+ "@babel/types": "^7.29.8",
+ "@jridgewell/remapping": "^2.3.5",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/router-generator": "1.167.38",
+ "@tanstack/router-plugin": "1.168.40",
+ "@tanstack/router-utils": "1.162.3",
+ "@tanstack/start-server-core": "1.169.37",
+ "exsolve": "^1.1.1",
+ "lightningcss": "^1.33.0",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.7",
+ "seroval": "^1.6.2",
+ "source-map": "^0.7.6",
+ "srvx": "^0.11.22",
+ "tinyglobby": "^0.2.17",
+ "ufo": "^1.6.4",
+ "vitefu": "^1.1.3",
+ "xmlbuilder2": "^4.0.3",
+ "zod": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "@rsbuild/core": "^2.0.0",
+ "vite": ">=7.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@rsbuild/core": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tanstack/start-server-core": {
+ "version": "1.169.37",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-server-core/-/start-server-core-1.169.37.tgz",
+ "integrity": "sha512-CjYMXg2XISMEJt9UVR4lbMRsnRyWPrdyuF8iuJ4gxTc1sVNRciwMVpMPVGQG2KuIma3ewlNxw8+Tpx2tnS3zRA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/history": "1.162.4",
+ "@tanstack/router-core": "1.171.32",
+ "@tanstack/start-client-core": "1.170.32",
+ "@tanstack/start-storage-context": "1.167.34",
+ "fetchdts": "^0.1.6",
+ "h3-v2": "npm:h3@2.0.1-rc.20",
+ "seroval": "^1.6.2"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/start-storage-context": {
+ "version": "1.167.34",
+ "resolved": "https://registry.npmjs.org/@tanstack/start-storage-context/-/start-storage-context-1.167.34.tgz",
+ "integrity": "sha512-cRPE0kjt1CnOIhepmNOvRFwCGhDAnCRohPD3HmzqJJThPkixLBRuLDvIYp4hRPtM1TJ9hiKbYl24IxAhjZO5TA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/router-core": "1.171.32"
+ },
+ "engines": {
+ "node": ">=22.12.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/store": {
+ "version": "0.11.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.11.1.tgz",
+ "integrity": "sha512-mzTOBhypOuDJAy/D8n2MfUZ1HFkXnmSETviRyhqEC8LUE7/IZQExOTxMANj3KjTofYTkFNpBY67qaVrT41YccA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/virtual-file-routes": {
+ "version": "1.162.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.162.0.tgz",
+ "integrity": "sha512-uhOeFyxLcU41HzvrxsGpiWdcMbScY1EDgbZ5K7DVRMYInbLYWAC0EA/kx9wXAoSM8q82bUG2hRl8+EAjE6XAbA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
@@ -2876,6 +4346,51 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
"node_modules/@types/chai": {
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
@@ -2901,12 +4416,22 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/node": {
+ "version": "22.20.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz",
+ "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
"node_modules/@types/react": {
"version": "19.2.18",
"resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.18.tgz",
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
+ "dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -2915,42 +4440,40 @@
"version": "19.2.5",
"resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.5.tgz",
"integrity": "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg==",
+ "dev": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
},
"node_modules/@vitejs/plugin-react": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.0.tgz",
- "integrity": "sha512-qd2BzUBehkov86WFhg0JkEFEYyCLG9uPCe6qWTY/kRlss9OvJrOF2UbIWT7p+8IzZHkEu0DNGHc4HSv+JdDLsw==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz",
+ "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@rolldown/pluginutils": "^1.0.1"
+ "@babel/core": "^7.29.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-rc.3",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.18.0"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
- "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
- "babel-plugin-react-compiler": "^1.0.0",
- "oxc-transform-react": "^0.145.0",
- "vite": "^8.0.0"
- },
- "peerDependenciesMeta": {
- "@rolldown/plugin-babel": {
- "optional": true
- },
- "babel-plugin-react-compiler": {
- "optional": true
- },
- "oxc-transform-react": {
- "optional": true
- }
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz",
+ "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@vitest/coverage-v8": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.11.tgz",
@@ -3095,13 +4618,6 @@
"url": "https://opencollective.com/vitest"
}
},
- "node_modules/@vitest/utils/node_modules/convert-source-map": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
- "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
- "dev": true,
- "license": "MIT"
- },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
@@ -3135,6 +4651,21 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
+ "node_modules/ansis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.4.0.tgz",
+ "integrity": "sha512-9k3v7xcHwgdO/DruxGIg4HtjvlAZlcnsX/mzqUb1t3NkYnl9kK2UJ+Gq0io+vQf7iT//BD/HB/NBkUR1LWxoeA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
"node_modules/aria-hidden": {
"version": "1.2.6",
"resolved": "https://registry.npmmirror.com/aria-hidden/-/aria-hidden-1.2.6.tgz",
@@ -3186,6 +4717,30 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/babel-dead-code-elimination": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz",
+ "integrity": "sha512-GERT7L2TiYcYDtYk1IpD+ASAYXjKbLTDPhBtYj7X1NuRMDTMtAx9kyBenub1Ev41lo91OHCKdmP+egTDmfQ7Ig==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.23.7",
+ "@babel/parser": "^7.23.6",
+ "@babel/traverse": "^7.23.7",
+ "@babel/types": "^7.23.6"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.25",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.25.tgz",
+ "integrity": "sha512-gMmEShwwq7FJqMwvfRwvCl00v4kN+KOfJqXn+f4nrufak5gNHJOksd/60Dvjuz7sI8Y5WiSFBa8FEYr+zoyqCw==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
"node_modules/bidi-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
@@ -3196,34 +4751,58 @@
"require-from-string": "^2.0.2"
}
},
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "node_modules/browserslist": {
+ "version": "4.29.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz",
+ "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
"license": "MIT",
"dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
+ "baseline-browser-mapping": "^2.11.23",
+ "caniuse-lite": "^1.0.30001810",
+ "electron-to-chromium": "^1.5.427",
+ "node-releases": "^2.0.55",
+ "update-browserslist-db": "^1.3.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
},
"engines": {
- "node": ">= 0.4"
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
- "node_modules/call-bound": {
- "version": "1.0.4",
- "resolved": "https://registry.npmmirror.com/call-bound/-/call-bound-1.0.4.tgz",
- "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "get-intrinsic": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001810",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz",
+ "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/chai": {
"version": "6.2.2",
@@ -3235,6 +4814,21 @@
"node": ">=18"
}
},
+ "node_modules/chokidar": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz",
+ "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^5.0.0"
+ },
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/class-variance-authority": {
"version": "0.7.1",
"resolved": "https://registry.npmmirror.com/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@@ -3256,17 +4850,39 @@
"node": ">=6"
}
},
- "node_modules/cookie": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
- "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
"license": "MIT",
"engines": {
- "node": ">=18"
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT"
+ },
+ "node_modules/cookie-es": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-3.1.1.tgz",
+ "integrity": "sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==",
+ "license": "MIT"
+ },
+ "node_modules/crossws": {
+ "version": "0.4.12",
+ "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.12.tgz",
+ "integrity": "sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==",
+ "license": "MIT",
+ "peerDependencies": {
+ "srvx": ">=0.11.5"
},
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
+ "peerDependenciesMeta": {
+ "srvx": {
+ "optional": true
+ }
}
},
"node_modules/css-tree": {
@@ -3303,8 +4919,8 @@
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "license": "MIT",
- "peer": true
+ "dev": true,
+ "license": "MIT"
},
"node_modules/data-urls": {
"version": "6.0.1",
@@ -3330,11 +4946,44 @@
"node": ">=20"
}
},
+ "node_modules/db0": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/db0/-/db0-0.3.4.tgz",
+ "integrity": "sha512-RiXXi4WaNzPTHEOu8UPQKMooIbqOEyqA1t7Z6MsdxSCeb8iUC9ko3LcmsLmeUt2SM5bctfArZKkRQggKZz7JNw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@electric-sql/pglite": "*",
+ "@libsql/client": "*",
+ "better-sqlite3": "*",
+ "drizzle-orm": "*",
+ "mysql2": "*",
+ "sqlite3": "*"
+ },
+ "peerDependenciesMeta": {
+ "@electric-sql/pglite": {
+ "optional": true
+ },
+ "@libsql/client": {
+ "optional": true
+ },
+ "better-sqlite3": {
+ "optional": true
+ },
+ "drizzle-orm": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ }
+ }
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -3369,7 +5018,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
- "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -3381,6 +5029,15 @@
"integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
"license": "MIT"
},
+ "node_modules/diff": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz",
+ "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/dom-accessibility-api": {
"version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@@ -3388,25 +5045,16 @@
"dev": true,
"license": "MIT"
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.431",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.431.tgz",
+ "integrity": "sha512-AAVihz2YwJeOdAynX8MUtqpvjY0gaiARp/7+r4kwqgzqXMrfG1qDN7vwT80uxAoOQRPcZH8t73EI+CA/1OJN8A==",
+ "license": "ISC"
},
"node_modules/enhanced-resolve": {
"version": "5.24.5",
"resolved": "https://registry.npmmirror.com/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
"integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -3429,31 +5077,39 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
- "node_modules/error-stack-parser": {
- "version": "2.1.4",
- "resolved": "https://registry.npmmirror.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
- "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
+ "node_modules/env-runner": {
+ "version": "0.1.16",
+ "resolved": "https://registry.npmjs.org/env-runner/-/env-runner-0.1.16.tgz",
+ "integrity": "sha512-2LRJM4P2KLX6J83QZZrMqvgCDt/D5ea7wPcI3yYiy5cG/9rX5QwdwZFx0D7ktWnjdRyZxYjttGGorb5nFqb1CA==",
"license": "MIT",
"dependencies": {
- "stackframe": "^1.3.4"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmmirror.com/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
+ "crossws": "^0.4.8",
+ "exsolve": "^1.1.0",
+ "httpxy": "^0.5.4",
+ "srvx": "^0.11.19"
+ },
+ "bin": {
+ "env-runner": "dist/cli.mjs"
+ },
+ "peerDependencies": {
+ "@netlify/runtime": "^4.1.23",
+ "@vercel/queue": ">=0.2.0",
+ "miniflare": "^4.20260515.0",
+ "wrangler": "^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@netlify/runtime": {
+ "optional": true
+ },
+ "@vercel/queue": {
+ "optional": true
+ },
+ "miniflare": {
+ "optional": true
+ },
+ "wrangler": {
+ "optional": true
+ }
}
},
"node_modules/es-module-lexer": {
@@ -3463,16 +5119,55 @@
"dev": true,
"license": "MIT"
},
- "node_modules/es-object-atoms": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
- "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "dev": true,
+ "hasInstallScript": true,
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
+ "bin": {
+ "esbuild": "bin/esbuild"
},
"engines": {
- "node": ">= 0.4"
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/estree-walker": {
@@ -3495,11 +5190,16 @@
"node": ">=12.0.0"
}
},
+ "node_modules/exsolve": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz",
+ "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==",
+ "license": "MIT"
+ },
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -3513,6 +5213,12 @@
}
}
},
+ "node_modules/fetchdts": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/fetchdts/-/fetchdts-0.1.7.tgz",
+ "integrity": "sha512-YoZjBdafyLIop9lSxXVI33oLD5kN31q4Td+CasofLLYeLXRFeOsuOw0Uo+XNRi9PZlbfdlN2GmRtm4tCEQ9/KA==",
+ "license": "MIT"
+ },
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -3528,37 +5234,13 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"license": "MIT",
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
- },
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=6.9.0"
}
},
"node_modules/get-nonce": {
@@ -3570,38 +5252,89 @@
"node": ">=6"
}
},
- "node_modules/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "license": "MIT",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
+ "node_modules/globrex": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
+ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==",
+ "license": "MIT"
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
"license": "ISC"
},
+ "node_modules/h3": {
+ "version": "2.0.1-rc.32",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.32.tgz",
+ "integrity": "sha512-Epg1E85L8yIKd9UeAQAVdm6QVrnHbppMsQ641EjpNi4c0IlAg+PJlwp4Xw03Z1loRXPQaH9k64gJT5qDELqm+g==",
+ "license": "MIT",
+ "dependencies": {
+ "rou3": "^0.9.2",
+ "srvx": "^1.0.5"
+ },
+ "bin": {
+ "h3": "bin/h3.mjs"
+ },
+ "engines": {
+ "node": ">=20.11.1"
+ },
+ "peerDependencies": {
+ "crossws": "^0.4.12",
+ "ocache": ">=0.3.0"
+ },
+ "peerDependenciesMeta": {
+ "crossws": {
+ "optional": true
+ },
+ "ocache": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/h3-v2": {
+ "name": "h3",
+ "version": "2.0.1-rc.20",
+ "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.20.tgz",
+ "integrity": "sha512-28ljodXuUp0fZovdiSRq4G9OgrxCztrJe5VdYzXAB7ueRvI7pIUqLU14Xi3XqdYJ/khXjfpUOOD2EQa6CmBgsg==",
+ "license": "MIT",
+ "dependencies": {
+ "rou3": "^0.8.1",
+ "srvx": "^0.11.13"
+ },
+ "bin": {
+ "h3": "bin/h3.mjs"
+ },
+ "engines": {
+ "node": ">=20.11.1"
+ },
+ "peerDependencies": {
+ "crossws": "^0.4.1"
+ },
+ "peerDependenciesMeta": {
+ "crossws": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/h3/node_modules/rou3": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.9.2.tgz",
+ "integrity": "sha512-3SOzvaAg8rkHrXtRjpCvCvbyO5to9oOO27Z/XqHEYXfMRVSw/qMIVdmaOk9W2lcRLtR6dlqTjo9hDeJk70QBYQ==",
+ "license": "MIT"
+ },
+ "node_modules/h3/node_modules/srvx": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/srvx/-/srvx-1.0.5.tgz",
+ "integrity": "sha512-KvSKRpgPG/oaq3cyT614OQ2bAa7DynuGamivCZeoZzIUULOdbQz6wUtnnVuC4+DCx2Zglzo8v5gBYmWIWYhDxA==",
+ "license": "MIT",
+ "bin": {
+ "srvx": "bin/srvx.mjs"
+ },
+ "engines": {
+ "node": ">=20.16.0"
+ }
+ },
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -3612,29 +5345,11 @@
"node": ">=8"
}
},
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/hasown": {
- "version": "2.0.4",
- "resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.4.tgz",
- "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
+ "node_modules/hookable": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.2.tgz",
+ "integrity": "sha512-+abwxtiEA52GCVIsQqut3S/uKTbUwYIp4Pe/vv+6py5XiXBCqMZHg6pA6Y5qhgLSEys0/cYuPbO0z1QFj5ZCmg==",
+ "license": "MIT"
},
"node_modules/html-encoding-sniffer": {
"version": "4.0.0",
@@ -3684,6 +5399,12 @@
"node": ">= 14"
}
},
+ "node_modules/httpxy": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.5.tgz",
+ "integrity": "sha512-uDjmnPyp1q4Sgzf3w+J/Fc6UqcCEj0x4Wjp7OqK5dGhNeDgpyrAmnS6ey8QWrX3SWDon2DMKf9sBa5X9+CVyMA==",
+ "license": "MIT"
+ },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
@@ -3704,6 +5425,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/isbot": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/isbot/-/isbot-5.2.2.tgz",
+ "integrity": "sha512-iQcBXcd+Rv/pkubRyGh2utW2j1oPG5hZY6TUhVPpqK4G+o3IbxpJNx04hgksjc/N7GK5pEorUxDeg31cFgEk/w==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
@@ -3747,7 +5477,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-2.7.0.tgz",
"integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==",
- "dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
@@ -3757,9 +5486,30 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/js-yaml": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz",
+ "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/jsdom": {
"version": "27.0.1",
"resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz",
@@ -3800,11 +5550,34 @@
}
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/lightningcss": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
- "dev": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -3837,7 +5610,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3858,7 +5630,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3879,7 +5650,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3900,7 +5670,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3921,7 +5690,6 @@
"cpu": [
"arm"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3942,7 +5710,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3963,7 +5730,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -3984,7 +5750,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4005,7 +5770,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4026,7 +5790,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4047,7 +5810,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -4061,18 +5823,6 @@
"url": "https://opencollective.com/parcel"
}
},
- "node_modules/lodash": {
- "version": "4.18.1",
- "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz",
- "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
- "license": "MIT"
- },
- "node_modules/lodash-es": {
- "version": "4.18.1",
- "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz",
- "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==",
- "license": "MIT"
- },
"node_modules/lru-cache": {
"version": "11.5.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
@@ -4083,6 +5833,15 @@
"node": "20 || >=22"
}
},
+ "node_modules/lucide-react": {
+ "version": "1.47.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.47.0.tgz",
+ "integrity": "sha512-o8C23aXpNQypRY73W7fW02EyvWJinEMXgKeGjFoKym0zj3Q73hD97A1IQps1g8C14HTGsOpZL0Am+xjfgFZAbg==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/lz-string": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
@@ -4097,7 +5856,6 @@
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
@@ -4131,15 +5889,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
"node_modules/mdn-data": {
"version": "2.27.1",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
@@ -4151,7 +5900,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -4173,16 +5921,92 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
- "node_modules/object-inspect": {
- "version": "1.13.4",
- "resolved": "https://registry.npmmirror.com/object-inspect/-/object-inspect-1.13.4.tgz",
- "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "node_modules/nf3": {
+ "version": "0.3.24",
+ "resolved": "https://registry.npmjs.org/nf3/-/nf3-0.3.24.tgz",
+ "integrity": "sha512-HxLK4bo+5jNsEETZp4w3tJblHOA9MCBY14IN9nZJJV8JDxt9yNIYxuBuLMjTAR5GFa3HL61+8VQDUrXv3/C8fw==",
+ "license": "MIT"
+ },
+ "node_modules/nitro": {
+ "version": "3.0.260415-beta",
+ "resolved": "https://registry.npmjs.org/nitro/-/nitro-3.0.260415-beta.tgz",
+ "integrity": "sha512-J0ntJERWtIdvweZdmkCiF8eOFvP9fIAJR2gpeIDrHbAlYavK41WQfADo/YoZ/LF7RMTZBiPaH/pt2s/nPru9Iw==",
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.4.2",
+ "crossws": "^0.4.5",
+ "db0": "^0.3.4",
+ "env-runner": "^0.1.7",
+ "h3": "^2.0.1-rc.20",
+ "hookable": "^6.1.1",
+ "nf3": "^0.3.16",
+ "ocache": "^0.1.4",
+ "ofetch": "^2.0.0-alpha.3",
+ "ohash": "^2.0.11",
+ "rolldown": "^1.0.0-rc.15",
+ "srvx": "^0.11.15",
+ "unenv": "^2.0.0-rc.24",
+ "unstorage": "^2.0.0-alpha.7"
+ },
+ "bin": {
+ "nitro": "dist/cli/index.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@vercel/queue": "^0.1.4",
+ "dotenv": "*",
+ "giget": "*",
+ "jiti": "^2.6.1",
+ "rollup": "^4.60.1",
+ "vite": "^7 || ^8",
+ "xml2js": "^0.6.2",
+ "zephyr-agent": "^0.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@vercel/queue": {
+ "optional": true
+ },
+ "dotenv": {
+ "optional": true
+ },
+ "giget": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "xml2js": {
+ "optional": true
+ },
+ "zephyr-agent": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nitro/node_modules/ocache": {
+ "version": "0.1.5",
+ "resolved": "https://registry.npmjs.org/ocache/-/ocache-0.1.5.tgz",
+ "integrity": "sha512-kNNnkkVQup/QDvmTz8Q84wc2ntiyoVHDxa6eHWKt5qdGAmFRBIxy83rxgCYEjW0x06UJ9E3P6VgM2yY4rOBH4w==",
+ "license": "MIT",
+ "dependencies": {
+ "ohash": "^2.0.11"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.56",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.56.tgz",
+ "integrity": "sha512-x0InOIyzgdk+eyaWaRJFH5snEtiImgBgblZ2CyPrLmqqcuMQkEvcDPHbzqbD8eDsSeJbVOjn+crzyzHaM4D+/A==",
"license": "MIT",
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=18"
}
},
"node_modules/obug": {
@@ -4199,10 +6023,16 @@
"node": ">=12.20.0"
}
},
- "node_modules/papaparse": {
- "version": "5.7.0",
- "resolved": "https://registry.npmmirror.com/papaparse/-/papaparse-5.7.0.tgz",
- "integrity": "sha512-qBGxg/7Q3Kl9Wfhrz2Z74UnvnHTXLNG6jmKJFeBvP2+y4lV7So+7SR62+Zd47JvdrCkX+nDcnr0ObPzek/+6RA==",
+ "node_modules/ofetch": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==",
+ "license": "MIT"
+ },
+ "node_modules/ohash": {
+ "version": "2.0.12",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.12.tgz",
+ "integrity": "sha512-65S/5gk9YSsaRjcyf7Nfa6h/d3E8/1gslpXfI4W7Dxn/oap8IKRuNT5VXkLQ1YFKIEg4apRY4Pj6aiwFzrDdmw==",
"license": "MIT"
},
"node_modules/parse5": {
@@ -4222,21 +6052,18 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
- "dev": true,
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -4292,15 +6119,6 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
- "node_modules/pluralize": {
- "version": "8.0.0",
- "resolved": "https://registry.npmmirror.com/pluralize/-/pluralize-8.0.0.tgz",
- "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/postcss": {
"version": "8.5.26",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz",
@@ -4330,6 +6148,21 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/prettier": {
+ "version": "3.9.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.8.tgz",
+ "integrity": "sha512-WRFq3Wn3WId7LLROfMLdH7xaFr2jR62wU8nLO6rQUOLOxNZUviyJQs1M0iIhLexSFy+L+w0ch66wtoO2jRjG0A==",
+ "license": "MIT",
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
@@ -4362,22 +6195,6 @@
"node": ">=6"
}
},
- "node_modules/qs": {
- "version": "6.16.0",
- "resolved": "https://registry.npmmirror.com/qs/-/qs-6.16.0.tgz",
- "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==",
- "license": "BSD-3-Clause",
- "dependencies": {
- "es-define-property": "^1.0.1",
- "side-channel": "^1.1.1"
- },
- "engines": {
- "node": ">=0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/radix-ui": {
"version": "1.6.7",
"resolved": "https://registry.npmmirror.com/radix-ui/-/radix-ui-1.6.7.tgz",
@@ -4476,6 +6293,16 @@
"react": "^19.2.8"
}
},
+ "node_modules/react-refresh": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz",
+ "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react-remove-scroll": {
"version": "2.7.2",
"resolved": "https://registry.npmmirror.com/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
@@ -4523,44 +6350,6 @@
}
}
},
- "node_modules/react-router": {
- "version": "7.18.2",
- "resolved": "https://registry.npmmirror.com/react-router/-/react-router-7.18.2.tgz",
- "integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
- "license": "MIT",
- "dependencies": {
- "cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- },
- "peerDependenciesMeta": {
- "react-dom": {
- "optional": true
- }
- }
- },
- "node_modules/react-router-dom": {
- "version": "7.18.2",
- "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-7.18.2.tgz",
- "integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
- "license": "MIT",
- "dependencies": {
- "react-router": "7.18.2"
- },
- "engines": {
- "node": ">=20.0.0"
- },
- "peerDependencies": {
- "react": ">=18",
- "react-dom": ">=18"
- }
- },
"node_modules/react-style-singleton": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
@@ -4583,6 +6372,19 @@
}
}
},
+ "node_modules/readdirp": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.1.1.tgz",
+ "integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20.19.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
"node_modules/remixicon": {
"version": "4.9.1",
"resolved": "https://registry.npmmirror.com/remixicon/-/remixicon-4.9.1.tgz",
@@ -4603,7 +6405,6 @@
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.6.tgz",
"integrity": "sha512-vMM4q3aixf46GiF1Kok8jDPFsEpXgFWGjUHXNkNHNm+Y2adXAG2dbX91jkti3i0ZRsOlcmbuzAz1poObSHCmUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.147.0",
@@ -4633,6 +6434,58 @@
"@rolldown/binding-win32-x64-msvc": "1.2.6"
}
},
+ "node_modules/rollup": {
+ "version": "4.63.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz",
+ "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.63.3",
+ "@rollup/rollup-android-arm64": "4.63.3",
+ "@rollup/rollup-darwin-arm64": "4.63.3",
+ "@rollup/rollup-darwin-x64": "4.63.3",
+ "@rollup/rollup-freebsd-arm64": "4.63.3",
+ "@rollup/rollup-freebsd-x64": "4.63.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.63.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.63.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.63.3",
+ "@rollup/rollup-linux-arm64-musl": "4.63.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.63.3",
+ "@rollup/rollup-linux-loong64-musl": "4.63.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.63.3",
+ "@rollup/rollup-linux-ppc64-musl": "4.63.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.63.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.63.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.63.3",
+ "@rollup/rollup-linux-x64-gnu": "4.63.3",
+ "@rollup/rollup-linux-x64-musl": "4.63.3",
+ "@rollup/rollup-openbsd-x64": "4.63.3",
+ "@rollup/rollup-openharmony-arm64": "4.63.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.63.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.63.3",
+ "@rollup/rollup-win32-x64-gnu": "4.63.3",
+ "@rollup/rollup-win32-x64-msvc": "4.63.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rou3": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.8.1.tgz",
+ "integrity": "sha512-ePa+XGk00/3HuCqrEnK3LxJW7I0SdNg6EFzKUJG73hMAdDcOUC/i/aSz7LSDwLrGr33kal/rqOGydzwl6U7zBA==",
+ "license": "MIT"
+ },
"node_modules/rrweb-cssom": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
@@ -4679,82 +6532,25 @@
"node": ">=10"
}
},
- "node_modules/set-cookie-parser": {
- "version": "2.7.2",
- "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
- "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
- "license": "MIT"
- },
- "node_modules/side-channel": {
- "version": "1.1.1",
- "resolved": "https://registry.npmmirror.com/side-channel/-/side-channel-1.1.1.tgz",
- "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "node_modules/seroval": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.6.7.tgz",
+ "integrity": "sha512-AeDcLh0yO2SFm9W71essgnSzLV9DI8ZH0x0knXn2DMnUZj728mpLbxjlbB6IqKCmqh8JA3cEqRyGoNkt584JcQ==",
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.4",
- "side-channel-list": "^1.0.1",
- "side-channel-map": "^1.0.1",
- "side-channel-weakmap": "^1.0.2"
- },
"engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "node": ">=10"
}
},
- "node_modules/side-channel-list": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/side-channel-list/-/side-channel-list-1.0.1.tgz",
- "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "node_modules/seroval-plugins": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.6.7.tgz",
+ "integrity": "sha512-4Nk35ttD3DTDJW4hgw5StsVAPeU6qnDFnULAouw6tQ7oLTV/ICXrWpsXo2EE52eSP2joUMazbVf52mFEcADqRw==",
"license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "object-inspect": "^1.13.4"
- },
"engines": {
- "node": ">= 0.4"
+ "node": ">=10"
},
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-map": {
- "version": "1.0.1",
- "resolved": "https://registry.npmmirror.com/side-channel-map/-/side-channel-map-1.0.1.tgz",
- "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/side-channel-weakmap": {
- "version": "1.0.2",
- "resolved": "https://registry.npmmirror.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
- "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
- "license": "MIT",
- "dependencies": {
- "call-bound": "^1.0.2",
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.5",
- "object-inspect": "^1.13.3",
- "side-channel-map": "^1.0.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "peerDependencies": {
+ "seroval": "^1.0"
}
},
"node_modules/siginfo": {
@@ -4764,16 +6560,36 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/source-map": {
+ "version": "0.7.6",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
+ "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
+ "node_modules/srvx": {
+ "version": "0.11.22",
+ "resolved": "https://registry.npmjs.org/srvx/-/srvx-0.11.22.tgz",
+ "integrity": "sha512-LqZxxBDMKuMAZzFzJnDCkFOrs9MZQZr0LvHiO/SuSZVdQaXD7xQ5UWTUxheJrQPve1qk9MG2B/yttUvJxw8egQ==",
+ "license": "MIT",
+ "bin": {
+ "srvx": "bin/srvx.mjs"
+ },
+ "engines": {
+ "node": ">=20.16.0"
+ }
+ },
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -4781,12 +6597,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/stackframe": {
- "version": "1.3.4",
- "resolved": "https://registry.npmmirror.com/stackframe/-/stackframe-1.3.4.tgz",
- "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
- "license": "MIT"
- },
"node_modules/std-env": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz",
@@ -4828,14 +6638,12 @@
"version": "4.3.3",
"resolved": "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.3.3.tgz",
"integrity": "sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz",
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -4866,7 +6674,6 @@
"version": "0.2.17",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -4935,12 +6742,42 @@
"node": ">=20"
}
},
+ "node_modules/tsconfck": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz",
+ "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==",
+ "deprecated": "unmaintained",
+ "license": "MIT",
+ "bin": {
+ "tsconfck": "bin/tsconfck.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz",
@@ -4955,6 +6792,122 @@
"node": ">=14.17"
}
},
+ "node_modules/ufo": {
+ "version": "1.6.4",
+ "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz",
+ "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.24",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
+ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/unplugin": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-3.4.0.tgz",
+ "integrity": "sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "picomatch": "^4.0.7",
+ "webpack-virtual-modules": "^0.6.2"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "peerDependencies": {
+ "@farmfe/core": "*",
+ "@rsbuild/core": "*",
+ "@rspack/core": "*",
+ "bun-types-no-globals": "*",
+ "esbuild": "*",
+ "rolldown": "*",
+ "rollup": "*",
+ "unloader": "*",
+ "vite": "*",
+ "webpack": "*"
+ },
+ "peerDependenciesMeta": {
+ "@farmfe/core": {
+ "optional": true
+ },
+ "@rsbuild/core": {
+ "optional": true
+ },
+ "@rspack/core": {
+ "optional": true
+ },
+ "bun-types-no-globals": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "rolldown": {
+ "optional": true
+ },
+ "rollup": {
+ "optional": true
+ },
+ "unloader": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ },
+ "webpack": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/unstorage": {
+ "version": "2.0.0-alpha.10",
+ "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-2.0.0-alpha.10.tgz",
+ "integrity": "sha512-6h1veZp8gnp4dGllf0tDijoXxDYymJu251IpKKkrSVH+QHL6tXFbwG85KM+CBHSgpkkgtPuIiZ9oLCLP5+1Evw==",
+ "license": "MIT"
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz",
+ "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
"node_modules/use-callback-ref": {
"version": "1.3.3",
"resolved": "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
@@ -4998,18 +6951,28 @@
}
}
},
+ "node_modules/use-sync-external-store": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz",
+ "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/vite": {
- "version": "8.2.2",
- "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz",
- "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
+ "version": "7.3.6",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz",
+ "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "lightningcss": "^1.33.0",
- "picomatch": "^4.0.5",
- "postcss": "^8.5.26",
- "rolldown": "~1.2.4",
- "tinyglobby": "^0.2.17"
+ "esbuild": "^0.27.0 || ^0.28.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
},
"bin": {
"vite": "bin/vite.js"
@@ -5025,10 +6988,9 @@
},
"peerDependencies": {
"@types/node": "^20.19.0 || >=22.12.0",
- "@vitejs/devtools": "^0.4.0 || ^0.5.0",
- "esbuild": "^0.27.0 || ^0.28.0",
"jiti": ">=1.21.0",
"less": "^4.0.0",
+ "lightningcss": "^1.21.0",
"sass": "^1.70.0",
"sass-embedded": "^1.70.0",
"stylus": ">=0.54.8",
@@ -5041,18 +7003,15 @@
"@types/node": {
"optional": true
},
- "@vitejs/devtools": {
- "optional": true
- },
- "esbuild": {
- "optional": true
- },
"jiti": {
"optional": true
},
"less": {
"optional": true
},
+ "lightningcss": {
+ "optional": true
+ },
"sass": {
"optional": true
},
@@ -5076,6 +7035,44 @@
}
}
},
+ "node_modules/vite-tsconfig-paths": {
+ "version": "5.1.4",
+ "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz",
+ "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "globrex": "^0.1.2",
+ "tsconfck": "^3.0.3"
+ },
+ "peerDependencies": {
+ "vite": "*"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vitefu": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz",
+ "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==",
+ "license": "MIT",
+ "workspaces": [
+ "tests/deps/*",
+ "tests/projects/*",
+ "tests/projects/workspace/packages/*"
+ ],
+ "peerDependencies": {
+ "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "vite": {
+ "optional": true
+ }
+ }
+ },
"node_modules/vitest": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz",
@@ -5179,12 +7176,6 @@
"node": ">=18"
}
},
- "node_modules/warn-once": {
- "version": "0.1.1",
- "resolved": "https://registry.npmmirror.com/warn-once/-/warn-once-0.1.1.tgz",
- "integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
- "license": "MIT"
- },
"node_modules/webidl-conversions": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
@@ -5195,6 +7186,12 @@
"node": ">=20"
}
},
+ "node_modules/webpack-virtual-modules": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
+ "license": "MIT"
+ },
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
@@ -5282,12 +7279,42 @@
"node": ">=18"
}
},
+ "node_modules/xmlbuilder2": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz",
+ "integrity": "sha512-bx8Q1STctnNaaDymWnkfQLKofs0mGNN7rLLapJlGuV3VlvegD7Ls4ggMjE3aUSWItCCzU0PEv45lI87iSigiCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@oozcitak/dom": "^2.0.2",
+ "@oozcitak/infra": "^2.0.2",
+ "@oozcitak/util": "^10.0.0",
+ "js-yaml": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=20.0"
+ }
+ },
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true,
"license": "MIT"
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "license": "ISC"
+ },
+ "node_modules/zod": {
+ "version": "4.6.5",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.5.tgz",
+ "integrity": "sha512-v5l/aFXZQeai4awLbOpSoHecE9UiMrnfx75tEXLjNonXVARxQ5mOeipTjROUchszUNCqnE+hqAMujRsRHsut2Q==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
}
}
}
diff --git a/web/package.json b/web/package.json
index bea1c4a..e975e6d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -4,35 +4,55 @@
"version": "0.1.0",
"type": "module",
"scripts": {
+ "dev": "vite dev --host 0.0.0.0",
"build": "vite build",
- "dev": "vite",
+ "start": "node .output/server/index.mjs",
+ "routes:generate": "node scripts/generate-route-tree.mjs",
+ "predev": "npm run routes:generate",
+ "prebuild": "npm run routes:generate",
+ "pretypecheck": "npm run routes:generate",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
- "test:responsive": "playwright test"
+ "test:responsive": "playwright test",
+ "typecheck": "tsc --noEmit"
},
"dependencies": {
- "@refinedev/core": "^5.0.12",
+ "@tailwindcss/vite": "^4.3.3",
+ "@tanstack/react-query": "5.95.2",
+ "@tanstack/react-router": "1.170.38",
+ "@tanstack/react-router-with-query": "1.130.17",
+ "@tanstack/react-start": "1.168.56",
+ "@tanstack/router-plugin": "1.168.40",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "lucide-react": "^1.28.0",
+ "nitro": "^3.0.260415-beta",
"radix-ui": "^1.6.7",
"react": "19.2.8",
"react-dom": "19.2.8",
- "react-router": "^7.18.2",
- "react-router-dom": "^7.18.2",
"remixicon": "^4.9.1",
- "tailwind-merge": "^3.6.0"
+ "tailwind-merge": "^3.6.0",
+ "tailwindcss": "^4.3.3",
+ "tw-animate-css": "^1.4.0",
+ "vite-tsconfig-paths": "^5.1.4"
},
"devDependencies": {
"@playwright/test": "1.62.1",
- "@tailwindcss/vite": "^4.3.3",
+ "@tanstack/router-generator": "1.167.38",
"@testing-library/dom": "10.4.1",
"@testing-library/react": "16.3.2",
- "@vitejs/plugin-react": "6.1.0",
+ "@types/node": "^22.12.0",
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0",
+ "@vitejs/plugin-react": "5.2.0",
"@vitest/coverage-v8": "4.1.11",
"jsdom": "27.0.1",
- "tailwindcss": "^4.3.3",
"typescript": "^5.9.3",
- "vite": "8.2.2",
+ "vite": "7.3.6",
"vitest": "4.1.11"
+ },
+ "overrides": {
+ "@types/react": "^19.0.0",
+ "@types/react-dom": "^19.0.0"
}
}
diff --git a/web/playwright.config.js b/web/playwright.config.js
index bc455fa..c8681f4 100644
--- a/web/playwright.config.js
+++ b/web/playwright.config.js
@@ -3,10 +3,10 @@ import { defineConfig } from '@playwright/test'
export default defineConfig({
testDir: './tests',
testMatch: '*.e2e.js',
- use: { baseURL: 'http://127.0.0.1:4173', channel: 'chrome' },
+ use: { baseURL: 'http://127.0.0.1:4174', channel: 'chrome' },
webServer: {
- command: 'npm run dev -- --host 127.0.0.1 --port 4173',
- url: 'http://127.0.0.1:4173',
- reuseExistingServer: true,
+ command: 'npm run dev -- --host 127.0.0.1 --port 4174',
+ url: 'http://127.0.0.1:4174',
+ reuseExistingServer: false,
},
})
diff --git a/web/scripts/generate-route-tree.mjs b/web/scripts/generate-route-tree.mjs
new file mode 100644
index 0000000..c32ce4d
--- /dev/null
+++ b/web/scripts/generate-route-tree.mjs
@@ -0,0 +1,14 @@
+import path from 'node:path'
+import { Generator } from '@tanstack/router-generator'
+import { getConfig } from '@tanstack/router-plugin'
+
+const root = process.cwd()
+const config = getConfig({
+ target: 'react',
+ routesDirectory: path.resolve(root, 'src/routes'),
+ generatedRouteTree: path.resolve(root, 'src/routeTree.gen.ts'),
+ routeTreeFileHeader: [],
+ routeTreeFileFooter: [],
+}, root)
+
+await new Generator({ config, root }).run()
diff --git a/web/src/app/Layout.jsx b/web/src/app/Layout.jsx
index 2761fe1..14a73ce 100644
--- a/web/src/app/Layout.jsx
+++ b/web/src/app/Layout.jsx
@@ -1,180 +1,148 @@
-import { useEffect, useState } from "react";
-import { Link, Outlet, useLocation } from "react-router";
-import { cn } from "../shared/ui/ui.jsx";
-
-const expandedWidth = 240;
-const collapsedWidth = 76;
+import { Link, Outlet, useLocation, useNavigate } from '../shared/router.jsx'
+import { cn } from '../shared/ui/ui.jsx'
+import {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarTrigger,
+ useSidebar,
+} from '../components/ui/sidebar'
+import { Separator } from '../components/ui/separator'
const menu = [
{
- section: "运营",
+ section: '运营',
items: [
- { to: "/accounts", icon: "ri-account-circle-line", label: "账号管理" },
- {
- to: "/creator/competitors",
- icon: "ri-line-chart-line",
- label: "竞品分析",
- },
- { to: "/creator/workbench", icon: "ri-chat-3-line", label: "运营工作台" },
- {
- to: "/creator/settings",
- icon: "ri-settings-3-line",
- label: "采集设置",
- },
- { to: "/tasks", icon: "ri-task-line", label: "任务中心" },
- { to: "/audit", icon: "ri-history-line", label: "审计记录" },
+ { to: '/accounts', icon: 'ri-account-circle-line', label: '账号管理' },
+ { to: '/creator/competitors', icon: 'ri-line-chart-line', label: '竞品分析' },
+ { to: '/creator/workbench', icon: 'ri-chat-3-line', label: '运营工作台' },
+ { to: '/creator/settings', icon: 'ri-settings-3-line', label: '采集设置' },
+ { to: '/tasks', icon: 'ri-task-line', label: '任务中心' },
+ { to: '/audit', icon: 'ri-history-line', label: '审计记录' },
],
},
{
- section: "资源",
+ section: '资源',
items: [
- { to: "/browsers", icon: "ri-hard-drive-3-line", label: "运行环境" },
- { to: "/network-exits", icon: "ri-earth-line", label: "网络出口" },
- { to: "/browser-versions", icon: "ri-window-line", label: "浏览器版本" },
- { to: "/gateways", icon: "ri-router-line", label: "网关管理" },
+ { to: '/browsers', icon: 'ri-hard-drive-3-line', label: '运行环境' },
+ { to: '/network-exits', icon: 'ri-earth-line', label: '网络出口' },
+ { to: '/browser-versions', icon: 'ri-window-line', label: '浏览器版本' },
+ { to: '/gateways', icon: 'ri-router-line', label: '网关管理' },
],
},
-];
+]
-function MenuLink({ to, icon, label, collapsed, onNavigate }) {
- const { pathname } = useLocation();
- const active = pathname === to || pathname.startsWith(`${to}/`);
- return (
-
-
- {collapsed ? null : {label}}
-
- );
+function isActivePath(pathname, to) {
+ return pathname === to || pathname.startsWith(`${to}/`)
}
-function Sidebar({ collapsed, onNavigate }) {
+function MenuLink({ item, pathname }) {
+ const { isMobile, setOpenMobile } = useSidebar()
return (
-
- );
+
+
+ { if (isMobile) setOpenMobile(false) }}>
+
+ {item.label}
+
+
+
+ )
}
-export function CreatorHubLayout() {
- const [collapsed, setCollapsed] = useState(false);
- const [mobileOpen, setMobileOpen] = useState(false);
- const { pathname } = useLocation();
+function AppSidebar({ pathname }) {
+ const navigate = useNavigate()
+ return (
+
+
+
+
+
+
+
+
+
+
+ CreatorHub
+ 运营控制台
+
+
+
+
+
+
+
+ {menu.map((group) => (
+
+ {group.section}
+
+
+ {group.items.map((item) => )}
+
+
+
+ ))}
+
+
+
+
+ {
+ window.localStorage.removeItem('creatorhub.auth')
+ navigate({ to: '/login', replace: true })
+ }}
+ >
+
+ 退出登录
+
+
+
+
+
+
+ )
+}
- useEffect(() => {
- setMobileOpen(false);
- }, [pathname]);
-
- const width = collapsed ? collapsedWidth : expandedWidth;
- const desktop =
- typeof window !== "undefined" &&
- window.matchMedia("(min-width: 768px)").matches;
- let menuLabel = "打开导航";
- if (desktop) {
- menuLabel = collapsed ? "展开导航" : "收起导航";
+function pageTitle(pathname) {
+ for (const group of menu) {
+ const match = group.items.find((item) => isActivePath(pathname, item.to))
+ if (match) return match.label
}
-
- return (
-
-
- {mobileOpen ? (
-
-
setMobileOpen(false)}
- />
-
-
- ) : null}
-
-
-
-
-
-
-
-
-
- );
+ return 'CreatorHub'
}
+
+export function CreatorHubLayout({ children }) {
+ const { pathname } = useLocation()
+ return (
+
+
+
+
+ 跳到主要内容
+
+
+
+
+
+
{pageTitle(pathname)}
+
CreatorHub 运营控制台
+
+
+ {children ?? }
+
+
+ )
+}
+
+export { menu }
diff --git a/web/src/app/Layout.test.jsx b/web/src/app/Layout.test.jsx
index d8dd540..6b1f199 100644
--- a/web/src/app/Layout.test.jsx
+++ b/web/src/app/Layout.test.jsx
@@ -1,6 +1,6 @@
import { afterEach, describe, expect, it } from "vitest";
import { cleanup, render, screen } from "@testing-library/react";
-import { MemoryRouter, Route, Routes } from "react-router";
+import { MemoryRouter, Route, Routes } from "../test/routerCompat.jsx";
import { CreatorHubLayout } from "./Layout";
const originalMatchMedia = window.matchMedia;
diff --git a/web/src/app/LoginPage.jsx b/web/src/app/LoginPage.jsx
index f2909bf..5ed03d5 100644
--- a/web/src/app/LoginPage.jsx
+++ b/web/src/app/LoginPage.jsx
@@ -1,5 +1,5 @@
import { useState } from "react";
-import { useNavigate } from "react-router";
+import { useNavigate } from "../shared/router.jsx";
import { Alert, Button, Field, Input } from "../shared/ui/ui.jsx";
// 开发阶段:本地保存 Basic 凭证,仅在请求层自动附带。开发代理(vite)不注入 Authorization。
diff --git a/web/src/app/main.jsx b/web/src/app/main.jsx
deleted file mode 100644
index 1521a3b..0000000
--- a/web/src/app/main.jsx
+++ /dev/null
@@ -1,98 +0,0 @@
-import { StrictMode } from "react";
-import { createRoot } from "react-dom/client";
-import { HashRouter, Navigate, Route, Routes, useLocation } from "react-router";
-import { Refine } from "@refinedev/core";
-import { dataProvider } from "../shared/api/dataProvider";
-import { CreatorHubLayout } from "./Layout";
-import { LoginPage } from "./LoginPage";
-import { AccountCreatePage, AccountDetail, AccountList } from "../features/accounts/AccountsPage";
-import { AccountEditPage } from "../features/accounts/AccountEditPage";
-import { CreatorCompetitorsPage } from "../features/creator/competitors/CreatorCompetitorsPage";
-import { CreatorSettingsPage } from "../features/creator/settings/CreatorSettingsPage";
-import { CreatorWorkbenchPage } from "../features/creator/workbench/CreatorWorkbenchPage";
-import { AuditPage } from "../features/operations/AuditPage";
-import { BrowserCreatePage, BrowserDetail, BrowserList } from "../features/environments/BrowsersPage";
-import { BrowserVersionList } from "../features/environments/BrowserVersionsPage";
-import { DraftPage } from "../features/operations/DraftPage";
-import { GatewayList } from "../features/environments/GatewaysPage";
-import { NetworkExitDetail, NetworkExitList } from "../features/environments/NetworkExitsPage";
-import { TaskDetail, TaskList } from "../features/operations/TasksPage";
-import { AttemptDetail } from "../features/operations/TracePages";
-import "remixicon/fonts/remixicon.css";
-import "./styles.css";
-
-// 开发阶段认证仅由页面层掌握:401 会清除凭证并跳转登录;localStorage 为空时直接展示登录页。
-// 不用 useState:HashRouter 的 pushState/replaceState 不触发 hashchange,状态会在导航后过期;
-// 改为每次渲染直接读 localStorage,登录后 navigate 引发重渲染即可拿到新状态。
-const authed = () => localStorage.getItem("creatorhub.auth") !== null;
-
-function AuthGate({ children }) {
- const { pathname } = useLocation();
- const loggedIn = authed();
- if (!loggedIn && pathname !== "/login")
- return
;
- if (loggedIn && pathname === "/login")
- return
;
- return children;
-}
-
-createRoot(document.getElementById("root")).render(
-
-
-
-
-
- } />
- }>
- } />
- } />
- } />
- } />
- } />
- }
- />
- }
- />
- } />
- }
- />
- }
- />
- } />
- }
- />
- } />
- } />
- } />
- } />
- }
- />
- } />
- } />
- } />
- }
- />
- } />
- } />
- } />
-
-
-
-
-
- ,
-);
diff --git a/web/src/app/styles.css b/web/src/app/styles.css
deleted file mode 100644
index a3d3e24..0000000
--- a/web/src/app/styles.css
+++ /dev/null
@@ -1,25 +0,0 @@
-@import "tailwindcss";
-
-@theme {
- --color-primary: #0866ef;
- --color-primary-dark: #0759d4;
- --color-success: #067647;
- --color-warning: #b54708;
- --color-danger: #b42318;
- --color-ink: #111827;
- --color-muted: #475467;
- --color-hairline: #d7dde7;
- --color-sidebar: #061a38;
- --color-sidebar-hover: #0b2a54;
-}
-
-html {
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
- color: var(--color-ink);
- background: #fff;
-}
-
-.anywhere {
- overflow-wrap: anywhere;
- word-break: break-all;
-}
diff --git a/web/src/components/ui/breadcrumb.tsx b/web/src/components/ui/breadcrumb.tsx
new file mode 100644
index 0000000..5c367c9
--- /dev/null
+++ b/web/src/components/ui/breadcrumb.tsx
@@ -0,0 +1,102 @@
+import * as React from 'react';
+import { ChevronRight, MoreHorizontal } from 'lucide-react';
+import { Slot } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) {
+ return
;
+}
+
+function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) {
+ return (
+
+ );
+}
+
+function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+function BreadcrumbLink({
+ asChild,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean;
+}) {
+ const Comp = asChild ? Slot.Root : 'a';
+
+ return (
+
+ );
+}
+
+function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+ );
+}
+
+function BreadcrumbSeparator({ children, className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
svg]:size-3.5', className)}
+ {...props}
+ >
+ {children ?? }
+
+ );
+}
+
+function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+
+ More
+
+ );
+}
+
+export {
+ Breadcrumb,
+ BreadcrumbList,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+ BreadcrumbEllipsis
+};
diff --git a/web/src/components/ui/button.tsx b/web/src/components/ui/button.tsx
new file mode 100644
index 0000000..e9f982b
--- /dev/null
+++ b/web/src/components/ui/button.tsx
@@ -0,0 +1,62 @@
+import * as React from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { Slot } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+const buttonVariants = cva(
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,background-color,border-color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+ {
+ variants: {
+ variant: {
+ default: 'bg-primary text-primary-foreground hover:bg-primary/90',
+ destructive:
+ 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40',
+ outline:
+ 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
+ ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
+ link: 'text-primary underline-offset-4 hover:underline'
+ },
+ size: {
+ default: 'h-9 px-4 py-2 has-[>svg]:px-3',
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
+ sm: 'h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5',
+ lg: 'h-10 rounded-md px-6 has-[>svg]:px-4',
+ icon: 'size-9',
+ 'icon-xs': "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
+ 'icon-sm': 'size-8',
+ 'icon-lg': 'size-10'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ }
+);
+
+function Button({
+ className,
+ variant = 'default',
+ size = 'default',
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> &
+ VariantProps
& {
+ asChild?: boolean;
+ }) {
+ const Comp = asChild ? Slot.Root : 'button';
+
+ return (
+
+ );
+}
+
+export { Button, buttonVariants };
diff --git a/web/src/components/ui/collapsible.tsx b/web/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..ed9712d
--- /dev/null
+++ b/web/src/components/ui/collapsible.tsx
@@ -0,0 +1,19 @@
+import { Collapsible as CollapsiblePrimitive } from 'radix-ui';
+
+function Collapsible({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function CollapsibleTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function CollapsibleContent({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent };
diff --git a/web/src/components/ui/dropdown-menu.tsx b/web/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..ad1c0ac
--- /dev/null
+++ b/web/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,228 @@
+'use client';
+
+import * as React from 'react';
+import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
+import { DropdownMenu as DropdownMenuPrimitive } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+function DropdownMenu({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ );
+}
+
+function DropdownMenuGroup({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = 'default',
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+ variant?: 'default' | 'destructive';
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({ className, ...props }: React.ComponentProps<'span'>) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent
+};
diff --git a/web/src/components/ui/input.tsx b/web/src/components/ui/input.tsx
new file mode 100644
index 0000000..ac8cfb3
--- /dev/null
+++ b/web/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from 'react';
+
+import { cn } from '@/lib/utils';
+
+function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
+ return (
+
+ );
+}
+
+export { Input };
diff --git a/web/src/components/ui/separator.tsx b/web/src/components/ui/separator.tsx
new file mode 100644
index 0000000..61e1eca
--- /dev/null
+++ b/web/src/components/ui/separator.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react';
+import { Separator as SeparatorPrimitive } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+function Separator({
+ className,
+ orientation = 'horizontal',
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export { Separator };
diff --git a/web/src/components/ui/sheet.tsx b/web/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..73eb4f5
--- /dev/null
+++ b/web/src/components/ui/sheet.tsx
@@ -0,0 +1,132 @@
+import * as React from 'react';
+import { XIcon } from 'lucide-react';
+import { Dialog as SheetPrimitive } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+function Sheet({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetClose({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetPortal({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function SheetOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SheetContent({
+ className,
+ children,
+ side = 'right',
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ side?: 'top' | 'right' | 'bottom' | 'left';
+ showCloseButton?: boolean;
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ );
+}
+
+function SheetHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SheetFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SheetTitle({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SheetDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+export {
+ Sheet,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription
+};
diff --git a/web/src/components/ui/sidebar.tsx b/web/src/components/ui/sidebar.tsx
new file mode 100644
index 0000000..5faaca1
--- /dev/null
+++ b/web/src/components/ui/sidebar.tsx
@@ -0,0 +1,693 @@
+import * as React from 'react';
+import { cva, type VariantProps } from 'class-variance-authority';
+import { PanelLeftIcon } from 'lucide-react';
+import { Slot } from 'radix-ui';
+
+import { useIsMobile } from '@/hooks/use-mobile';
+import { cn } from '@/lib/utils';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Separator } from '@/components/ui/separator';
+import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetHeader,
+ SheetTitle
+} from '@/components/ui/sheet';
+import { Skeleton } from '@/components/ui/skeleton';
+import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
+
+const SIDEBAR_COOKIE_NAME = 'sidebar_state';
+const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
+const SIDEBAR_WIDTH = '16rem';
+const SIDEBAR_WIDTH_MOBILE = '18rem';
+const SIDEBAR_WIDTH_ICON = '3rem';
+const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
+
+type SidebarContextProps = {
+ state: 'expanded' | 'collapsed';
+ open: boolean;
+ setOpen: (open: boolean) => void;
+ openMobile: boolean;
+ setOpenMobile: (open: boolean) => void;
+ isMobile: boolean;
+ toggleSidebar: () => void;
+};
+
+const SidebarContext = React.createContext(null);
+
+export function useSidebar() {
+ const context = React.useContext(SidebarContext);
+ if (!context) {
+ throw new Error('useSidebar must be used within a SidebarProvider.');
+ }
+
+ return context;
+}
+
+function SidebarProvider({
+ defaultOpen = true,
+ open: openProp,
+ onOpenChange: setOpenProp,
+ className,
+ style,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ defaultOpen?: boolean;
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+}) {
+ const isMobile = useIsMobile();
+ const [openMobile, setOpenMobile] = React.useState(false);
+
+ // This is the internal state of the sidebar.
+ // We use openProp and setOpenProp for control from outside the component.
+ const [_open, _setOpen] = React.useState(defaultOpen);
+ const open = openProp ?? _open;
+ const setOpen = React.useCallback(
+ (value: boolean | ((value: boolean) => boolean)) => {
+ const openState = typeof value === 'function' ? value(open) : value;
+ if (setOpenProp) {
+ setOpenProp(openState);
+ } else {
+ _setOpen(openState);
+ }
+
+ // This sets the cookie to keep the sidebar state.
+ document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
+ },
+ [setOpenProp, open]
+ );
+
+ // Helper to toggle the sidebar.
+ const toggleSidebar = React.useCallback(() => {
+ return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open);
+ }, [isMobile, setOpen, setOpenMobile]);
+
+ // Adds a keyboard shortcut to toggle the sidebar.
+ React.useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
+ event.preventDefault();
+ toggleSidebar();
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [toggleSidebar]);
+
+ // We add a state so that we can do data-state="expanded" or "collapsed".
+ // This makes it easier to style the sidebar with Tailwind classes.
+ const state = open ? 'expanded' : 'collapsed';
+
+ const contextValue = React.useMemo(
+ () => ({
+ state,
+ open,
+ setOpen,
+ isMobile,
+ openMobile,
+ setOpenMobile,
+ toggleSidebar
+ }),
+ [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
+ );
+
+ return (
+
+
+
+ {children}
+
+
+
+ );
+}
+
+function Sidebar({
+ side = 'left',
+ variant = 'sidebar',
+ collapsible = 'offcanvas',
+ className,
+ children,
+ ...props
+}: React.ComponentProps<'div'> & {
+ side?: 'left' | 'right';
+ variant?: 'sidebar' | 'floating' | 'inset';
+ collapsible?: 'offcanvas' | 'icon' | 'none';
+}) {
+ const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
+
+ if (collapsible === 'none') {
+ return (
+
+ {children}
+
+ );
+ }
+
+ if (isMobile) {
+ return (
+
+
+
+ Sidebar
+ Displays the mobile sidebar.
+
+ {children}
+
+
+ );
+ }
+
+ return (
+
+ {/* This is what handles the sidebar gap on desktop */}
+
+
+
+ );
+}
+
+function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps) {
+ const { toggleSidebar, isMobile, open, openMobile } = useSidebar();
+ const label = isMobile ? (openMobile ? '收起导航' : '打开导航') : (open ? '收起导航' : '打开导航');
+
+ return (
+
+ );
+}
+
+function SidebarRail({ className, ...props }: React.ComponentProps<'button'>) {
+ const { toggleSidebar } = useSidebar();
+
+ return (
+
+ );
+}
+
+function SidebarInset({ className, ...props }: React.ComponentProps<'main'>) {
+ return (
+
+ );
+}
+
+function SidebarInput({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarHeader({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarFooter({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarSeparator({ className, ...props }: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function SidebarContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroup({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarGroupLabel({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'div'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : 'div';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ 'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupAction({
+ className,
+ asChild = false,
+ ...props
+}: React.ComponentProps<'button'> & { asChild?: boolean }) {
+ const Comp = asChild ? Slot.Root : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarGroupContent({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenu({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+const sidebarMenuButtonVariants = cva(
+ 'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
+ {
+ variants: {
+ variant: {
+ default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
+ outline:
+ 'bg-background shadow-[0_0_0_1px_var(--sidebar-border)] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_var(--sidebar-accent)]'
+ },
+ size: {
+ default: 'h-8 text-sm',
+ sm: 'h-7 text-xs',
+ lg: 'h-12 text-sm group-data-[collapsible=icon]:p-0!'
+ }
+ },
+ defaultVariants: {
+ variant: 'default',
+ size: 'default'
+ }
+ }
+);
+
+function SidebarMenuButton({
+ asChild = false,
+ isActive = false,
+ variant = 'default',
+ size = 'default',
+ tooltip,
+ className,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ isActive?: boolean;
+ tooltip?: string | React.ComponentProps;
+} & VariantProps) {
+ const Comp = asChild ? Slot.Root : 'button';
+ const { isMobile, state } = useSidebar();
+
+ const button = (
+
+ );
+
+ if (!tooltip) {
+ return button;
+ }
+
+ if (typeof tooltip === 'string') {
+ tooltip = {
+ children: tooltip
+ };
+ }
+
+ return (
+
+ {button}
+
+
+ );
+}
+
+function SidebarMenuAction({
+ className,
+ asChild = false,
+ showOnHover = false,
+ ...props
+}: React.ComponentProps<'button'> & {
+ asChild?: boolean;
+ showOnHover?: boolean;
+}) {
+ const Comp = asChild ? Slot.Root : 'button';
+
+ return (
+ svg]:size-4 [&>svg]:shrink-0',
+ // Increases the hit area of the button on mobile.
+ 'after:absolute after:-inset-2 md:after:hidden',
+ 'peer-data-[size=sm]/menu-button:top-1',
+ 'peer-data-[size=default]/menu-button:top-1.5',
+ 'peer-data-[size=lg]/menu-button:top-2.5',
+ 'group-data-[collapsible=icon]:hidden',
+ showOnHover &&
+ 'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground data-[state=open]:opacity-100 md:opacity-0',
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+function SidebarMenuBadge({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSkeleton({
+ className,
+ showIcon = false,
+ ...props
+}: React.ComponentProps<'div'> & {
+ showIcon?: boolean;
+}) {
+ // Random width between 50 to 90%.
+ const width = React.useMemo(() => {
+ return `${Math.floor(Math.random() * 40) + 50}%`;
+ }, []);
+
+ return (
+
+ {showIcon && }
+
+
+ );
+}
+
+function SidebarMenuSub({ className, ...props }: React.ComponentProps<'ul'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubItem({ className, ...props }: React.ComponentProps<'li'>) {
+ return (
+
+ );
+}
+
+function SidebarMenuSubButton({
+ asChild = false,
+ size = 'md',
+ isActive = false,
+ className,
+ ...props
+}: React.ComponentProps<'a'> & {
+ asChild?: boolean;
+ size?: 'sm' | 'md';
+ isActive?: boolean;
+}) {
+ const Comp = asChild ? Slot.Root : 'a';
+
+ return (
+ span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
+ 'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
+ size === 'sm' && 'text-xs',
+ size === 'md' && 'text-sm',
+ 'group-data-[collapsible=icon]:hidden',
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+export {
+ Sidebar,
+ SidebarContent,
+ SidebarFooter,
+ SidebarGroup,
+ SidebarGroupAction,
+ SidebarGroupContent,
+ SidebarGroupLabel,
+ SidebarHeader,
+ SidebarInput,
+ SidebarInset,
+ SidebarMenu,
+ SidebarMenuAction,
+ SidebarMenuBadge,
+ SidebarMenuButton,
+ SidebarMenuItem,
+ SidebarMenuSkeleton,
+ SidebarMenuSub,
+ SidebarMenuSubButton,
+ SidebarMenuSubItem,
+ SidebarProvider,
+ SidebarRail,
+ SidebarSeparator,
+ SidebarTrigger
+};
diff --git a/web/src/components/ui/skeleton.tsx b/web/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..24d6b2f
--- /dev/null
+++ b/web/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from '@/lib/utils';
+
+function Skeleton({ className, ...props }: React.ComponentProps<'div'>) {
+ return (
+
+ );
+}
+
+export { Skeleton };
diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..f657404
--- /dev/null
+++ b/web/src/components/ui/tooltip.tsx
@@ -0,0 +1,51 @@
+import * as React from 'react';
+import { Tooltip as TooltipPrimitive } from 'radix-ui';
+
+import { cn } from '@/lib/utils';
+
+function TooltipProvider({
+ delayDuration = 0,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function Tooltip({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function TooltipTrigger({ ...props }: React.ComponentProps) {
+ return ;
+}
+
+function TooltipContent({
+ className,
+ sideOffset = 0,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ {children}
+
+
+
+ );
+}
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
diff --git a/web/src/features/accounts/AccountEditPage.jsx b/web/src/features/accounts/AccountEditPage.jsx
index 133c418..0010f62 100644
--- a/web/src/features/accounts/AccountEditPage.jsx
+++ b/web/src/features/accounts/AccountEditPage.jsx
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "react";
-import { Link, useParams } from "react-router";
-import { useDataProvider } from "@refinedev/core";
+import { Link, useParams } from "../../shared/router.jsx";
+import { useDataProvider } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
diff --git a/web/src/features/accounts/AccountsPage.jsx b/web/src/features/accounts/AccountsPage.jsx
index cd3d7da..1866192 100644
--- a/web/src/features/accounts/AccountsPage.jsx
+++ b/web/src/features/accounts/AccountsPage.jsx
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
-import { Link, useNavigate, useParams } from "react-router";
-import { useDataProvider, useList, useOne } from "@refinedev/core";
+import { Link, useNavigate, useParams } from "../../shared/router.jsx";
+import { useDataProvider, useList, useOne } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
diff --git a/web/src/features/accounts/AccountsPage.test.jsx b/web/src/features/accounts/AccountsPage.test.jsx
index 707b4b4..db22a7d 100644
--- a/web/src/features/accounts/AccountsPage.test.jsx
+++ b/web/src/features/accounts/AccountsPage.test.jsx
@@ -7,8 +7,8 @@ import {
waitFor,
} from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { Refine } from "@refinedev/core";
-import { MemoryRouter, Route, Routes } from "react-router";
+import { TestDataProvider } from "../../test/providerCompat.jsx";
+import { MemoryRouter, Route, Routes } from "../../test/routerCompat.jsx";
import {
AccountCreatePage,
AccountDetail,
@@ -85,11 +85,11 @@ const queryClient = () =>
function renderList(dataProvider) {
return render(
-
+
-
+
,
);
}
@@ -97,13 +97,13 @@ function renderList(dataProvider) {
function renderDetail(dataProvider) {
return render(
-
+
} />
-
+
,
);
}
@@ -111,13 +111,13 @@ function renderDetail(dataProvider) {
function renderCreate(dataProvider) {
return render(
-
+
} />
-
+
,
);
}
diff --git a/web/src/features/creator/CreatorPages.test.jsx b/web/src/features/creator/CreatorPages.test.jsx
index 3cacc82..c757f0b 100644
--- a/web/src/features/creator/CreatorPages.test.jsx
+++ b/web/src/features/creator/CreatorPages.test.jsx
@@ -7,8 +7,8 @@ import {
waitFor,
} from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { Refine } from "@refinedev/core";
-import { MemoryRouter, Route, Routes } from "react-router";
+import { TestDataProvider } from "../../test/providerCompat.jsx";
+import { MemoryRouter, Route, Routes } from "../../test/routerCompat.jsx";
import { AccountEditPage } from "../accounts/AccountEditPage";
import { AccountList } from "../accounts/AccountsPage";
import { CreatorCompetitorsPage } from "./competitors/CreatorCompetitorsPage";
@@ -90,9 +90,9 @@ function provider(overrides = {}) {
function renderPage(element, dataProvider) {
return render(
-
+
{element}
-
+
,
);
}
@@ -100,13 +100,13 @@ function renderPage(element, dataProvider) {
function renderAccountPage(dataProvider) {
return render(
-
+
} />
-
+
,
);
}
@@ -498,7 +498,6 @@ describe("creator pages", () => {
expect.objectContaining({
resource: "creator-competitor-preview",
variables: {
- account_id: "account-a",
platform: "douyin",
share_url: "https://v.douyin.com/share-a/",
},
@@ -511,7 +510,6 @@ describe("creator pages", () => {
expect.objectContaining({
resource: "creator-competitors",
variables: {
- account_id: "account-a",
platform: "douyin",
share_url: "https://v.douyin.com/share-a/",
tags: ["重点监测"],
diff --git a/web/src/features/creator/competitors/CreatorCompetitorsPage.jsx b/web/src/features/creator/competitors/CreatorCompetitorsPage.jsx
index 4d84512..423af2d 100644
--- a/web/src/features/creator/competitors/CreatorCompetitorsPage.jsx
+++ b/web/src/features/creator/competitors/CreatorCompetitorsPage.jsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { useDataProvider } from "@refinedev/core";
+import { useDataProvider } from "../../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
@@ -64,7 +64,6 @@ export function CreatorCompetitorsPage() {
total: 0,
hasNext: false,
});
- const [accounts, setAccounts] = useState([]);
const [platform, setPlatform] = useState("");
const [createOpen, setCreateOpen] = useState(false);
const [shareText, setShareText] = useState("");
@@ -81,7 +80,6 @@ export function CreatorCompetitorsPage() {
const [previewConfirmed, setPreviewConfirmed] = useState(false);
const [error, setError] = useState(null);
const [notice, setNotice] = useState(null);
- const [accountError, setAccountError] = useState(null);
const [material, setMaterial] = useState(null);
const [materialPending, setMaterialPending] = useState(false);
const [rewriteRequirement, setRewriteRequirement] = useState("");
@@ -154,15 +152,6 @@ export function CreatorCompetitorsPage() {
publishedAfter,
publishedBefore,
]);
- useEffect(() => {
- dataProvider
- .getList({ resource: "creator-accounts" })
- .then((result) => {
- setAccountError(null);
- setAccounts(result.data);
- })
- .catch((loadError) => setAccountError(loadError));
- }, [dataProvider]);
const extractedShareURL = extractShareURL(shareText);
const sharePlatform = platformForShareURL(extractedShareURL);
const closeCreate = () => {
@@ -183,33 +172,18 @@ export function CreatorCompetitorsPage() {
setNotice({ variant: "warning", text: "链接不是支持的平台分享链接。" });
return;
}
- const account = accounts.find(
- (value) =>
- value.platform === sharePlatform &&
- value.authorization_status === "authorized" &&
- (value.business_status === "normal" || value.business_status === "muted") &&
- value.login_status === "logged_in",
- );
- if (!account) {
- setNotice({
- variant: "warning",
- text: `没有可用于解析${sharePlatform === "douyin" ? "抖音" : "小红书"}分享内容的已登录账号。`,
- });
- return;
- }
setBusy(true);
setNotice(null);
try {
const result = await dataProvider.create({
resource: "creator-competitor-preview",
variables: {
- account_id: account.id,
platform: sharePlatform,
share_url: extractedShareURL,
},
});
const verified = result.data;
- setPreview({ ...verified, account_id: account.id, share_url: extractedShareURL });
+ setPreview({ ...verified, share_url: extractedShareURL });
setPreviewConfirmed(false);
setNotice({
variant: "info",
@@ -233,7 +207,6 @@ export function CreatorCompetitorsPage() {
setNotice(null);
try {
const variables = {
- account_id: preview.account_id,
platform: preview.platform,
share_url: preview.share_url || extractedShareURL,
};
@@ -610,12 +583,6 @@ export function CreatorCompetitorsPage() {
/>
- {accountError ? (
-
- 同平台账号读取失败:
- {conflictMessage(accountError, "请重试账号列表")}
-
- ) : null}
{
@@ -59,11 +59,11 @@ const renderVersions = (dataProvider) =>
new QueryClient({ defaultOptions: { queries: { retry: false } } })
}
>
-
+
-
+
,
);
diff --git a/web/src/features/environments/BrowsersPage.jsx b/web/src/features/environments/BrowsersPage.jsx
index fbc31c5..e73c1e8 100644
--- a/web/src/features/environments/BrowsersPage.jsx
+++ b/web/src/features/environments/BrowsersPage.jsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";
-import { Link, useNavigate, useParams } from "react-router";
-import { useDataProvider, useList, useOne } from "@refinedev/core";
+import { Link, useNavigate, useParams } from "../../shared/router.jsx";
+import { useDataProvider, useList, useOne } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
diff --git a/web/src/features/environments/BrowsersPage.test.jsx b/web/src/features/environments/BrowsersPage.test.jsx
index 3806f68..1515fc8 100644
--- a/web/src/features/environments/BrowsersPage.test.jsx
+++ b/web/src/features/environments/BrowsersPage.test.jsx
@@ -8,8 +8,8 @@ import {
within,
} from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { Refine } from "@refinedev/core";
-import { MemoryRouter } from "react-router";
+import { TestDataProvider } from "../../test/providerCompat.jsx";
+import { MemoryRouter } from "../../test/routerCompat.jsx";
import { BrowserCreatePage, BrowserList } from "./BrowsersPage";
afterEach(() => {
@@ -126,22 +126,22 @@ const queryClient = () =>
const renderList = (dataProvider) =>
render(
-
+
-
+
,
);
const renderCreate = (dataProvider) =>
render(
-
+
-
+
,
);
diff --git a/web/src/features/environments/GatewaysPage.jsx b/web/src/features/environments/GatewaysPage.jsx
index 385732d..62b93d0 100644
--- a/web/src/features/environments/GatewaysPage.jsx
+++ b/web/src/features/environments/GatewaysPage.jsx
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react";
-import { useDataProvider, useList } from "@refinedev/core";
+import { useDataProvider, useList } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
diff --git a/web/src/features/environments/GatewaysPage.test.jsx b/web/src/features/environments/GatewaysPage.test.jsx
index f9d6f5e..5ca95a4 100644
--- a/web/src/features/environments/GatewaysPage.test.jsx
+++ b/web/src/features/environments/GatewaysPage.test.jsx
@@ -8,8 +8,8 @@ import {
within,
} from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { Refine } from "@refinedev/core";
-import { MemoryRouter } from "react-router";
+import { TestDataProvider } from "../../test/providerCompat.jsx";
+import { MemoryRouter } from "../../test/routerCompat.jsx";
import { GatewayList } from "./GatewaysPage";
afterEach(() => {
@@ -63,11 +63,11 @@ const renderGateways = (dataProvider) =>
new QueryClient({ defaultOptions: { queries: { retry: false } } })
}
>
-
+
-
+
,
);
diff --git a/web/src/features/environments/NetworkExitsPage.jsx b/web/src/features/environments/NetworkExitsPage.jsx
index 2761cd8..4cc4559 100644
--- a/web/src/features/environments/NetworkExitsPage.jsx
+++ b/web/src/features/environments/NetworkExitsPage.jsx
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from "react";
-import { Link, useParams } from "react-router";
-import { useDataProvider, useList, useOne } from "@refinedev/core";
+import { Link, useParams } from "../../shared/router.jsx";
+import { useDataProvider, useList, useOne } from "../../shared/hooks/dataHooks.js";
import {
Alert,
Button,
diff --git a/web/src/features/environments/NetworkExitsPage.test.jsx b/web/src/features/environments/NetworkExitsPage.test.jsx
index 102892c..572d4a5 100644
--- a/web/src/features/environments/NetworkExitsPage.test.jsx
+++ b/web/src/features/environments/NetworkExitsPage.test.jsx
@@ -8,8 +8,8 @@ import {
within,
} from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { Refine } from "@refinedev/core";
-import { MemoryRouter } from "react-router";
+import { TestDataProvider } from "../../test/providerCompat.jsx";
+import { MemoryRouter } from "../../test/routerCompat.jsx";
import { NetworkExitList } from "./NetworkExitsPage";
afterEach(() => {
@@ -60,11 +60,11 @@ const renderExits = (dataProvider) =>
new QueryClient({ defaultOptions: { queries: { retry: false } } })
}
>
-
+
-
+
,
);
diff --git a/web/src/features/operations/AuditPage.jsx b/web/src/features/operations/AuditPage.jsx
index c0ec303..0e413bc 100644
--- a/web/src/features/operations/AuditPage.jsx
+++ b/web/src/features/operations/AuditPage.jsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
-import { Link, useSearchParams } from 'react-router'
-import { useList } from '@refinedev/core'
+import { Link, useSearchParams } from '../../shared/router.jsx'
+import { useList } from '../../shared/hooks/dataHooks.js'
import { Alert, Button, PageHeader, PageState, Table } from '../../shared/ui/ui.jsx'
import { useTitle } from '../../shared/hooks/hooks.js'
diff --git a/web/src/features/operations/AuditPage.test.jsx b/web/src/features/operations/AuditPage.test.jsx
index bd8694b..3c94a6e 100644
--- a/web/src/features/operations/AuditPage.test.jsx
+++ b/web/src/features/operations/AuditPage.test.jsx
@@ -1,8 +1,8 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { Refine } from '@refinedev/core'
-import { MemoryRouter } from 'react-router'
+import { TestDataProvider } from '../../test/providerCompat.jsx'
+import { MemoryRouter } from '../../test/routerCompat.jsx'
import { AuditPage, auditCSV } from './AuditPage'
afterEach(() => { cleanup(); vi.restoreAllMocks() })
@@ -12,9 +12,9 @@ const httpQuery = () => new QueryClient({ defaultOptions: { queries: { retry: fa
function renderAudit(dataProvider, initialEntry = '/audit?task_id=task-a') {
return render(
-
+
-
+
,
)
}
diff --git a/web/src/features/operations/DraftPage.jsx b/web/src/features/operations/DraftPage.jsx
index d24501d..1651e51 100644
--- a/web/src/features/operations/DraftPage.jsx
+++ b/web/src/features/operations/DraftPage.jsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
-import { useNavigate, useParams } from 'react-router'
-import { useDataProvider, useList, useOne } from '@refinedev/core'
+import { useNavigate, useParams } from '../../shared/router.jsx'
+import { useDataProvider, useList, useOne } from '../../shared/hooks/dataHooks.js'
import {
Alert, Button, Card, CardContent, Checkbox, ConfirmDialog, DetailList, Field, Input, Modal,
PageHeader, PageState, StatusPill, conflictMessage,
diff --git a/web/src/features/operations/DraftPage.test.jsx b/web/src/features/operations/DraftPage.test.jsx
index 59f0f58..b3d5dc4 100644
--- a/web/src/features/operations/DraftPage.test.jsx
+++ b/web/src/features/operations/DraftPage.test.jsx
@@ -1,8 +1,8 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { Refine } from '@refinedev/core'
-import { MemoryRouter, Route, Routes } from 'react-router'
+import { TestDataProvider } from '../../test/providerCompat.jsx'
+import { MemoryRouter, Route, Routes } from '../../test/routerCompat.jsx'
import { DraftPage } from './DraftPage'
afterEach(() => { cleanup(); vi.restoreAllMocks() })
@@ -30,9 +30,9 @@ function provider(record = draft, overrides = {}) {
const renderDraft = (dataProvider, id = 'draft-a') => render(
-
+
} />
-
+
,
)
diff --git a/web/src/features/operations/TasksPage.jsx b/web/src/features/operations/TasksPage.jsx
index f6b91f2..9833542 100644
--- a/web/src/features/operations/TasksPage.jsx
+++ b/web/src/features/operations/TasksPage.jsx
@@ -1,6 +1,6 @@
import { useState } from 'react'
-import { Link, useNavigate, useParams } from 'react-router'
-import { useDataProvider, useList, useOne } from '@refinedev/core'
+import { Link, useNavigate, useParams } from '../../shared/router.jsx'
+import { useDataProvider, useList, useOne } from '../../shared/hooks/dataHooks.js'
import {
Alert, Button, Card, CardContent, DetailList, Field, PageHeader, PageState, Select, StatusPill,
} from '../../shared/ui/ui.jsx'
diff --git a/web/src/features/operations/TasksPage.test.jsx b/web/src/features/operations/TasksPage.test.jsx
index 45e0a2a..ba7be25 100644
--- a/web/src/features/operations/TasksPage.test.jsx
+++ b/web/src/features/operations/TasksPage.test.jsx
@@ -1,8 +1,8 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { Refine } from '@refinedev/core'
-import { MemoryRouter, Route, Routes } from 'react-router'
+import { TestDataProvider } from '../../test/providerCompat.jsx'
+import { MemoryRouter, Route, Routes } from '../../test/routerCompat.jsx'
import { TaskDetail, TaskList } from './TasksPage'
afterEach(() => { cleanup(); vi.restoreAllMocks() })
@@ -27,19 +27,19 @@ const queryClient = () => new QueryClient({ defaultOptions: { queries: { retry:
const renderTask = dataProvider => render(
-
+
} />
-
+
,
)
const renderTasks = dataProvider => render(
-
+
-
+
,
)
diff --git a/web/src/features/operations/TracePages.jsx b/web/src/features/operations/TracePages.jsx
index 9df3abe..8dd0369 100644
--- a/web/src/features/operations/TracePages.jsx
+++ b/web/src/features/operations/TracePages.jsx
@@ -1,5 +1,5 @@
-import { Link, useParams } from 'react-router'
-import { useOne } from '@refinedev/core'
+import { Link, useParams } from '../../shared/router.jsx'
+import { useOne } from '../../shared/hooks/dataHooks.js'
import { Alert, Button, Card, CardContent, DetailList, PageHeader, PageState } from '../../shared/ui/ui.jsx'
import { useTitle } from '../../shared/hooks/hooks.js'
diff --git a/web/src/hooks/use-media-query.ts b/web/src/hooks/use-media-query.ts
new file mode 100644
index 0000000..50182c3
--- /dev/null
+++ b/web/src/hooks/use-media-query.ts
@@ -0,0 +1,19 @@
+import { useEffect, useState } from 'react';
+
+export function useMediaQuery() {
+ const [isOpen, setIsOpen] = useState(false);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(max-width: 768px)');
+ setIsOpen(mediaQuery.matches);
+
+ const handler = (e: MediaQueryListEvent) => {
+ setIsOpen(e.matches);
+ };
+
+ mediaQuery.addEventListener('change', handler);
+ return () => mediaQuery.removeEventListener('change', handler);
+ }, []);
+
+ return { isOpen };
+}
diff --git a/web/src/hooks/use-mobile.ts b/web/src/hooks/use-mobile.ts
new file mode 100644
index 0000000..d5623c4
--- /dev/null
+++ b/web/src/hooks/use-mobile.ts
@@ -0,0 +1,23 @@
+import * as React from 'react';
+
+const MOBILE_BREAKPOINT = 768;
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined);
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ };
+ if (mql.addEventListener) mql.addEventListener('change', onChange);
+ else mql.addListener?.(onChange);
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
+ return () => {
+ if (mql.removeEventListener) mql.removeEventListener('change', onChange);
+ else mql.removeListener?.(onChange);
+ };
+ }, []);
+
+ return !!isMobile;
+}
diff --git a/web/src/lib/query-client.ts b/web/src/lib/query-client.ts
new file mode 100644
index 0000000..5ad8f7d
--- /dev/null
+++ b/web/src/lib/query-client.ts
@@ -0,0 +1,17 @@
+import { QueryClient } from '@tanstack/react-query'
+
+let queryClient: QueryClient | undefined
+
+export function getQueryClient() {
+ if (!queryClient) {
+ queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ staleTime: 30_000,
+ refetchOnWindowFocus: false,
+ },
+ },
+ })
+ }
+ return queryClient
+}
diff --git a/web/src/lib/utils.ts b/web/src/lib/utils.ts
new file mode 100644
index 0000000..9f62c11
--- /dev/null
+++ b/web/src/lib/utils.ts
@@ -0,0 +1,24 @@
+import { type ClassValue, clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+
+export function formatBytes(
+ bytes: number,
+ opts: {
+ decimals?: number;
+ sizeType?: 'accurate' | 'normal';
+ } = {}
+) {
+ const { decimals = 0, sizeType = 'normal' } = opts;
+
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+ const accurateSizes = ['Bytes', 'KiB', 'MiB', 'GiB', 'TiB'];
+ if (bytes === 0) return '0 Byte';
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
+ return `${(bytes / Math.pow(1024, i)).toFixed(decimals)} ${
+ sizeType === 'accurate' ? (accurateSizes[i] ?? 'Bytest') : (sizes[i] ?? 'Bytes')
+ }`;
+}
diff --git a/web/src/router.tsx b/web/src/router.tsx
new file mode 100644
index 0000000..220fd28
--- /dev/null
+++ b/web/src/router.tsx
@@ -0,0 +1,37 @@
+import { createRouter as createTanStackRouter } from '@tanstack/react-router'
+import { routerWithQueryClient } from '@tanstack/react-router-with-query'
+import { getQueryClient } from './lib/query-client'
+import { routeTree } from './routeTree.gen'
+
+export const getRouter = createRouter
+
+export function createRouter() {
+ const queryClient = getQueryClient()
+ const router = createTanStackRouter({
+ routeTree,
+ context: { queryClient },
+ scrollRestoration: true,
+ defaultPreloadStaleTime: 0,
+ defaultPendingComponent: () => (
+
+
+
+ ),
+ defaultNotFoundComponent: () => (
+
+
+
404
+
页面不存在
+
请从左侧导航进入已配置的功能页面。
+
+
+ ),
+ })
+ return routerWithQueryClient(router, queryClient)
+}
+
+declare module '@tanstack/react-router' {
+ interface Register {
+ router: ReturnType
+ }
+}
diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx
new file mode 100644
index 0000000..fb53cee
--- /dev/null
+++ b/web/src/routes/__root.tsx
@@ -0,0 +1,29 @@
+import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/react-router'
+import type { QueryClient } from '@tanstack/react-query'
+import appCss from '@/styles/globals.css?url'
+
+export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
+ head: () => ({
+ meta: [
+ { charSet: 'utf-8' },
+ { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+ { name: 'theme-color', content: '#061a38' },
+ { title: 'CreatorHub' },
+ { name: 'description', content: 'CreatorHub 账号与运营控制台' },
+ ],
+ links: [{ rel: 'stylesheet', href: appCss }],
+ }),
+ component: RootDocument,
+})
+
+function RootDocument() {
+ return (
+
+
+
+
+
+
+
+ )
+}
diff --git a/web/src/routes/_app.tsx b/web/src/routes/_app.tsx
new file mode 100644
index 0000000..fb69eb0
--- /dev/null
+++ b/web/src/routes/_app.tsx
@@ -0,0 +1,31 @@
+import { useEffect, useState, type ReactNode } from 'react'
+import { createFileRoute, Outlet, useLocation, useNavigate } from '@tanstack/react-router'
+import { CreatorHubLayout } from '@/app/Layout'
+
+export const Route = createFileRoute('/_app')({ component: AuthenticatedLayout })
+
+function AuthenticatedLayout() {
+ return (
+
+
+
+ )
+}
+
+function AuthGate({ children }: { children: ReactNode }) {
+ const location = useLocation()
+ const navigate = useNavigate()
+ const [authenticated, setAuthenticated] = useState(null)
+
+ useEffect(() => {
+ const loggedIn = window.localStorage.getItem('creatorhub.auth') !== null
+ setAuthenticated(loggedIn)
+ if (!loggedIn) navigate({ to: '/login', replace: true })
+ }, [location.pathname, navigate])
+
+ if (authenticated === null) {
+ return
+ }
+ if (!authenticated) return null
+ return children
+}
diff --git a/web/src/routes/_app/accounts/$id.tsx b/web/src/routes/_app/accounts/$id.tsx
new file mode 100644
index 0000000..a63b0ff
--- /dev/null
+++ b/web/src/routes/_app/accounts/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AccountDetail } from '@/features/accounts/AccountsPage'
+
+export const Route = createFileRoute('/_app/accounts/$id')({ component: AccountDetail })
diff --git a/web/src/routes/_app/accounts/$id/edit.tsx b/web/src/routes/_app/accounts/$id/edit.tsx
new file mode 100644
index 0000000..619698e
--- /dev/null
+++ b/web/src/routes/_app/accounts/$id/edit.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AccountEditPage } from '@/features/accounts/AccountEditPage'
+
+export const Route = createFileRoute('/_app/accounts/$id/edit')({ component: AccountEditPage })
diff --git a/web/src/routes/_app/accounts/index.tsx b/web/src/routes/_app/accounts/index.tsx
new file mode 100644
index 0000000..e74ee84
--- /dev/null
+++ b/web/src/routes/_app/accounts/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AccountList } from '@/features/accounts/AccountsPage'
+
+export const Route = createFileRoute('/_app/accounts/')({ component: AccountList })
diff --git a/web/src/routes/_app/accounts/new.tsx b/web/src/routes/_app/accounts/new.tsx
new file mode 100644
index 0000000..4e1019f
--- /dev/null
+++ b/web/src/routes/_app/accounts/new.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AccountCreatePage } from '@/features/accounts/AccountsPage'
+
+export const Route = createFileRoute('/_app/accounts/new')({ component: AccountCreatePage })
diff --git a/web/src/routes/_app/attempts/$id.tsx b/web/src/routes/_app/attempts/$id.tsx
new file mode 100644
index 0000000..9372ece
--- /dev/null
+++ b/web/src/routes/_app/attempts/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AttemptDetail } from '@/features/operations/TracePages'
+
+export const Route = createFileRoute('/_app/attempts/$id')({ component: AttemptDetail })
diff --git a/web/src/routes/_app/audit.tsx b/web/src/routes/_app/audit.tsx
new file mode 100644
index 0000000..9c1d87d
--- /dev/null
+++ b/web/src/routes/_app/audit.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { AuditPage } from '@/features/operations/AuditPage'
+
+export const Route = createFileRoute('/_app/audit')({ component: AuditPage })
diff --git a/web/src/routes/_app/browser-versions.tsx b/web/src/routes/_app/browser-versions.tsx
new file mode 100644
index 0000000..c01b497
--- /dev/null
+++ b/web/src/routes/_app/browser-versions.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { BrowserVersionList } from '@/features/environments/BrowserVersionsPage'
+
+export const Route = createFileRoute('/_app/browser-versions')({ component: BrowserVersionList })
diff --git a/web/src/routes/_app/browsers/$id.tsx b/web/src/routes/_app/browsers/$id.tsx
new file mode 100644
index 0000000..0ed6e99
--- /dev/null
+++ b/web/src/routes/_app/browsers/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { BrowserDetail } from '@/features/environments/BrowsersPage'
+
+export const Route = createFileRoute('/_app/browsers/$id')({ component: BrowserDetail })
diff --git a/web/src/routes/_app/browsers/index.tsx b/web/src/routes/_app/browsers/index.tsx
new file mode 100644
index 0000000..e0fc51e
--- /dev/null
+++ b/web/src/routes/_app/browsers/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { BrowserList } from '@/features/environments/BrowsersPage'
+
+export const Route = createFileRoute('/_app/browsers/')({ component: BrowserList })
diff --git a/web/src/routes/_app/browsers/new.tsx b/web/src/routes/_app/browsers/new.tsx
new file mode 100644
index 0000000..0ff3a65
--- /dev/null
+++ b/web/src/routes/_app/browsers/new.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { BrowserCreatePage } from '@/features/environments/BrowsersPage'
+
+export const Route = createFileRoute('/_app/browsers/new')({ component: BrowserCreatePage })
diff --git a/web/src/routes/_app/competitors/$id.tsx b/web/src/routes/_app/competitors/$id.tsx
new file mode 100644
index 0000000..a77df30
--- /dev/null
+++ b/web/src/routes/_app/competitors/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorCompetitorsPage } from '@/features/creator/competitors/CreatorCompetitorsPage'
+
+export const Route = createFileRoute('/_app/competitors/$id')({ component: CreatorCompetitorsPage })
diff --git a/web/src/routes/_app/competitors/index.tsx b/web/src/routes/_app/competitors/index.tsx
new file mode 100644
index 0000000..e188631
--- /dev/null
+++ b/web/src/routes/_app/competitors/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorCompetitorsPage } from '@/features/creator/competitors/CreatorCompetitorsPage'
+
+export const Route = createFileRoute('/_app/competitors/')({ component: CreatorCompetitorsPage })
diff --git a/web/src/routes/_app/creator/competitors/$id.tsx b/web/src/routes/_app/creator/competitors/$id.tsx
new file mode 100644
index 0000000..c16340d
--- /dev/null
+++ b/web/src/routes/_app/creator/competitors/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorCompetitorsPage } from '@/features/creator/competitors/CreatorCompetitorsPage'
+
+export const Route = createFileRoute('/_app/creator/competitors/$id')({ component: CreatorCompetitorsPage })
diff --git a/web/src/routes/_app/creator/competitors/index.tsx b/web/src/routes/_app/creator/competitors/index.tsx
new file mode 100644
index 0000000..5e2e506
--- /dev/null
+++ b/web/src/routes/_app/creator/competitors/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorCompetitorsPage } from '@/features/creator/competitors/CreatorCompetitorsPage'
+
+export const Route = createFileRoute('/_app/creator/competitors/')({ component: CreatorCompetitorsPage })
diff --git a/web/src/routes/_app/creator/settings.tsx b/web/src/routes/_app/creator/settings.tsx
new file mode 100644
index 0000000..ed6d105
--- /dev/null
+++ b/web/src/routes/_app/creator/settings.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorSettingsPage } from '@/features/creator/settings/CreatorSettingsPage'
+
+export const Route = createFileRoute('/_app/creator/settings')({ component: CreatorSettingsPage })
diff --git a/web/src/routes/_app/creator/workbench.tsx b/web/src/routes/_app/creator/workbench.tsx
new file mode 100644
index 0000000..838b8de
--- /dev/null
+++ b/web/src/routes/_app/creator/workbench.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { CreatorWorkbenchPage } from '@/features/creator/workbench/CreatorWorkbenchPage'
+
+export const Route = createFileRoute('/_app/creator/workbench')({ component: CreatorWorkbenchPage })
diff --git a/web/src/routes/_app/drafts/$id.tsx b/web/src/routes/_app/drafts/$id.tsx
new file mode 100644
index 0000000..64107e1
--- /dev/null
+++ b/web/src/routes/_app/drafts/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { DraftPage } from '@/features/operations/DraftPage'
+
+export const Route = createFileRoute('/_app/drafts/$id')({ component: DraftPage })
diff --git a/web/src/routes/_app/gateways.tsx b/web/src/routes/_app/gateways.tsx
new file mode 100644
index 0000000..4eb8619
--- /dev/null
+++ b/web/src/routes/_app/gateways.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { GatewayList } from '@/features/environments/GatewaysPage'
+
+export const Route = createFileRoute('/_app/gateways')({ component: GatewayList })
diff --git a/web/src/routes/_app/network-exits/$id.tsx b/web/src/routes/_app/network-exits/$id.tsx
new file mode 100644
index 0000000..480a092
--- /dev/null
+++ b/web/src/routes/_app/network-exits/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { NetworkExitDetail } from '@/features/environments/NetworkExitsPage'
+
+export const Route = createFileRoute('/_app/network-exits/$id')({ component: NetworkExitDetail })
diff --git a/web/src/routes/_app/network-exits/index.tsx b/web/src/routes/_app/network-exits/index.tsx
new file mode 100644
index 0000000..bf3800a
--- /dev/null
+++ b/web/src/routes/_app/network-exits/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { NetworkExitList } from '@/features/environments/NetworkExitsPage'
+
+export const Route = createFileRoute('/_app/network-exits/')({ component: NetworkExitList })
diff --git a/web/src/routes/_app/tasks/$id.tsx b/web/src/routes/_app/tasks/$id.tsx
new file mode 100644
index 0000000..09f9d22
--- /dev/null
+++ b/web/src/routes/_app/tasks/$id.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { TaskDetail } from '@/features/operations/TasksPage'
+
+export const Route = createFileRoute('/_app/tasks/$id')({ component: TaskDetail })
diff --git a/web/src/routes/_app/tasks/index.tsx b/web/src/routes/_app/tasks/index.tsx
new file mode 100644
index 0000000..0c4d6cf
--- /dev/null
+++ b/web/src/routes/_app/tasks/index.tsx
@@ -0,0 +1,4 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { TaskList } from '@/features/operations/TasksPage'
+
+export const Route = createFileRoute('/_app/tasks/')({ component: TaskList })
diff --git a/web/src/routes/index.tsx b/web/src/routes/index.tsx
new file mode 100644
index 0000000..41a2c75
--- /dev/null
+++ b/web/src/routes/index.tsx
@@ -0,0 +1,5 @@
+import { createFileRoute, redirect } from '@tanstack/react-router'
+
+export const Route = createFileRoute('/')({
+ beforeLoad: () => { throw redirect({ to: '/accounts' }) },
+})
diff --git a/web/src/routes/login.tsx b/web/src/routes/login.tsx
new file mode 100644
index 0000000..8116cbf
--- /dev/null
+++ b/web/src/routes/login.tsx
@@ -0,0 +1,7 @@
+import { createFileRoute } from '@tanstack/react-router'
+import { LoginPage } from '@/app/LoginPage'
+
+export const Route = createFileRoute('/login')({
+ head: () => ({ meta: [{ title: 'CreatorHub · 登录' }] }),
+ component: LoginPage,
+})
diff --git a/web/src/shared/api/dataProvider.js b/web/src/shared/api/dataProvider.js
index 3ffcf9d..89f45e8 100644
--- a/web/src/shared/api/dataProvider.js
+++ b/web/src/shared/api/dataProvider.js
@@ -1,7 +1,10 @@
-// 401 = 未认证:抹去凭证并引导到登录页。开发代理下 401 由后端直接返回。
+// 401 = 未认证:抹去凭证并通过 TanStack Router 监听的 history 事件回到登录页。
const unauthorized = () => {
localStorage.removeItem("creatorhub.auth");
- if (!location.hash.startsWith("#/login")) location.hash = "#/login";
+ if (typeof window !== "undefined" && window.location.pathname !== "/login") {
+ window.history.replaceState({}, "", "/login");
+ window.dispatchEvent(new PopStateEvent("popstate"));
+ }
return Object.assign(new Error("认证已过期,请重新登录"), { status: 401 });
};
@@ -74,11 +77,11 @@ const withID = (record, fallback) => ({
});
export const dataProvider = {
- // Refine v5 契约:所有方法收单个参数对象(getList 收 {resource, pagination, filters, meta})。
+ // 页面数据层契约:所有方法收单个参数对象(getList 收 {resource, pagination, filters, meta})。
async getList({ resource, pagination = {}, filters = [], meta = {} }) {
const path = resourcePaths[resource];
if (!path) return unsupported(resource, "getList");
- // filters 为 [{field, value, operator}];meta 是 Refine 透传的查询上下文。
+ // filters 为 [{field, value, operator}];meta 是页面层透传的查询上下文。
const valueOf = (field) => {
const direct = meta?.[field];
if (direct !== undefined) return direct;
diff --git a/web/src/shared/api/dataProvider.test.js b/web/src/shared/api/dataProvider.test.js
index 4ad4296..654a489 100644
--- a/web/src/shared/api/dataProvider.test.js
+++ b/web/src/shared/api/dataProvider.test.js
@@ -47,7 +47,7 @@ describe("dataProvider", () => {
dataProvider.getList({ resource: "browsers" }),
).rejects.toMatchObject({ status: 401 });
expect(localStorage.getItem("creatorhub.auth")).toBeNull();
- expect(location.hash).toBe("#/login");
+ expect(location.pathname).toBe("/login");
});
it.each([
diff --git a/web/src/shared/hooks/dataHooks.js b/web/src/shared/hooks/dataHooks.js
new file mode 100644
index 0000000..49e6199
--- /dev/null
+++ b/web/src/shared/hooks/dataHooks.js
@@ -0,0 +1,51 @@
+import { createContext, createElement, useContext } from 'react'
+import { useQuery } from '@tanstack/react-query'
+import { dataProvider as defaultDataProvider } from '../api/dataProvider'
+
+export const DataProviderContext = createContext(defaultDataProvider)
+
+export function DataProviderProvider({ dataProvider, children }) {
+ return createElement(DataProviderContext.Provider, { value: dataProvider }, children)
+}
+
+const emptyList = { data: [], total: 0 }
+
+function listKey({ resource, pagination, filters, meta }) {
+ return ['creatorhub', 'list', resource, { pagination, filters, meta }]
+}
+
+export function useList({ resource, pagination, filters, meta, queryOptions = {} }) {
+ const provider = useContext(DataProviderContext)
+ const query = useQuery({
+ queryKey: listKey({ resource, pagination, filters, meta }),
+ queryFn: () => provider.getList({ resource, pagination, filters, meta }),
+ ...queryOptions,
+ })
+ return {
+ result: query.data ?? emptyList,
+ query,
+ error: query.error,
+ isPending: query.isPending,
+ }
+}
+
+export function useOne({ resource, id, queryOptions = {} }) {
+ const provider = useContext(DataProviderContext)
+ const query = useQuery({
+ queryKey: ['creatorhub', 'one', resource, id],
+ queryFn: () => provider.getOne({ resource, id }),
+ enabled: Boolean(id) && queryOptions.enabled !== false,
+ ...queryOptions,
+ })
+ return {
+ result: query.data?.data,
+ query,
+ error: query.error,
+ isPending: query.isPending,
+ }
+}
+
+export function useDataProvider() {
+ const provider = useContext(DataProviderContext)
+ return () => provider
+}
diff --git a/web/src/shared/router.jsx b/web/src/shared/router.jsx
new file mode 100644
index 0000000..db28b2f
--- /dev/null
+++ b/web/src/shared/router.jsx
@@ -0,0 +1,69 @@
+import { createContext, useContext } from 'react'
+import {
+ Link as TanStackLink,
+ Outlet as TanStackOutlet,
+ useLocation as useTanStackLocation,
+ useNavigate as useTanStackNavigate,
+ useParams as useTanStackParams,
+} from '@tanstack/react-router'
+
+export const TestRouterContext = createContext(null)
+
+export function Link({ to, ...props }) {
+ const testRouter = useContext(TestRouterContext)
+ if (testRouter) {
+ return {
+ props.onClick?.(event)
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return
+ event.preventDefault()
+ testRouter.navigate({ to })
+ }} />
+ }
+ return
+}
+
+export function Outlet() {
+ return
+}
+
+export function useNavigate() {
+ const testRouter = useContext(TestRouterContext)
+ if (testRouter) return (to, options = {}) => testRouter.navigate(typeof to === 'string' ? { to, ...options } : to)
+ const navigate = useTanStackNavigate()
+ return (to, options = {}) => {
+ if (typeof to === 'string') return navigate({ to, ...options })
+ return navigate(to)
+ }
+}
+
+export function useLocation() {
+ const testRouter = useContext(TestRouterContext)
+ if (testRouter) return testRouter.location
+ const location = useTanStackLocation()
+ return {
+ ...location,
+ pathname: location.pathname,
+ search: location.searchStr,
+ }
+}
+
+export const RouterParamsContext = createContext(null)
+
+export function useParams() {
+ return useContext(RouterParamsContext) ?? useTanStackParams({ strict: false })
+}
+
+export function useSearchParams() {
+ const location = useLocation()
+ const navigate = useNavigate()
+ const searchParams = new URLSearchParams(location.searchStr || '')
+ const setSearchParams = (next, options = {}) => {
+ const values = next instanceof URLSearchParams ? Object.fromEntries(next) : next
+ return navigate({
+ to: location.pathname,
+ search: values,
+ ...options,
+ })
+ }
+ return [searchParams, setSearchParams]
+}
diff --git a/web/src/start.ts b/web/src/start.ts
new file mode 100644
index 0000000..59d02c7
--- /dev/null
+++ b/web/src/start.ts
@@ -0,0 +1,5 @@
+import { createStart } from '@tanstack/react-start'
+
+const startConfig = () => ({})
+
+export const startInstance = createStart(startConfig)
diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css
new file mode 100644
index 0000000..9cd6ee0
--- /dev/null
+++ b/web/src/styles/globals.css
@@ -0,0 +1,76 @@
+@import 'tailwindcss';
+@import 'tw-animate-css';
+
+@custom-variant dark (&:is(.dark *));
+
+@theme {
+ --font-sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --color-background: #f7f8fa;
+ --color-foreground: #111827;
+ --color-card: #ffffff;
+ --color-card-foreground: #111827;
+ --color-popover: #ffffff;
+ --color-popover-foreground: #111827;
+ --color-primary: #0866ef;
+ --color-primary-foreground: #ffffff;
+ --color-secondary: #eef5ff;
+ --color-secondary-foreground: #0759d4;
+ --color-muted: #f5f7fa;
+ --color-muted-foreground: #475467;
+ --color-accent: #eef5ff;
+ --color-accent-foreground: #0759d4;
+ --color-destructive: #b42318;
+ --color-border: #d7dde7;
+ --color-input: #c4ccd9;
+ --color-ring: #0866ef;
+ --color-sidebar: #061a38;
+ --color-sidebar-foreground: #f8fafc;
+ --color-sidebar-primary: #0866ef;
+ --color-sidebar-primary-foreground: #ffffff;
+ --color-sidebar-accent: #0b2a54;
+ --color-sidebar-accent-foreground: #ffffff;
+ --color-sidebar-border: #17365e;
+ --color-sidebar-ring: #4a90ff;
+ --color-success: #067647;
+ --color-warning: #b54708;
+ --color-danger: #b42318;
+ --color-ink: #111827;
+ --color-hairline: #d7dde7;
+}
+
+:root {
+ --radius: 0.5rem;
+ --background: #f7f8fa;
+ --foreground: #111827;
+ --card: #ffffff;
+ --card-foreground: #111827;
+ --popover: #ffffff;
+ --popover-foreground: #111827;
+ --primary: #0866ef;
+ --primary-foreground: #ffffff;
+ --secondary: #eef5ff;
+ --secondary-foreground: #0759d4;
+ --muted: #f5f7fa;
+ --muted-foreground: #475467;
+ --accent: #eef5ff;
+ --accent-foreground: #0759d4;
+ --destructive: #b42318;
+ --border: #d7dde7;
+ --input: #c4ccd9;
+ --ring: #0866ef;
+ --sidebar: #061a38;
+ --sidebar-foreground: #f8fafc;
+ --sidebar-primary: #0866ef;
+ --sidebar-primary-foreground: #ffffff;
+ --sidebar-accent: #0b2a54;
+ --sidebar-accent-foreground: #ffffff;
+ --sidebar-border: #17365e;
+ --sidebar-ring: #4a90ff;
+}
+
+* { border-color: var(--border); }
+html { font-family: var(--font-sans); color: var(--foreground); background: var(--background); }
+body { min-width: 320px; }
+button, a, input, select, textarea { -webkit-tap-highlight-color: transparent; }
+button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
+.anywhere { overflow-wrap: anywhere; word-break: break-word; }
diff --git a/web/src/test/providerCompat.jsx b/web/src/test/providerCompat.jsx
new file mode 100644
index 0000000..2eb39a0
--- /dev/null
+++ b/web/src/test/providerCompat.jsx
@@ -0,0 +1,5 @@
+import { DataProviderProvider } from '../shared/hooks/dataHooks.js'
+
+export function TestDataProvider({ dataProvider, children }) {
+ return {children}
+}
diff --git a/web/src/test/routerCompat.jsx b/web/src/test/routerCompat.jsx
new file mode 100644
index 0000000..c4f6ca7
--- /dev/null
+++ b/web/src/test/routerCompat.jsx
@@ -0,0 +1,91 @@
+import { Children, cloneElement, isValidElement, useMemo, useState } from 'react'
+import { RouterParamsContext, TestRouterContext, useLocation } from '../shared/router.jsx'
+
+export function Route() {
+ return null
+}
+
+export function Routes() {
+ return null
+}
+
+function routePath(path, parent = '') {
+ const joined = path.startsWith('/') ? path : `${parent}/${path}`
+ return joined.replace(/\/+/g, '/') || '/'
+}
+
+function childRoutes(node, parent = '') {
+ if (!isValidElement(node) || node.type !== Route) return []
+ const { path, element, children } = node.props
+ const fullPath = path ? routePath(path, parent) : parent
+ const nested = Children.toArray(children)
+ if (nested.length === 0) return [{ path: fullPath || '*', element }]
+ return nested.flatMap((child) => childRoutes(child, fullPath).map((route) => ({
+ ...route,
+ element: element ? cloneElement(element, { children: route.element }) : route.element,
+ })))
+}
+
+function entriesFromChildren(children) {
+ const nodes = Children.toArray(children)
+ const routeContainer = nodes.find((node) => isValidElement(node) && node.type === Routes)
+ if (!routeContainer) return [{ path: '*', element: children }]
+ return Children.toArray(routeContainer.props.children).flatMap((node) => childRoutes(node))
+}
+
+function matchRoute(pattern, pathname) {
+ if (pattern === '*' || pattern === '/*') return {}
+ const patternParts = pattern.split('/').filter(Boolean)
+ const pathParts = pathname.split('/').filter(Boolean)
+ if (patternParts.some((part) => part === '*')) return {}
+ if (patternParts.length !== pathParts.length) return null
+ const params = {}
+ for (let index = 0; index < patternParts.length; index += 1) {
+ const expected = patternParts[index]
+ const actual = pathParts[index]
+ if (expected.startsWith(':') || expected.startsWith('$')) params[expected.slice(1)] = decodeURIComponent(actual)
+ else if (expected !== actual) return null
+ }
+ return params
+}
+
+function TestRouteRenderer({ entries }) {
+ const { pathname } = useLocation()
+ const entry = useMemo(() => [...entries].sort((a, b) => {
+ const aWild = a.path.includes('*') ? 1 : 0
+ const bWild = b.path.includes('*') ? 1 : 0
+ return aWild - bWild
+ }).find((candidate) => matchRoute(candidate.path, pathname) !== null), [entries, pathname])
+ const params = entry ? matchRoute(entry.path, pathname) : {}
+ return entry ? {entry.element} : null
+}
+
+function parseLocation(entry) {
+ const url = new URL(entry, 'http://creatorhub.test')
+ return { pathname: url.pathname, search: url.search, searchStr: url.search }
+}
+
+function createTestRouter(initialEntries) {
+ const [initial] = initialEntries
+ return {
+ initialLocation: parseLocation(initial),
+ }
+}
+
+export function MemoryRouter({ initialEntries = ['/'], children }) {
+ const [location, setLocation] = useState(() => createTestRouter(initialEntries).initialLocation)
+ const router = useMemo(() => ({
+ location,
+ navigate: (target) => {
+ const nextPath = typeof target === 'string' ? target : target.to || location.pathname
+ const next = new URL(nextPath, 'http://creatorhub.test')
+ if (typeof target === 'object' && target.search) {
+ const search = target.search instanceof URLSearchParams ? target.search.toString() : new URLSearchParams(target.search).toString()
+ next.search = search ? `?${search}` : ''
+ }
+ setLocation({ pathname: next.pathname, search: next.search, searchStr: next.search })
+ },
+ }), [location])
+ const entries = useMemo(() => entriesFromChildren(children), [children])
+ return
+}
diff --git a/web/src/test/setup.js b/web/src/test/setup.js
new file mode 100644
index 0000000..61f30e4
--- /dev/null
+++ b/web/src/test/setup.js
@@ -0,0 +1,20 @@
+import { cleanup } from '@testing-library/react'
+import { afterEach, vi } from 'vitest'
+
+afterEach(() => cleanup())
+
+window.scrollTo = vi.fn()
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: vi.fn().mockImplementation((query) => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: vi.fn(),
+ removeListener: vi.fn(),
+ addEventListener: vi.fn(),
+ removeEventListener: vi.fn(),
+ dispatchEvent: vi.fn(),
+ })),
+})
diff --git a/web/tests/responsive.e2e.js b/web/tests/responsive.e2e.js
index 7a21d07..4751769 100644
--- a/web/tests/responsive.e2e.js
+++ b/web/tests/responsive.e2e.js
@@ -27,16 +27,16 @@ test('opens social-account and network-exit configuration from the main menu', a
await page.route('**/api/creator/competitors', route => route.fulfill({ json: [] }))
await page.route('**/api/network-exits', route => route.fulfill({ json: [] }))
await page.setViewportSize({ width: 599, height: 800 })
- await page.goto('/#/browsers')
+ await page.goto('/browsers')
await page.getByRole('button', { name: '打开导航' }).click()
await page.getByRole('link', { name: '账号管理' }).click()
- await expect(page).toHaveURL(/#\/accounts$/)
+ await expect(page).toHaveURL(/\/accounts$/)
await expect(page.getByRole('heading', { level: 1, name: '账号管理' })).toBeVisible()
await page.getByRole('button', { name: '打开导航' }).click()
await page.getByRole('link', { name: '网络出口' }).click()
- await expect(page).toHaveURL(/#\/network-exits$/)
+ await expect(page).toHaveURL(/\/network-exits$/)
await expect(page.getByRole('heading', { level: 1, name: '网络出口' })).toBeVisible()
})
@@ -47,7 +47,7 @@ test('keeps the create form inside a 900px viewport', async ({ page }) => {
await page.route('**/api/phase-a/accounts', route => route.fulfill({ json: [] }))
await page.route('**/api/network-exits', route => route.fulfill({ json: [] }))
await page.setViewportSize({ width: 900, height: 800 })
- await page.goto('/#/browsers')
+ await page.goto('/browsers')
await expect(page.getByRole('button', { name: '收起导航' }).first()).toBeVisible()
const createButton = page.getByRole('button', { name: '创建环境' })
@@ -67,7 +67,7 @@ test('shows the CDP endpoint only in the active branch at 900px and 599px', asyn
const endpoint = page.getByText('http://account-a:9222')
await page.setViewportSize({ width: 900, height: 800 })
- await page.goto('/#/browsers')
+ await page.goto('/browsers')
await expect(page.getByRole('cell', { name: 'http://account-a:9222' })).toBeVisible()
await expect(endpoint.filter({ visible: true })).toHaveCount(1)
@@ -90,7 +90,7 @@ test('enforces browser readiness before starting at 900px', async ({ page }) =>
await page.route('**/api/network-exits', route => route.fulfill({ json: [] }))
await page.route('**/api/browsers/*/start', route => route.fulfill({ status: 204 }))
await page.setViewportSize({ width: 900, height: 900 })
- await page.goto('/#/browsers')
+ await page.goto('/browsers')
const ready = page.getByRole('button', { name: '启动 就绪环境' })
const missing = page.getByRole('button', { name: '启动 实例缺失环境' })
@@ -113,7 +113,7 @@ test('submits the minimal social-account form at 900px', async ({ page }) => {
await page.route('**/api/creator/competitors', route => route.fulfill({ json: [] }))
await page.route('**/api/browsers', route => route.fulfill({ json: [] }))
await page.setViewportSize({ width: 900, height: 900 })
- await page.goto('/#/accounts')
+ await page.goto('/accounts')
// 创建账号使用独立页面:打开页面后填写并提交
await page.getByRole('link', { name: '创建自有账号' }).click()
@@ -142,7 +142,7 @@ test('keeps account and network-exit pages inside 599px, 900px and 1280px', asyn
await page.route('**/api/browsers', route => route.fulfill({ json: [binding] }))
await page.route('**/api/network-exits', route => route.fulfill({ json: [networkExit] }))
- for (const path of ['/#/accounts', '/#/accounts/account-a', '/#/network-exits']) {
+ for (const path of ['/accounts', '/accounts/account-a', '/network-exits']) {
for (const width of [599, 900, 1280]) {
await page.setViewportSize({ width, height: 900 })
await page.goto(path)
@@ -160,10 +160,10 @@ test('opens account detail at the phase A route', async ({ page }) => {
await page.route('**/api/browsers', route => route.fulfill({ json: [] }))
await page.route('**/api/phase-a/drafts?account_id=account-a', route => route.fulfill({ json: [] }))
await page.setViewportSize({ width: 599, height: 900 })
- await page.goto('/#/accounts')
+ await page.goto('/accounts')
await page.getByRole('link', { name: '查看账号' }).click()
- await expect(page).toHaveURL(/#\/accounts\/account-a$/)
+ await expect(page).toHaveURL(/\/accounts\/account-a$/)
await expect(page.getByRole('heading', { level: 1, name: '店铺一号' })).toBeVisible()
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(599)
})
@@ -182,7 +182,7 @@ test('keeps draft review responsive and restores focus after dialog close and su
for (const width of [599, 900, 1280]) {
await page.setViewportSize({ width, height: 900 })
- await page.goto('/#/drafts/draft-a')
+ await page.goto('/drafts/draft-a')
await expect(page.getByRole('heading', { level: 1, name: '草稿核对' })).toBeVisible()
expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(width)
}
@@ -239,7 +239,7 @@ test('keeps task recovery and audit traceable without blind retry at 599px, 900p
}], total: 26, page: currentPage, page_size: 25 } })
})
- for (const path of ['/#/tasks', '/#/tasks/task-a', '/#/attempts/attempt-a', '/#/browsers/env-a', '/#/network-exits/exit-a', '/#/audit?task_id=task-a']) {
+ for (const path of ['/tasks', '/tasks/task-a', '/attempts/attempt-a', '/browsers/env-a', '/network-exits/exit-a', '/audit?task_id=task-a']) {
for (const width of [599, 900, 1280]) {
await page.setViewportSize({ width, height: 900 })
await page.goto(path)
@@ -250,7 +250,7 @@ test('keeps task recovery and audit traceable without blind retry at 599px, 900p
verified = false
await page.setViewportSize({ width: 599, height: 900 })
- await page.goto('/#/tasks/task-a')
+ await page.goto('/tasks/task-a')
await expect(page.getByRole('button', { name: /恢复排队/ })).toHaveCount(0)
await page.getByRole('button', { name: '记录人工核验' }).click()
await expect(page.getByRole('button', { name: '按核验结论恢复排队' })).toBeVisible()
@@ -258,11 +258,11 @@ test('keeps task recovery and audit traceable without blind retry at 599px, 900p
await expect.poll(() => resumed).toBe(true)
await expect(page.getByText('已排队')).toBeVisible()
await page.getByRole('link', { name: '查看关联审计' }).click()
- await expect(page).toHaveURL(/#\/audit\?task_id=task-a$/)
+ await expect(page).toHaveURL(/\/audit\?task_id=task-a$/)
await expect(page.getByRole('link', { name: 'task-a' }).first()).toBeVisible()
await page.getByRole('button', { name: '下一页' }).click()
await expect.poll(() => auditPage).toBe(2)
await page.getByRole('link', { name: 'attempt-a' }).first().click()
- await expect(page).toHaveURL(/#\/attempts\/attempt-a$/)
- await expect(page.getByRole('link', { name: '返回任务' })).toHaveAttribute('href', '#/tasks/task-a')
+ await expect(page).toHaveURL(/\/attempts\/attempt-a$/)
+ await expect(page.getByRole('link', { name: '返回任务' })).toHaveAttribute('href', '/tasks/task-a')
})
diff --git a/web/tsconfig.json b/web/tsconfig.json
index 1923496..db4ce98 100644
--- a/web/tsconfig.json
+++ b/web/tsconfig.json
@@ -1,4 +1,23 @@
{
- "extends": "./tsconfig.tsapp.json",
- "include": ["tsconfig.tsapp.json", "types/*.d.ts", "scripts/*"]
+ "compilerOptions": {
+ "target": "ES2022",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ES2022"],
+ "allowJs": true,
+ "checkJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "baseUrl": ".",
+ "paths": { "@/*": ["./src/*"] }
+ },
+ "include": ["src", "vite.config.ts"]
}
diff --git a/web/vite.config.js b/web/vite.config.js
deleted file mode 100644
index f73ac52..0000000
--- a/web/vite.config.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import { defineConfig } from "vite";
-import react from "@vitejs/plugin-react";
-import tailwindcss from "@tailwindcss/vite";
-
-export default defineConfig({
- plugins: [react(), tailwindcss()],
- server: {
- host: true,
- port: 5173,
- strictPort: true,
- proxy: { "/api": "http://127.0.0.1:8082" },
- },
- test: {
- environment: "jsdom",
- pool: "forks",
- maxWorkers: 1,
- isolate: false, // ponytail: one worker avoids 90s startup stalls; restore isolation if tests leak state.
- coverage: {
- provider: "v8",
- reporter: ["text", "json-summary", "json"],
- thresholds: { lines: 65 },
- },
- },
-});
diff --git a/web/vite.config.ts b/web/vite.config.ts
new file mode 100644
index 0000000..0a78053
--- /dev/null
+++ b/web/vite.config.ts
@@ -0,0 +1,91 @@
+import http from 'node:http'
+import https from 'node:https'
+import tailwindcss from '@tailwindcss/vite'
+import { tanstackStart } from '@tanstack/react-start/plugin/vite'
+import viteReact from '@vitejs/plugin-react'
+import { defineConfig, type Plugin } from 'vite'
+import tsconfigPaths from 'vite-tsconfig-paths'
+import { nitro } from 'nitro/vite'
+
+const flattenPlugins = (plugins: Array): Plugin[] =>
+ plugins.flat(Infinity).filter((plugin): plugin is Plugin => Boolean(plugin))
+
+type BuildWarning = { code?: string; id?: string; message: string }
+
+function warnBuild(warning: BuildWarning, warn: (warning: BuildWarning) => void) {
+ const dependency = warning.id?.includes('/node_modules/') ?? false
+ const knownDependencyNoise = (
+ (dependency && warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes('use client'))
+ || (warning.code === 'UNUSED_EXTERNAL_IMPORT' && warning.message.includes('@tanstack/'))
+ )
+ if (!knownDependencyNoise) warn(warning)
+}
+
+const rollupWarningOptions = { onwarn: warnBuild }
+
+function creatorHubBuildWarningPlugin(): Plugin {
+ return {
+ name: 'creatorhub:build-warning-filter',
+ config() {
+ return {
+ environments: {
+ ssr: { build: { rollupOptions: rollupWarningOptions } },
+ },
+ }
+ },
+ }
+}
+
+function creatorHubApiProxy(): Plugin {
+ const target = process.env.CREATORHUB_CONTROL_PLANE_URL ?? 'http://127.0.0.1:8082'
+ return {
+ name: 'creatorhub:control-plane-proxy',
+ configureServer(server) {
+ server.middlewares.use((request, response, next) => {
+ if (!request.url?.startsWith('/api/')) return next()
+ const upstreamURL = new URL(request.url, target)
+ const transport = upstreamURL.protocol === 'https:' ? https : http
+ const headers = { ...request.headers, host: upstreamURL.host }
+ const upstream = transport.request(upstreamURL, { method: request.method, headers }, (upstreamResponse) => {
+ response.statusCode = upstreamResponse.statusCode ?? 502
+ for (const [name, value] of Object.entries(upstreamResponse.headers)) {
+ if (value !== undefined) response.setHeader(name, value)
+ }
+ upstreamResponse.pipe(response)
+ })
+ upstream.on('error', (error) => {
+ server.config.logger.error(`[creatorhub:control-plane-proxy] ${request.method ?? 'GET'} ${request.url}: ${error.message}`)
+ if (!response.headersSent) {
+ response.statusCode = 502
+ response.setHeader('content-type', 'application/json')
+ response.end(JSON.stringify({ error: '控制面代理不可用' }))
+ } else {
+ response.destroy(error)
+ }
+ })
+ request.pipe(upstream)
+ })
+ },
+ }
+}
+
+export default defineConfig({
+ server: {
+ host: true,
+ port: 5173,
+ strictPort: true,
+ proxy: { '/api': 'http://127.0.0.1:8082' }
+ },
+ build: {
+ rollupOptions: rollupWarningOptions
+ },
+ plugins: flattenPlugins([
+ creatorHubApiProxy(),
+ tsconfigPaths(),
+ tailwindcss(),
+ tanstackStart(),
+ creatorHubBuildWarningPlugin(),
+ nitro({ preset: process.env.SERVER_PRESET, rollupConfig: rollupWarningOptions }),
+ viteReact()
+ ])
+})
diff --git a/web/vitest.config.ts b/web/vitest.config.ts
new file mode 100644
index 0000000..894c426
--- /dev/null
+++ b/web/vitest.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from 'vitest/config'
+import react from '@vitejs/plugin-react'
+import tsconfigPaths from 'vite-tsconfig-paths'
+
+export default defineConfig({
+ plugins: [tsconfigPaths(), react()],
+ test: {
+ environment: 'jsdom',
+ pool: 'forks',
+ maxWorkers: 1,
+ isolate: false,
+ setupFiles: ['./src/test/setup.js'],
+ coverage: {
+ provider: 'v8',
+ reporter: ['text', 'json-summary', 'json'],
+ thresholds: { lines: 65 }
+ }
+ }
+})