From 992444c6d03a4f3b1f334369b6f0ed0e210ca55f Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Mon, 29 Jun 2026 16:05:49 +0900 Subject: [PATCH 01/13] =?UTF-8?q?refactor:=20UI=20=EC=A0=95=EC=B1=85?= =?UTF-8?q?=EA=B3=BC=20=EB=9D=BC=EC=9A=B0=ED=8C=85=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/README.md | 118 +++++---------- ui/{sample-app => app}/.gitignore | 0 ui/app/DESIGN_GUIDELINES.md | 5 + ui/{sample-app => app}/Dockerfile | 0 ui/app/README.md | 42 ++++++ ui/{sample-app => app}/index.html | 0 ui/{sample-app => app}/nginx.conf | 0 ui/{sample-app => app}/package-lock.json | 4 +- ui/{sample-app => app}/package.json | 5 +- ui/{sample-app => app}/public/overview.md | 0 .../scripts/check-docs-layout.mjs | 8 +- ui/app/scripts/check-hooks-contract.mjs | 35 +++++ ui/app/scripts/check-policy-docs.mjs | 54 +++++++ .../scripts/check-register-flow.mjs | 6 +- ui/app/scripts/check-routing-contract.mjs | 27 ++++ ui/app/src/App.tsx | 52 +++++++ ui/{sample-app => app}/src/DocsPage.tsx | 12 +- ui/app/src/app/routes.tsx | 58 ++++++++ ui/app/src/components/Navigation.tsx | 41 ++++++ .../src/constants/landingContent.ts | 0 .../src/constants/pagePath.ts | 6 +- .../src/demo/ConsoleDemo.tsx | 2 +- ui/{sample-app => app}/src/demo/types.ts | 0 ui/{sample-app => app}/src/docs/constants.ts | 0 ui/{sample-app => app}/src/docs/hooks.ts | 0 ui/{sample-app => app}/src/docs/types.ts | 0 ui/{sample-app => app}/src/docs/utils.tsx | 0 .../src/features/home/HomePage.tsx | 0 .../src/features/register/RegisterPage.tsx | 12 +- ui/app/src/hooks/useAppNavigation.ts | 38 +++++ ui/app/src/hooks/useClipboard.ts | 50 +++++++ ui/{sample-app => app}/src/main.tsx | 0 ui/{sample-app => app}/src/styles.css | 0 ui/{sample-app => app}/tsconfig.json | 0 ui/{sample-app => app}/vite.config.ts | 0 ui/policies/design-system.md | 82 +++++++++++ ui/policies/engineering.md | 51 +++++++ ui/sample-app/DESIGN_GUIDELINES.md | 135 ----------------- ui/sample-app/README.md | 139 ------------------ ui/sample-app/src/App.tsx | 79 ---------- ui/sample-app/src/components/Navigation.tsx | 55 ------- ui/skills/profanity-ui/SKILL.md | 29 ++++ 42 files changed, 632 insertions(+), 513 deletions(-) rename ui/{sample-app => app}/.gitignore (100%) create mode 100644 ui/app/DESIGN_GUIDELINES.md rename ui/{sample-app => app}/Dockerfile (100%) create mode 100644 ui/app/README.md rename ui/{sample-app => app}/index.html (100%) rename ui/{sample-app => app}/nginx.conf (100%) rename ui/{sample-app => app}/package-lock.json (99%) rename ui/{sample-app => app}/package.json (75%) rename ui/{sample-app => app}/public/overview.md (100%) rename ui/{sample-app => app}/scripts/check-docs-layout.mjs (96%) create mode 100644 ui/app/scripts/check-hooks-contract.mjs create mode 100644 ui/app/scripts/check-policy-docs.mjs rename ui/{sample-app => app}/scripts/check-register-flow.mjs (86%) create mode 100644 ui/app/scripts/check-routing-contract.mjs create mode 100644 ui/app/src/App.tsx rename ui/{sample-app => app}/src/DocsPage.tsx (96%) create mode 100644 ui/app/src/app/routes.tsx create mode 100644 ui/app/src/components/Navigation.tsx rename ui/{sample-app => app}/src/constants/landingContent.ts (100%) rename ui/{sample-app => app}/src/constants/pagePath.ts (60%) rename ui/{sample-app => app}/src/demo/ConsoleDemo.tsx (99%) rename ui/{sample-app => app}/src/demo/types.ts (100%) rename ui/{sample-app => app}/src/docs/constants.ts (100%) rename ui/{sample-app => app}/src/docs/hooks.ts (100%) rename ui/{sample-app => app}/src/docs/types.ts (100%) rename ui/{sample-app => app}/src/docs/utils.tsx (100%) rename ui/{sample-app => app}/src/features/home/HomePage.tsx (100%) rename ui/{sample-app => app}/src/features/register/RegisterPage.tsx (94%) create mode 100644 ui/app/src/hooks/useAppNavigation.ts create mode 100644 ui/app/src/hooks/useClipboard.ts rename ui/{sample-app => app}/src/main.tsx (100%) rename ui/{sample-app => app}/src/styles.css (100%) rename ui/{sample-app => app}/tsconfig.json (100%) rename ui/{sample-app => app}/vite.config.ts (100%) create mode 100644 ui/policies/design-system.md create mode 100644 ui/policies/engineering.md delete mode 100644 ui/sample-app/DESIGN_GUIDELINES.md delete mode 100644 ui/sample-app/README.md delete mode 100644 ui/sample-app/src/App.tsx delete mode 100644 ui/sample-app/src/components/Navigation.tsx create mode 100644 ui/skills/profanity-ui/SKILL.md diff --git a/ui/README.md b/ui/README.md index d7cb634..d37684c 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1,95 +1,57 @@ -# profanity-filter-api frontend +# profanity-filter-api UI -이 디렉터리는 `profanity-filter-api`의 프론트엔드 실험과 공개 문서 UI를 관리합니다. -현재 실제로 빌드하고 확인하는 앱은 `sample-app`입니다. +`ui/`는 `profanity-filter-api`의 프론트엔드 작업 영역이다. 공개 랜딩, API 문서, 키 발급 화면, 이후 추가될 콘솔성 메뉴를 하나의 React 앱에서 점진적으로 확장한다. -## 현재 구조 +## Structure ```text ui/ -├── README.md # FE 영역 전체 안내 -├── openapi.yaml # 코드 기준으로 정리한 수동 OpenAPI 스펙 -└── sample-app/ # Vite + React + TypeScript 샘플 앱 +├── README.md +├── openapi.yaml +├── policies/ +│ ├── engineering.md +│ └── design-system.md +├── skills/ +│ └── profanity-ui/SKILL.md +└── app/ + ├── package.json + ├── src/ + └── scripts/ ``` -`sample-app`은 공개 랜딩 페이지와 `/docs` 문서 화면을 제공합니다. -이전 정적 목업은 제거했고, 앞으로 FE 판단과 수정은 `sample-app`을 기준으로 합니다. +## Source Of Truth -## 현재 앱이 하는 일 +- Engineering policy: `policies/engineering.md` +- Design system policy: `policies/design-system.md` +- Agent workflow: `skills/profanity-ui/SKILL.md` +- Runtime app: `app` +- Navigation registry: `app/src/app/routes.tsx` -- `/`: 서비스 소개용 공개 랜딩 페이지를 렌더링합니다. -- `/docs`: API 서버의 `https://api.kr-filter.com/openapi.json`을 읽어 API 문서를 렌더링합니다. -- `sample-app/public/overview.md`: 문서 첫 화면에서 보여줄 시작 가이드와 오류 모델 설명을 제공합니다. -- `sample-app/src/demo/ConsoleDemo.tsx`: API Key 발급과 필터 테스트 콘솔 후보 화면입니다. 아직 라우트에 연결하지 않았습니다. +## Current Routes -## 실행 +- `/`: 서비스 소개 랜딩 +- `/register`: 키 발급 화면 +- `/docs`: API 문서 -```bash -cd ui/sample-app -npm install -npm run dev -``` - -운영 빌드와 로컬 미리보기: - -```bash -cd ui/sample-app -npm run build -npm run preview -``` - -문서 구조 검사: +## Commands ```bash -cd ui/sample-app +cd ui/app +npm ci +npm run dev +npm run typecheck +npm run test:policy +npm run test:routing +npm run test:hooks +npm run test:register npm run test:docs +npm run build ``` -## 기술 선택 - -현재 샘플 앱은 다음 조합을 사용합니다. - -- Vite: 개발 서버와 정적 빌드 도구 -- React: 랜딩 페이지와 문서 화면 컴포넌트 구성 -- TypeScript: API 문서와 화면 상태 타입 안정성 확보 -- Scalar API Reference: OpenAPI 문서 렌더링 -- Nginx static container: k3s에서 정적 파일 서빙 - -향후 실제 콘솔 기능을 붙일 때 검토할 후보는 다음과 같습니다. - -- TanStack Query: API 호출 상태, 캐시, 재시도 관리 -- React Hook Form + Zod: 발급 폼, 문의 폼, 단어 제안 입력 검증 -- shadcn/ui + lucide-react: 운영형 UI 컴포넌트와 아이콘 구성 - -## 앞으로 만들 화면 후보 - -현재 PR 범위는 공개 랜딩과 API 문서입니다. -콘솔이나 계정성 기능은 별도 작업으로 분리하는 것이 좋습니다. - -- API Key 발급 -- API Key 재발급과 폐기 -- 비속어 필터 테스트 콘솔 -- 단어 추가, 제거, 수정 요청 -- 문의 접수 - -## 백엔드와 맞춰야 할 계약 - -- 문의 API를 만들지, `mailto`나 외부 폼으로 시작할지 결정해야 합니다. -- API Key를 브라우저에 저장할지, 사용자가 매번 입력하게 할지 결정해야 합니다. -- 관리자 단어 승인 API는 현재 백엔드 구현 상태를 확인한 뒤 화면 제공 여부를 정해야 합니다. -- 공개 문서의 OpenAPI JSON은 API 서버가 제공하는 `/openapi.json`을 source-of-truth로 둡니다. - -## 배포 구상 - -```text -ui/sample-app - -> npm run build - -> dist/ - -> Docker image - -> k3s Deployment - -> Service - -> HTTPRoute -``` +## Working Rules -처음에는 하나의 FE 앱 안에서 라우팅만 나눠도 충분합니다. -도메인을 분리할 경우 공개 페이지, 문서, 콘솔의 책임을 먼저 나눈 뒤 결정합니다. +- 새 메뉴는 `APP_ROUTES`에 추가한다. +- route matching은 경계 조건을 테스트한다. +- 브라우저 effect는 custom hook으로 분리한다. +- 화면 변경 전후로 주요 route를 브라우저에서 확인한다. +- 디자인 변경은 `policies/design-system.md`를 먼저 따른다. diff --git a/ui/sample-app/.gitignore b/ui/app/.gitignore similarity index 100% rename from ui/sample-app/.gitignore rename to ui/app/.gitignore diff --git a/ui/app/DESIGN_GUIDELINES.md b/ui/app/DESIGN_GUIDELINES.md new file mode 100644 index 0000000..8cfb603 --- /dev/null +++ b/ui/app/DESIGN_GUIDELINES.md @@ -0,0 +1,5 @@ +# FE Design Guidelines + +Canonical design system rules live in `../policies/design-system.md`. + +Keep this file as a compatibility pointer for local editors. Do not add product rules here. diff --git a/ui/sample-app/Dockerfile b/ui/app/Dockerfile similarity index 100% rename from ui/sample-app/Dockerfile rename to ui/app/Dockerfile diff --git a/ui/app/README.md b/ui/app/README.md new file mode 100644 index 0000000..cf1bd6e --- /dev/null +++ b/ui/app/README.md @@ -0,0 +1,42 @@ +# profanity-filter-api App + +이 앱은 `profanity-filter-api`의 프론트엔드 런타임이다. 정책과 작업 지침은 상위 `ui/` 디렉터리에 둔다. + +## Routes + +- `/`: 서비스 소개 랜딩 +- `/register`: 키 발급 +- `/docs`: API 문서 + +## Commands + +```bash +npm ci +npm run dev +npm run typecheck +npm run test:policy +npm run test:routing +npm run test:hooks +npm run test:register +npm run test:docs +npm run build +``` + +## Runtime Boundaries + +- `src/app/routes.tsx`: 메뉴와 route source of truth +- `src/hooks/useAppNavigation.ts`: history, popstate, scroll restoration +- `src/hooks/useClipboard.ts`: clipboard success/failure feedback +- `src/docs/*`: OpenAPI와 overview 문서 로딩 +- `src/features/*`: route별 화면 + +## Deployment Shape + +```text +npm run build + -> dist/ + -> Docker image + -> k3s Deployment + -> Service + -> HTTPRoute +``` diff --git a/ui/sample-app/index.html b/ui/app/index.html similarity index 100% rename from ui/sample-app/index.html rename to ui/app/index.html diff --git a/ui/sample-app/nginx.conf b/ui/app/nginx.conf similarity index 100% rename from ui/sample-app/nginx.conf rename to ui/app/nginx.conf diff --git a/ui/sample-app/package-lock.json b/ui/app/package-lock.json similarity index 99% rename from ui/sample-app/package-lock.json rename to ui/app/package-lock.json index f8b1c1f..422cb60 100644 --- a/ui/sample-app/package-lock.json +++ b/ui/app/package-lock.json @@ -1,11 +1,11 @@ { - "name": "profanity-filter-console-sample", + "name": "profanity-filter-console-ui", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "profanity-filter-console-sample", + "name": "profanity-filter-console-ui", "version": "0.0.0", "dependencies": { "@scalar/api-reference-react": "^0.9.47", diff --git a/ui/sample-app/package.json b/ui/app/package.json similarity index 75% rename from ui/sample-app/package.json rename to ui/app/package.json index 5e63c15..27fdc33 100644 --- a/ui/sample-app/package.json +++ b/ui/app/package.json @@ -1,5 +1,5 @@ { - "name": "profanity-filter-console-sample", + "name": "profanity-filter-console-ui", "private": true, "version": "0.0.0", "type": "module", @@ -9,6 +9,9 @@ "preview": "vite preview", "test:register": "node scripts/check-register-flow.mjs", "test:docs": "node scripts/check-docs-layout.mjs", + "test:hooks": "node scripts/check-hooks-contract.mjs", + "test:policy": "node scripts/check-policy-docs.mjs", + "test:routing": "node scripts/check-routing-contract.mjs", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/ui/sample-app/public/overview.md b/ui/app/public/overview.md similarity index 100% rename from ui/sample-app/public/overview.md rename to ui/app/public/overview.md diff --git a/ui/sample-app/scripts/check-docs-layout.mjs b/ui/app/scripts/check-docs-layout.mjs similarity index 96% rename from ui/sample-app/scripts/check-docs-layout.mjs rename to ui/app/scripts/check-docs-layout.mjs index bb6b60b..0b83038 100644 --- a/ui/sample-app/scripts/check-docs-layout.mjs +++ b/ui/app/scripts/check-docs-layout.mjs @@ -10,7 +10,7 @@ const docsHooks = readFileSync(resolve(appRoot, "src", "docs", "hooks.ts"), "utf const docsUtils = readFileSync(resolve(appRoot, "src", "docs", "utils.tsx"), "utf8"); const homePage = readFileSync(resolve(appRoot, "src", "features", "home", "HomePage.tsx"), "utf8"); const landingContent = readFileSync(resolve(appRoot, "src", "constants", "landingContent.ts"), "utf8"); -const guidelines = readFileSync(resolve(appRoot, "DESIGN_GUIDELINES.md"), "utf8"); +const designPolicy = readFileSync(resolve(appRoot, "..", "policies", "design-system.md"), "utf8"); const styles = readFileSync(resolve(appRoot, "src", "styles.css"), "utf8"); const overview = readFileSync(resolve(appRoot, "public", "overview.md"), "utf8"); const appSources = [app, homePage, landingContent].join("\n"); @@ -106,9 +106,9 @@ assert.match(mobileBlock, /\.api-docs-content\s*{[^}]*padding:/s, "Mobile docs l assert.doesNotMatch(mobileBlock, /\.api-docs-content\s*{[^}]*padding:\s*0\s*;/s, "Mobile docs content must keep horizontal padding instead of touching viewport edges."); assert.doesNotMatch(mobileBlock, /\.api-docs-sidebar\s*{[^}]*position:\s*fixed/s, "Mobile sidebar must not become a fixed overlay."); -assert.match(guidelines, /첫 화면은 4개 블록으로 구성한다\./, "Guidelines must define the four-block landing structure."); -assert.match(guidelines, /2번 블록은 프로젝트 정체성을 짧게 설명한다\./, "Guidelines must define the second block as the project identity section."); -assert.match(guidelines, /3번 블록은 사용 시나리오를 영상형 흐름으로 보여준다\./, "Guidelines must define the third block as the scenario reel section."); +assert.match(designPolicy, /첫 화면은 4개 블록으로 구성한다\./, "Design policy must define the four-block landing structure."); +assert.match(designPolicy, /2번 블록은 프로젝트 정체성을 짧게 설명한다\./, "Design policy must define the second block as the project identity section."); +assert.match(designPolicy, /3번 블록은 사용 시나리오를 영상형 흐름으로 보여준다\./, "Design policy must define the third block as the scenario reel section."); assert.match(styles, /--landing-letterbox:\s*16vw/, "Landing sections must define a ratio-based side letterbox gutter."); assert.match(styles, /--landing-rail-width:\s*68vw/, "Landing sections must use a ratio-based readable rail."); assert.match(styles, /--landing-section-height:\s*calc\(100dvh - var\(--landing-nav-height\)\)/, "Landing story sections must target one viewport below the fixed nav."); diff --git a/ui/app/scripts/check-hooks-contract.mjs b/ui/app/scripts/check-hooks-contract.mjs new file mode 100644 index 0000000..f029e54 --- /dev/null +++ b/ui/app/scripts/check-hooks-contract.mjs @@ -0,0 +1,35 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const appRoot = resolve(import.meta.dirname, ".."); + +const navigationHookPath = resolve(appRoot, "src", "hooks", "useAppNavigation.ts"); +const clipboardHookPath = resolve(appRoot, "src", "hooks", "useClipboard.ts"); + +assert.ok(existsSync(navigationHookPath), "useAppNavigation hook must exist."); +assert.ok(existsSync(clipboardHookPath), "useClipboard hook must exist."); + +const navigationHook = readFileSync(navigationHookPath, "utf8"); +const clipboardHook = readFileSync(clipboardHookPath, "utf8"); +const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8"); +const docsPage = readFileSync(resolve(appRoot, "src", "DocsPage.tsx"), "utf8"); +const registerPage = readFileSync(resolve(appRoot, "src", "features", "register", "RegisterPage.tsx"), "utf8"); + +assert.match(navigationHook, /export function useAppNavigation/, "Navigation hook must export useAppNavigation."); +assert.match(navigationHook, /addEventListener\("popstate"/, "Navigation hook must own popstate synchronization."); +assert.match(navigationHook, /scrollRestoration\s*=\s*"manual"/, "Navigation hook must own scroll restoration."); +assert.match(navigationHook, /getRouteForPathname/, "Navigation hook must use route registry."); +assert.match(navigationHook, /navigate/, "Navigation hook must return a navigate command."); + +assert.match(clipboardHook, /export function useClipboard/, "Clipboard hook must export useClipboard."); +assert.match(clipboardHook, /ClipboardStatus\s*=\s*"idle" \| "copied" \| "failed"/, "Clipboard state must include failed status."); +assert.match(clipboardHook, /try\s*{[\s\S]*writeText[\s\S]*}\s*catch/s, "Clipboard writes must handle failures."); + +assert.match(app, /useAppNavigation/, "App must use navigation hook."); +assert.doesNotMatch(app, /addEventListener\("popstate"/, "App must not own popstate directly."); + +assert.match(docsPage, /useClipboard/, "Docs page must use clipboard hook."); +assert.match(registerPage, /useClipboard/, "Register page must use clipboard hook."); +assert.doesNotMatch(docsPage, /navigator\.clipboard\.writeText/, "Docs page must not call clipboard directly."); +assert.doesNotMatch(registerPage, /navigator\.clipboard\.writeText/, "Register page must not call clipboard directly."); diff --git a/ui/app/scripts/check-policy-docs.mjs b/ui/app/scripts/check-policy-docs.mjs new file mode 100644 index 0000000..325cea1 --- /dev/null +++ b/ui/app/scripts/check-policy-docs.mjs @@ -0,0 +1,54 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +const uiRoot = resolve(import.meta.dirname, "..", ".."); +const appRoot = resolve(import.meta.dirname, ".."); + +assert.equal(appRoot.endsWith(`${resolve(uiRoot, "app")}`), true, "Runtime app directory must be ui/app."); +assert.equal( + existsSync(resolve(uiRoot, ["sample", "app"].join("-"))), + false, + "Runtime app directory must not use the old temporary name.", +); + +const requiredFiles = [ + "policies/engineering.md", + "policies/design-system.md", + "skills/profanity-ui/SKILL.md", +]; + +for (const filePath of requiredFiles) { + assert.ok(existsSync(resolve(uiRoot, filePath)), `${filePath} must live under ui/.`); +} + +const uiReadme = readFileSync(resolve(uiRoot, "README.md"), "utf8"); +const appReadme = readFileSync(resolve(appRoot, "README.md"), "utf8"); +const engineeringPolicy = readFileSync(resolve(uiRoot, "policies", "engineering.md"), "utf8"); +const designPolicy = readFileSync(resolve(uiRoot, "policies", "design-system.md"), "utf8"); +const skill = readFileSync(resolve(uiRoot, "skills", "profanity-ui", "SKILL.md"), "utf8"); + +for (const [name, content] of [ + ["ui/README.md", uiReadme], + ["app/README.md", appReadme], +]) { + assert.doesNotMatch(content, new RegExp(["샘", "플"].join("")), `${name} must describe the product UI, not a temporary app.`); + assert.doesNotMatch(content, /실험|데모|demo/i, `${name} must avoid prototype/demo framing.`); +} + +assert.match(engineeringPolicy, /YAGNI/, "Engineering policy must explicitly define YAGNI."); +assert.match(engineeringPolicy, /OOP/, "Engineering policy must explicitly define OOP boundaries."); +assert.match(engineeringPolicy, /Tell, Don'?t Ask/, "Engineering policy must include Tell, Don't Ask."); +assert.match(engineeringPolicy, /Rules of Hooks/, "Engineering policy must cite hook rules."); +assert.match(engineeringPolicy, /Red.*Green.*Refactor/s, "Engineering policy must define TDD flow."); + +assert.match(designPolicy, /Design Tokens/, "Design policy must define design tokens."); +assert.match(designPolicy, /--ivory/, "Design policy must keep the existing ivory token."); +assert.match(designPolicy, /--pine/, "Design policy must keep the existing pine token."); +assert.match(designPolicy, /--sage/, "Design policy must keep the existing sage token."); +assert.match(designPolicy, /상태/, "Design policy must cover UI state rules."); +assert.match(designPolicy, /메뉴/, "Design policy must cover progressive menu growth."); + +assert.match(skill, /^---\nname: profanity-ui\n/m, "Project skill must have a stable name."); +assert.match(skill, /policies\/engineering\.md/, "Project skill must route agents to engineering policy."); +assert.match(skill, /policies\/design-system\.md/, "Project skill must route agents to design policy."); diff --git a/ui/sample-app/scripts/check-register-flow.mjs b/ui/app/scripts/check-register-flow.mjs similarity index 86% rename from ui/sample-app/scripts/check-register-flow.mjs rename to ui/app/scripts/check-register-flow.mjs index 8a37a7c..9bc8832 100644 --- a/ui/sample-app/scripts/check-register-flow.mjs +++ b/ui/app/scripts/check-register-flow.mjs @@ -6,12 +6,14 @@ const appRoot = resolve(import.meta.dirname, ".."); const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8"); const pagePath = readFileSync(resolve(appRoot, "src", "constants", "pagePath.ts"), "utf8"); +const routes = readFileSync(resolve(appRoot, "src", "app", "routes.tsx"), "utf8"); const homePage = readFileSync(resolve(appRoot, "src", "features", "home", "HomePage.tsx"), "utf8"); const registerPage = readFileSync(resolve(appRoot, "src", "features", "register", "RegisterPage.tsx"), "utf8"); const styles = readFileSync(resolve(appRoot, "src", "styles.css"), "utf8"); assert.match(pagePath, /REGISTER_PATH(?:\s*:\s*PagePath)?\s*=\s*"\/register"/, "Register route must be defined as a first-class page path."); -assert.match(app, //, "App must render the register page for the register route."); +assert.match(app, /currentRoute\.render/, "App must render selected routes through the route registry."); +assert.match(routes, /id:\s*"register"[\s\S]*/, "Route registry must render the register page for the register route."); assert.match(homePage, /onNavigate\("\/register"\)/, "Home apply CTA must navigate to the register page."); assert.match(registerPage, /function createLocalApiKey/, "Register page must create an API key locally until the API is connected."); @@ -28,7 +30,7 @@ assert.doesNotMatch(registerPage, /https:\/\/api\.kr-filter\.com\/api\/v1\/clien assert.doesNotMatch(registerPage, /mock|모의/i, "Register page must not present the key issuance flow as fake UI."); assert.doesNotMatch(registerPage, /API Key|POST \/api\/v1\/clients\/register|ClientsRegistResponse|이름, 이메일, 발급자 정보를 입력해 키를 발급합니다/, "Register page must not show implementation or document-copy text."); assert.doesNotMatch(registerPage, /responsePreview|
/, "Register page must not show raw response JSON.");
-assert.match(registerPage, /navigator\.clipboard\.writeText/, "Issued keys must be copyable.");
+assert.match(registerPage, /useClipboard/, "Issued keys must be copyable through the shared clipboard hook.");
 
 assert.match(styles, /\.register-page\b/, "Register page shell must be styled.");
 assert.match(styles, /\.register-form\b/, "Register form must be styled.");
diff --git a/ui/app/scripts/check-routing-contract.mjs b/ui/app/scripts/check-routing-contract.mjs
new file mode 100644
index 0000000..6c56d2f
--- /dev/null
+++ b/ui/app/scripts/check-routing-contract.mjs
@@ -0,0 +1,27 @@
+import assert from "node:assert/strict";
+import { readFileSync } from "node:fs";
+import { resolve } from "node:path";
+
+const appRoot = resolve(import.meta.dirname, "..");
+
+const routes = readFileSync(resolve(appRoot, "src", "app", "routes.tsx"), "utf8");
+const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8");
+const navigation = readFileSync(resolve(appRoot, "src", "components", "Navigation.tsx"), "utf8");
+const pagePath = readFileSync(resolve(appRoot, "src", "constants", "pagePath.ts"), "utf8");
+
+assert.match(routes, /export const APP_ROUTES/, "Routes must be centralized in APP_ROUTES.");
+assert.match(routes, /showInNavigation:\s*true/, "Navigation visibility must be route metadata.");
+assert.match(routes, /matchPath/, "Each route must expose a matchPath contract.");
+assert.match(routes, /pathname === DOCS_PATH \|\| pathname\.startsWith\(`\$\{DOCS_PATH\}\/`\)/, "Docs route must not match docs-admin style paths.");
+assert.match(routes, /getRouteForPathname/, "Route selection must be centralized.");
+assert.match(routes, /getNavigationItems/, "Navigation item selection must be centralized.");
+
+assert.match(app, /currentRoute\.render/, "App must render through route registry.");
+assert.doesNotMatch(app, /pagePath\.startsWith\("\/docs"\)/, "App must not hard-code docs prefix matching.");
+assert.doesNotMatch(app, /pagePath === "\/register"/, "App must not hard-code register routing.");
+
+assert.match(navigation, /getNavigationItems/, "Navigation must read menu items from route registry.");
+assert.doesNotMatch(navigation, /href="\/register"[\s\S]*href="\/docs"/, "Navigation must not hard-code menu anchors.");
+
+assert.match(pagePath, /DOCS_PATH/, "Page path constants must expose DOCS_PATH.");
+assert.doesNotMatch(pagePath, /pathname\.startsWith\(DOCS_PREFIX\)/, "Page path parsing must not use broad docs prefix matching.");
diff --git a/ui/app/src/App.tsx b/ui/app/src/App.tsx
new file mode 100644
index 0000000..7bab7c1
--- /dev/null
+++ b/ui/app/src/App.tsx
@@ -0,0 +1,52 @@
+import { lazy, Suspense } from "react";
+
+import { OPENAPI_DOCUMENT_URL, OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN } from "./docs/constants";
+import { preloadDocsDocuments } from "./docs/hooks";
+import { Navigation } from "./components/Navigation";
+import { useAppNavigation } from "./hooks/useAppNavigation";
+
+function loadDocsPage() {
+  const docsPageModule = import("./DocsPage");
+  const docsDocuments = preloadDocsDocuments(
+    OPENAPI_DOCUMENT_URL,
+    OVERVIEW_MARKDOWN_PATH,
+    FALLBACK_OVERVIEW_MARKDOWN,
+  );
+
+  return Promise.all([
+    docsPageModule,
+    docsDocuments,
+  ]).then(([module]) => module);
+}
+
+const DocsPage = lazy(loadDocsPage);
+
+export default function App() {
+  const { currentRoute, navigate, pagePath } = useAppNavigation();
+  const docsElement = (
+    }>
+      
+    
+  );
+
+  return (
+    
+ + {currentRoute.render({ docsElement, navigate })} +
+ ); +} + +function DocsFallback() { + return ; +} + +function DocsLoadingOverlay() { + return ( +
+
+ +
+
+ ); +} diff --git a/ui/sample-app/src/DocsPage.tsx b/ui/app/src/DocsPage.tsx similarity index 96% rename from ui/sample-app/src/DocsPage.tsx rename to ui/app/src/DocsPage.tsx index 572d87e..5c20436 100644 --- a/ui/sample-app/src/DocsPage.tsx +++ b/ui/app/src/DocsPage.tsx @@ -13,6 +13,7 @@ import { parseMarkdown, } from "./docs/utils"; import { useMarkdownDocument, useOpenApiDocument } from "./docs/hooks"; +import { useClipboard } from "./hooks/useClipboard"; function getDecodedHash() { const rawHash = window.location.hash.replace(/^#/, ""); @@ -27,11 +28,11 @@ function getDecodedHash() { } export default function DocsPage() { - const [copied, setCopied] = useState(false); const [activeHash, setActiveHash] = useState(getDecodedHash); const [openSectionSlugs, setOpenSectionSlugs] = useState>(() => new Set()); const { document, error } = useOpenApiDocument(OPENAPI_DOCUMENT_URL); const overview = useMarkdownDocument(OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN); + const { copy, status: copyStatus } = useClipboard(); useEffect(() => { const syncHash = () => setActiveHash(getDecodedHash()); @@ -115,12 +116,7 @@ export default function DocsPage() { }, [activeHash, selectedSection?.slug, overview.content]); const copyOpenApiUrl = async () => { - if (!navigator.clipboard) { - return; - } - await navigator.clipboard.writeText(OPENAPI_DOCUMENT_URL); - setCopied(true); - window.setTimeout(() => setCopied(false), 1400); + await copy(OPENAPI_DOCUMENT_URL); }; if (error) { @@ -213,7 +209,7 @@ export default function DocsPage() {
{isOverview ? (overview.source === "local" ? "Markdown" : "fallback") : document.openapi}
diff --git a/ui/app/src/app/routes.tsx b/ui/app/src/app/routes.tsx new file mode 100644 index 0000000..5fb1482 --- /dev/null +++ b/ui/app/src/app/routes.tsx @@ -0,0 +1,58 @@ +import type { ReactNode } from "react"; + +import { DOCS_PATH, HOME_PATH, REGISTER_PATH, type PagePath } from "../constants/pagePath"; +import { HomePage } from "../features/home/HomePage"; +import { RegisterPage } from "../features/register/RegisterPage"; + +type Navigate = (path: PagePath) => void; + +type RouteRenderContext = { + docsElement: ReactNode; + navigate: Navigate; +}; + +export type AppRoute = { + id: "home" | "register" | "docs"; + label: string; + matchPath: (pathname: string) => boolean; + path: PagePath; + render: (context: RouteRenderContext) => ReactNode; + showInNavigation: boolean; +}; + +export const APP_ROUTES: AppRoute[] = [ + { + id: "home", + label: "홈", + matchPath: (pathname) => pathname === HOME_PATH, + path: HOME_PATH, + render: ({ navigate }) => , + showInNavigation: true, + }, + { + id: "register", + label: "시작", + matchPath: (pathname) => pathname === REGISTER_PATH, + path: REGISTER_PATH, + render: ({ navigate }) => , + showInNavigation: true, + }, + { + id: "docs", + label: "문서", + matchPath: (pathname) => pathname === DOCS_PATH || pathname.startsWith(`${DOCS_PATH}/`), + path: DOCS_PATH, + render: ({ docsElement }) => docsElement, + showInNavigation: true, + }, +]; + +export const HOME_ROUTE = APP_ROUTES[0]; + +export function getRouteForPathname(pathname: string) { + return APP_ROUTES.find((route) => route.matchPath(pathname)) ?? HOME_ROUTE; +} + +export function getNavigationItems() { + return APP_ROUTES.filter((route) => route.showInNavigation); +} diff --git a/ui/app/src/components/Navigation.tsx b/ui/app/src/components/Navigation.tsx new file mode 100644 index 0000000..cfcd858 --- /dev/null +++ b/ui/app/src/components/Navigation.tsx @@ -0,0 +1,41 @@ +import type { PagePath } from "../constants/pagePath"; +import { getNavigationItems } from "../app/routes"; + +type NavigationProps = { + onNavigate: (path: PagePath) => void; + pagePath: PagePath; +}; + +export function Navigation({ onNavigate, pagePath }: NavigationProps) { + const navigationItems = getNavigationItems(); + + return ( +
+ { + event.preventDefault(); + onNavigate("/"); + }} + > + 말조심하세욧 + + +
+ ); +} diff --git a/ui/sample-app/src/constants/landingContent.ts b/ui/app/src/constants/landingContent.ts similarity index 100% rename from ui/sample-app/src/constants/landingContent.ts rename to ui/app/src/constants/landingContent.ts diff --git a/ui/sample-app/src/constants/pagePath.ts b/ui/app/src/constants/pagePath.ts similarity index 60% rename from ui/sample-app/src/constants/pagePath.ts rename to ui/app/src/constants/pagePath.ts index 569d2f4..6fe0977 100644 --- a/ui/sample-app/src/constants/pagePath.ts +++ b/ui/app/src/constants/pagePath.ts @@ -1,11 +1,11 @@ -export type PagePath = "/" | "/register" | `/docs${string}`; +export type PagePath = "/" | "/register" | "/docs" | `/docs/${string}`; export const HOME_PATH: PagePath = "/"; -export const DOCS_PREFIX = "/docs"; +export const DOCS_PATH: PagePath = "/docs"; export const REGISTER_PATH: PagePath = "/register"; export function getCurrentPagePath(pathname: string): PagePath { - if (pathname.startsWith(DOCS_PREFIX)) { + if (pathname === DOCS_PATH || pathname.startsWith(`${DOCS_PATH}/`)) { return pathname as PagePath; } diff --git a/ui/sample-app/src/demo/ConsoleDemo.tsx b/ui/app/src/demo/ConsoleDemo.tsx similarity index 99% rename from ui/sample-app/src/demo/ConsoleDemo.tsx rename to ui/app/src/demo/ConsoleDemo.tsx index d33ec90..86348c3 100644 --- a/ui/sample-app/src/demo/ConsoleDemo.tsx +++ b/ui/app/src/demo/ConsoleDemo.tsx @@ -185,7 +185,7 @@ function buildClientRegisterRequest(name: string, email: string, issuerInfo: str name, email, issuerInfo, - note: "sample-app console", + note: "ui console", }; } diff --git a/ui/sample-app/src/demo/types.ts b/ui/app/src/demo/types.ts similarity index 100% rename from ui/sample-app/src/demo/types.ts rename to ui/app/src/demo/types.ts diff --git a/ui/sample-app/src/docs/constants.ts b/ui/app/src/docs/constants.ts similarity index 100% rename from ui/sample-app/src/docs/constants.ts rename to ui/app/src/docs/constants.ts diff --git a/ui/sample-app/src/docs/hooks.ts b/ui/app/src/docs/hooks.ts similarity index 100% rename from ui/sample-app/src/docs/hooks.ts rename to ui/app/src/docs/hooks.ts diff --git a/ui/sample-app/src/docs/types.ts b/ui/app/src/docs/types.ts similarity index 100% rename from ui/sample-app/src/docs/types.ts rename to ui/app/src/docs/types.ts diff --git a/ui/sample-app/src/docs/utils.tsx b/ui/app/src/docs/utils.tsx similarity index 100% rename from ui/sample-app/src/docs/utils.tsx rename to ui/app/src/docs/utils.tsx diff --git a/ui/sample-app/src/features/home/HomePage.tsx b/ui/app/src/features/home/HomePage.tsx similarity index 100% rename from ui/sample-app/src/features/home/HomePage.tsx rename to ui/app/src/features/home/HomePage.tsx diff --git a/ui/sample-app/src/features/register/RegisterPage.tsx b/ui/app/src/features/register/RegisterPage.tsx similarity index 94% rename from ui/sample-app/src/features/register/RegisterPage.tsx rename to ui/app/src/features/register/RegisterPage.tsx index b1e2d0c..9c70a81 100644 --- a/ui/sample-app/src/features/register/RegisterPage.tsx +++ b/ui/app/src/features/register/RegisterPage.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import type { FormEvent } from "react"; import type { PagePath } from "../../constants/pagePath"; +import { useClipboard } from "../../hooks/useClipboard"; type RegisterPageProps = { onNavigate: (path: PagePath) => void; @@ -28,10 +29,10 @@ const INITIAL_FORM_STATE: RegisterFormState = { export function RegisterPage({ onNavigate }: RegisterPageProps) { const [formState, setFormState] = useState(INITIAL_FORM_STATE); const [issuedClient, setIssuedClient] = useState(null); - const [copyState, setCopyState] = useState<"idle" | "copied">("idle"); const [emailCodeSent, setEmailCodeSent] = useState(false); const [verificationCode, setVerificationCode] = useState(""); const [emailVerified, setEmailVerified] = useState(false); + const { copy, reset: resetCopyState, status: copyStatus } = useClipboard(); const canSendEmailCode = Boolean(formState.email.trim()); const canVerifyEmail = Boolean(emailCodeSent && verificationCode.trim().length === 6); @@ -41,7 +42,7 @@ export function RegisterPage({ onNavigate }: RegisterPageProps) { function updateField(field: keyof RegisterFormState, value: string) { setFormState((current) => ({ ...current, [field]: value })); - setCopyState("idle"); + resetCopyState(); setIssuedClient(null); if (field === "email") { @@ -61,7 +62,7 @@ export function RegisterPage({ onNavigate }: RegisterPageProps) { ...formState, apiKey: createLocalApiKey(), }); - setCopyState("idle"); + resetCopyState(); } function sendEmailCode() { @@ -87,8 +88,7 @@ export function RegisterPage({ onNavigate }: RegisterPageProps) { return; } - await navigator.clipboard.writeText(issuedClient.apiKey); - setCopyState("copied"); + await copy(issuedClient.apiKey); } return ( @@ -184,7 +184,7 @@ export function RegisterPage({ onNavigate }: RegisterPageProps) { 발급됨 {issuedClient.apiKey} ) : emailVerified ? ( diff --git a/ui/app/src/hooks/useAppNavigation.ts b/ui/app/src/hooks/useAppNavigation.ts new file mode 100644 index 0000000..642a369 --- /dev/null +++ b/ui/app/src/hooks/useAppNavigation.ts @@ -0,0 +1,38 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; + +import { getRouteForPathname } from "../app/routes"; +import { getCurrentPagePath, type PagePath } from "../constants/pagePath"; + +export function useAppNavigation() { + const [pagePath, setPagePath] = useState(() => getCurrentPagePath(window.location.pathname)); + + const syncLocation = useCallback(() => { + setPagePath(getCurrentPagePath(window.location.pathname)); + }, []); + + useEffect(() => { + window.history.scrollRestoration = "manual"; + window.addEventListener("popstate", syncLocation); + return () => window.removeEventListener("popstate", syncLocation); + }, [syncLocation]); + + useEffect(() => { + window.scrollTo({ top: 0, behavior: "auto" }); + }, [pagePath]); + + const navigate = useCallback((path: PagePath) => { + if (window.location.pathname !== path) { + window.history.pushState({}, "", path); + } + setPagePath(path); + window.scrollTo({ top: 0, behavior: "smooth" }); + }, []); + + const currentRoute = useMemo(() => getRouteForPathname(pagePath), [pagePath]); + + return { + currentRoute, + navigate, + pagePath, + }; +} diff --git a/ui/app/src/hooks/useClipboard.ts b/ui/app/src/hooks/useClipboard.ts new file mode 100644 index 0000000..bb75764 --- /dev/null +++ b/ui/app/src/hooks/useClipboard.ts @@ -0,0 +1,50 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +export type ClipboardStatus = "idle" | "copied" | "failed"; + +export function useClipboard(resetAfterMs = 1400) { + const [status, setStatus] = useState("idle"); + const resetTimer = useRef(null); + + const clearResetTimer = useCallback(() => { + if (resetTimer.current !== null) { + window.clearTimeout(resetTimer.current); + resetTimer.current = null; + } + }, []); + + const reset = useCallback(() => { + clearResetTimer(); + setStatus("idle"); + }, [clearResetTimer]); + + const copy = useCallback( + async (value: string) => { + clearResetTimer(); + + try { + if (!navigator.clipboard) { + throw new Error("Clipboard API is not available."); + } + + await navigator.clipboard.writeText(value); + setStatus("copied"); + resetTimer.current = window.setTimeout(() => setStatus("idle"), resetAfterMs); + return true; + } catch { + setStatus("failed"); + resetTimer.current = window.setTimeout(() => setStatus("idle"), resetAfterMs); + return false; + } + }, + [clearResetTimer, resetAfterMs], + ); + + useEffect(() => () => clearResetTimer(), [clearResetTimer]); + + return { + copy, + reset, + status, + }; +} diff --git a/ui/sample-app/src/main.tsx b/ui/app/src/main.tsx similarity index 100% rename from ui/sample-app/src/main.tsx rename to ui/app/src/main.tsx diff --git a/ui/sample-app/src/styles.css b/ui/app/src/styles.css similarity index 100% rename from ui/sample-app/src/styles.css rename to ui/app/src/styles.css diff --git a/ui/sample-app/tsconfig.json b/ui/app/tsconfig.json similarity index 100% rename from ui/sample-app/tsconfig.json rename to ui/app/tsconfig.json diff --git a/ui/sample-app/vite.config.ts b/ui/app/vite.config.ts similarity index 100% rename from ui/sample-app/vite.config.ts rename to ui/app/vite.config.ts diff --git a/ui/policies/design-system.md b/ui/policies/design-system.md new file mode 100644 index 0000000..ec4382d --- /dev/null +++ b/ui/policies/design-system.md @@ -0,0 +1,82 @@ +# UI Design System Policy + +이 문서는 `profanity-filter-api` UI의 시각 언어와 컴포넌트 규칙이다. 제품 화면은 조용한 콘솔형 도구로 읽혀야 한다. + +## Design Philosophy + +- 기능 설명보다 사용자가 판단할 입력, 상태, 결과, 오류를 먼저 배치한다. +- 장식은 구조를 설명할 때만 사용한다. +- 메뉴는 점진적으로 늘어난다. 첫 구조는 작게 유지하되 메뉴가 늘어도 위치, 활성 상태, density가 흔들리지 않아야 한다. +- 화면 문구는 짧고 직접적으로 쓴다. + +## Design Tokens + +기본 색상 token은 3개를 유지한다. + +```css +--ivory: #f6f1df; +--pine: #173b2f; +--sage: #a9b99a; +``` + +- `--ivory`: 페이지 배경, 입력 배경, 보조 버튼 배경 +- `--pine`: 본문 텍스트, 구조선, primary 버튼, 고정 네비게이션 +- `--sage`: 상태 영역, 선택 배경, 보조 강조 +- 새 색상은 상태 의미가 필요할 때만 추가한다. 예: danger, success, warning. +- token 이름은 색 자체보다 역할을 우선한다. + +## Layout Rules + +- 데스크톱은 필요한 경우 2열, 모바일은 1열을 기본으로 한다. +- 주요 콘텐츠 폭은 제한한다. +- 카드 안에 카드를 중첩하지 않는다. +- 반복 정보 단위만 카드로 표현한다. +- 고정 네비게이션이 있는 화면은 `scroll-margin-top` 또는 padding으로 가려짐을 방지한다. +- 긴 JSON, 오류, API key는 줄바꿈 또는 내부 스크롤로 처리한다. + +## Landing Route Rules + +- 첫 화면은 4개 블록으로 구성한다. +- 1번 블록은 hero 구조를 유지한다. +- 2번 블록은 프로젝트 정체성을 짧게 설명한다. +- 2번 블록은 한국어 문장 입력, 비속어 검출, 결과 모드, 서비스 연동 흐름을 보여준다. +- 3번 블록은 사용 시나리오를 영상형 흐름으로 보여준다. +- 3번 블록은 실제 API 응답처럼 보이는 데이터가 아니라 적용 장면을 설명하는 문장만 사용한다. +- 4번 블록은 푸터형 CTA로 두고 `신청하기`, `문서 보기` 버튼만 둔다. +- 랜딩에서 실제 요청 흐름을 실행하거나 실제 API 응답처럼 보이는 데이터를 노출하지 않는다. + +## Typography Rules + +- 기본은 system sans-serif다. +- letter-spacing은 0을 유지한다. +- viewport width에 직접 비례하는 과한 글자 크기 조정을 피한다. +- hero scale은 랜딩 첫 화면에만 쓴다. +- dashboard, form, docs sidebar는 작고 단단한 제목 체계를 쓴다. + +## Component Rules + +- 버튼은 명령이다. 상태 badge와 버튼을 시각적으로 구분한다. +- 입력은 label, value, validation, disabled 상태를 함께 고려한다. +- 메뉴 item은 `label`, `path`, `active` 규칙을 route metadata에서 받는다. +- 아이콘을 도입할 경우 lucide-react를 우선 검토한다. +- 화면 내 visible text로 기능 설명서를 길게 쓰지 않는다. + +## State Rules + +- 상태는 `idle`, `loading`, `success`, `failed`, `empty`처럼 명시한다. +- 색상만으로 상태를 전달하지 않는다. +- 오류는 원인과 다음 행동을 짧게 알려준다. +- 복사, 발급, 인증처럼 짧은 feedback은 1-2초 안에 자동 복귀할 수 있다. + +## Menu Growth Rules + +- 새 메뉴는 route registry에 추가한다. +- nav에 보이지 않는 route는 `showInNavigation: false`로 둔다. +- prefix route는 경계 조건을 테스트한다. +- 메뉴가 5개를 넘으면 desktop nav density와 mobile overflow 방식을 별도 결정한다. + +## Verification Rules + +- 디자인 변경은 실제 브라우저 화면으로 확인한다. +- 데스크톱과 모바일 viewport 중 최소 하나 이상을 캡처한다. +- 텍스트가 버튼, 카드, sidebar 밖으로 밀려나지 않는지 확인한다. diff --git a/ui/policies/engineering.md b/ui/policies/engineering.md new file mode 100644 index 0000000..703547e --- /dev/null +++ b/ui/policies/engineering.md @@ -0,0 +1,51 @@ +# UI Engineering Policy + +이 문서는 `ui/` 영역의 코드 리팩토링, 구현, 테스트 기준이다. 새 화면과 메뉴는 점진적으로 추가하되, 미래 기능을 미리 구현하지 않는다. + +## Core Philosophy + +- OOP: 모듈은 데이터 묶음이 아니라 책임과 계약을 가진 객체처럼 다룬다. 외부에는 사용법을 드러내고 내부 상태와 브라우저 effect는 숨긴다. +- YAGNI: 아직 쓰지 않는 화면, API client, 상태 저장소를 만들지 않는다. 단, route registry, 테스트, 작은 리팩토링처럼 미래 변경을 안전하게 만드는 구조는 허용한다. +- Tell, Don't Ask: 호출자가 route 종류를 직접 묻고 분기하지 않는다. route definition이 `matchPath()`와 `render()` 계약을 제공한다. +- Separation of Concerns: routing, browser history, clipboard, API fetch, form state, view markup을 같은 함수에 섞지 않는다. +- Evolutionary Design: 작은 테스트로 현재 동작을 고정하고, 필요한 만큼만 구조를 개선한다. + +## Refactoring Rules + +- 리팩토링은 사용자-visible 동작을 바꾸지 않는다. +- 먼저 기존 화면 캡처와 주요 동작을 확인한다. +- 변경 전 실패하는 테스트를 추가하고 Red, Green, Refactor 순서를 지킨다. +- 3개 이상 파일을 바꾸는 변경은 파일 책임 목록을 먼저 정리한다. +- 기존 사용자 변경은 되돌리지 않는다. + +## Hooks Rules + +- React 공식 Rules of Hooks를 따른다. +- hook 이름은 반드시 `use`로 시작한다. +- hook은 React 함수 컴포넌트 또는 다른 hook의 top level에서만 호출한다. +- 브라우저 effect는 hook으로 격리한다. 예: history, popstate, clipboard, timer. +- hook은 command 이름을 명확히 반환한다. 예: `navigate`, `copy`, `reset`. +- 실패 상태를 숨기지 않는다. clipboard, fetch, parsing 실패는 UI가 표현할 수 있는 상태로 만든다. + +## Routing Rules + +- 메뉴와 route는 `src/app/routes.tsx`의 `APP_ROUTES`가 source of truth다. +- 새 메뉴는 route object를 추가해 확장한다. +- route object는 `path`, `label`, `showInNavigation`, `matchPath`, `render`를 가진다. +- prefix route는 경계를 명시한다. `/docs`는 `/docs`와 `/docs/...`만 매칭하고 `/docs-admin`은 매칭하지 않는다. +- fallback route는 홈으로 둔다. + +## Testing Rules + +- Red, Green, Refactor를 지킨다. +- 문자열 계약 검사는 `scripts/check-*.mjs`에 둔다. +- 화면 동작은 Playwright 또는 브라우저 기반 smoke check로 확인한다. +- 검증 없이 완료를 주장하지 않는다. +- test skip, 빈 catch, 타입 회피, lint disable로 문제를 우회하지 않는다. + +## API Rules + +- API 호출은 컴포넌트에 직접 흩뿌리지 않는다. +- endpoint별 client 함수와 DTO 타입을 분리한다. +- HTTP 성공과 body `status.code` 성공은 별개로 해석한다. +- API key와 민감 값은 기본적으로 브라우저 저장소에 오래 보관하지 않는다. diff --git a/ui/sample-app/DESIGN_GUIDELINES.md b/ui/sample-app/DESIGN_GUIDELINES.md deleted file mode 100644 index 927f7ad..0000000 --- a/ui/sample-app/DESIGN_GUIDELINES.md +++ /dev/null @@ -1,135 +0,0 @@ -# FE 기본 지침 - -이 문서는 `ui/sample-app` 데모를 기준으로 한 프론트엔드 작업 지침입니다. -메뉴, 화면 목록, 기능 설명은 이 문서의 범위가 아닙니다. - -## 기본 방향 - -- 프론트엔드는 백엔드와 별도 앱으로 관리한다. -- 첫 배포 단위는 정적 SPA로 둔다. -- 실행 환경은 k3s + Nginx 정적 서빙을 기준으로 한다. -- UI는 빠르게 읽히는 콘솔형 제품 화면을 기준으로 한다. -- 장식보다 입력, 결과, 상태, 오류를 명확히 보여주는 것을 우선한다. - -## 기술 기준 - -- 기본 스택은 `Vite + React + TypeScript`로 둔다. -- Vite는 개발 서버와 정적 빌드 산출물 생성을 담당한다. -- React는 화면을 작은 컴포넌트 단위로 나누기 위해 사용한다. -- TypeScript는 API 요청/응답 계약을 명시하기 위해 사용한다. -- 서버 상태 관리는 실제 구현 단계에서 TanStack Query로 분리한다. -- 폼 상태와 검증은 실제 구현 단계에서 React Hook Form + Zod로 분리한다. -- UI 컴포넌트는 shadcn/ui 도입을 우선 검토하되, 프로젝트 안에서 수정 가능한 형태를 유지한다. - -## 색상 기준 - -사용 가능한 기준 색상은 아래 3개 CSS 변수로 제한한다. - -```css ---ivory: #f6f1df; ---pine: #173b2f; ---sage: #a9b99a; -``` - -- `--ivory`: 페이지 배경, 카드 내부 배경, 입력 배경, 보조 버튼 배경 -- `--pine`: 본문 텍스트, 테두리, 구조선, primary 버튼 배경 -- `--sage`: 상태 영역, 헤더성 배지, 영역 구분 배경 -- hover/focus 상태는 `pine` 배경과 `ivory` 글자 반전을 기본으로 한다. -- 새 색상을 추가하기 전에 기존 3색 조합으로 표현 가능한지 먼저 확인한다. -- 차트, 위험 상태, 성공 상태처럼 추가 색상이 필요한 경우에는 별도 디자인 결정으로 분리한다. - -## 레이아웃 기준 - -- 화면은 넓은 데스크톱에서 2열 구조를 기본으로 하되, 모바일에서는 1열로 접는다. -- 주요 콘텐츠 폭은 제한해 긴 줄 읽기를 피한다. -- 카드 안에 카드를 중첩하지 않는다. -- 반복되는 정보 단위만 카드로 표현한다. -- 페이지 섹션은 장식적인 배경보다 명확한 여백과 구분선으로 나눈다. -- 고정 크기가 필요한 영역은 `min-height`, `grid`, `max-width` 같은 명시적 제약을 둔다. -- 텍스트가 버튼, 입력, 카드 밖으로 밀려나지 않게 한다. - -## 랜딩 페이지 기준 - -- 첫 화면은 4개 블록으로 구성한다. -- 1번 블록은 현재 hero 구조를 유지한다. -- 2번 블록은 프로젝트 정체성을 짧게 설명한다. -- 2번 블록은 한국어 문장 입력, 비속어 검출, 결과 모드, 서비스 연동 흐름을 보여준다. -- 3번 블록은 사용 시나리오를 영상형 흐름으로 보여준다. -- 3번 블록은 실제 API 응답처럼 보이는 데이터가 아니라 적용 장면을 설명하는 샘플 문장만 사용한다. -- 4번 블록은 푸터형 CTA로 두고 `신청하기`, `문서 보기` 버튼만 둔다. -- 랜딩에서 실제 요청 흐름을 실행하거나 실제 API 응답처럼 보이는 데이터를 노출하지 않는다. -- 문서 페이지는 OpenAPI 문서를 source of truth로 두고, 자체 사이드바와 Scalar 본문 렌더링을 분리한다. -- 문서 사이드바는 데스크톱에서 sticky, 모바일에서 일반 문서 흐름으로 내려 화면을 덮지 않게 한다. - -## 타이포그래피 기준 - -- 시스템 sans-serif 계열을 기본으로 한다. -- letter-spacing은 기본값을 유지한다. -- viewport width에 직접 비례하는 과한 글자 크기 조정은 피한다. -- 제목은 화면 맥락에 맞게만 크게 쓴다. -- 패널 내부 제목은 작고 단단하게 둔다. -- 본문은 설명보다 판단에 필요한 정보 전달을 우선한다. - -## 컴포넌트 기준 - -- 컴포넌트는 역할 단위로 나눈다. -- 한 컴포넌트는 하나의 명확한 책임만 가진다. -- 입력 컴포넌트는 label, value, error 상태를 함께 다룰 수 있어야 한다. -- 버튼은 명령을 나타내고, 상태 표시는 버튼처럼 보이지 않게 한다. -- 응답 미리보기나 로그 영역은 긴 텍스트와 JSON overflow를 안전하게 처리한다. -- 공통 패턴은 복사보다 컴포넌트화한다. -- 컴포넌트를 만들 때 내부 구현을 몰라도 사용법이 드러나게 props를 설계한다. - -## 상태 관리 기준 - -- 로컬 입력 상태는 화면 가까이에 둔다. -- 서버에서 온 데이터는 TanStack Query 같은 서버 상태 계층으로 분리한다. -- API 호출 결과는 성공, 실패, 로딩, 빈 결과 상태를 모두 표현한다. -- 백엔드가 HTTP 200 안에 `status.code`를 담는 구조이므로 공통 응답 해석 레이어를 둔다. -- 인증 키 같은 민감한 값은 기본적으로 오래 저장하지 않는다. -- 브라우저 저장소 사용 여부는 별도 보안 결정 후 적용한다. - -## API 연동 기준 - -- API 호출은 컴포넌트 내부에 직접 흩뿌리지 않는다. -- endpoint별 client 함수를 만든다. -- 요청 DTO와 응답 DTO는 TypeScript 타입으로 관리한다. -- 백엔드 validation과 FE validation 메시지는 서로 충돌하지 않게 맞춘다. -- 실패 응답은 사람이 이해할 수 있는 문장으로 변환해 표시한다. -- 운영 API 호출 전에는 base URL, CORS, 인증 헤더, 개인정보 저장 정책을 확인한다. - -## 접근성과 사용성 기준 - -- 모든 입력에는 명확한 label을 둔다. -- 키보드 focus 상태를 시각적으로 확인할 수 있어야 한다. -- 버튼과 입력은 모바일 터치에 충분한 높이를 가진다. -- 색상만으로 상태를 설명하지 않는다. -- 긴 응답, 긴 오류, 긴 입력값은 레이아웃을 깨지 않고 줄바꿈 또는 스크롤 처리한다. -- 화면 문구는 짧고 직접적으로 쓴다. - -## 배포 기준 - -- `npm run build`가 통과해야 배포 가능한 상태로 본다. -- 빌드 산출물은 `dist/`에 생성한다. -- 컨테이너는 정적 파일 서빙 전용으로 둔다. -- Nginx 설정은 SPA fallback을 포함한다. -- k3s 배포 시 Deployment, Service, HTTPRoute를 분리한다. -- 헬스 체크 경로는 정적 FE 컨테이너에서도 제공한다. - -## AI 작업 기준 - -- AI가 생성한 UI도 이 문서의 색상, 레이아웃, 컴포넌트 기준을 따라야 한다. -- 새 화면을 만들 때 기능 설명보다 입력, 상태, 결과, 오류 흐름을 먼저 설계한다. -- 라이브러리 사용법은 최신 공식 문서를 확인한 뒤 적용한다. -- AI가 만든 코드는 `npm run build`로 검증한다. -- 디자인 변경은 실제 브라우저에서 확인한다. -- 샘플 데이터를 실제 운영 데이터처럼 보이게 만들지 않는다. - -## 금지 기준 - -- 기준 팔레트 밖 색상을 임의로 추가하지 않는다. -- 기능이 다른 책임을 한 컴포넌트에 합치지 않는다. -- API key나 민감 값을 로그, 샘플 응답, 문서 예시에 실제 값처럼 남기지 않는다. -- 오류를 숨기거나 성공처럼 표시하지 않는다. -- 정적 FE에 서버 전용 비밀값을 주입하지 않는다. -- 배포 파일에 `node_modules/`, `dist/`, 빌드 캐시를 커밋하지 않는다. diff --git a/ui/sample-app/README.md b/ui/sample-app/README.md deleted file mode 100644 index 1559128..0000000 --- a/ui/sample-app/README.md +++ /dev/null @@ -1,139 +0,0 @@ -# profanity-filter-api sample FE - -이 폴더는 공개 랜딩 페이지와 API 문서 화면을 확인하기 위한 Vite 샘플 앱입니다. -현재 라우트는 `/`와 `/docs`에 집중합니다. -콘솔 데모는 아직 라우트에 연결하지 않고 `src/demo/ConsoleDemo.tsx`에 보관합니다. - -## 이 샘플로 판단할 수 있는 것 - -- Vite 프로젝트가 어떤 파일 구조를 갖는지 -- React 컴포넌트가 랜딩 섹션을 어떻게 나누는지 -- 공개 시작 가이드와 문서/예제 섹션을 어떻게 배치하는지 -- k3s에 정적 웹 앱으로 배포할 때 어떤 Dockerfile/Nginx 설정이 필요한지 -- AI가 이후 화면을 확장할 때 어느 파일을 고치면 되는지 - -## 실행 방법 - -```bash -cd ui/sample-app -npm install -npm run dev -``` - -운영 빌드: - -```bash -npm run build -npm run preview -``` - -Vite의 기본 흐름은 `npm run dev`로 개발 서버를 띄우고, `npm run build`로 `dist/` 정적 파일을 만드는 방식입니다. - -## 폴더 구조 - -```text -ui/sample-app -├── package.json # FE 의존성과 실행 스크립트 -├── index.html # React 앱이 붙는 HTML entry -├── vite.config.ts # Vite 설정 -├── Dockerfile # k3s 배포용 정적 웹 컨테이너 -├── nginx.conf # SPA fallback 포함 Nginx 설정 -└── src/ - ├── main.tsx # React 시작점 - ├── App.tsx # 공개 랜딩 페이지와 /docs 라우팅 - ├── DocsPage.tsx # API 서버의 OpenAPI JSON을 읽어 문서 렌더링 - ├── demo/ # 분리 보관한 콘솔 데모 - └── styles.css # 랜딩/문서 공통 스타일 -``` - -## 각 도구가 맡는 일 - -## 색상 기준 - -샘플 앱의 기준 팔레트는 아래 3개 CSS 변수입니다. - -```css ---ivory: #f6f1df; ---pine: #173b2f; ---sage: #a9b99a; -``` - -- `--ivory`: 페이지 배경, 카드/버튼 내부 배경 -- `--pine`: 본문 텍스트, 테두리, primary 버튼 배경 -- `--sage`: 패널 헤더 배지, 상태 영역 배경 -- hover/focus: pine 배경과 ivory 글자를 반전해 선택 상태 표시 - -### Vite - -개발 서버와 빌드 도구입니다. -Spring Boot에서 Gradle이 빌드 진입점인 것처럼, FE에서는 Vite가 개발/빌드 진입점입니다. - -이 샘플에서는: - -- `npm run dev`가 Vite 개발 서버를 실행합니다. -- `npm run build`가 배포용 `dist/`를 생성합니다. -- `Dockerfile`은 생성된 `dist/`를 Nginx에 올립니다. - -### React - -화면을 컴포넌트로 나누는 UI 라이브러리입니다. -이 샘플의 실제 진입점은 다음 파일입니다. - -- `src/App.tsx`: 고정 상단 네비게이션, 랜딩 페이지, `/docs` 전환을 담당합니다. -- `src/DocsPage.tsx`: `https://api.kr-filter.com/openapi.json`을 읽어 API 문서를 렌더링합니다. -- `src/demo/ConsoleDemo.tsx`: API Key 발급·필터 테스트 화면입니다. 현재 공개 라우트에는 연결하지 않았습니다. - -### TypeScript - -JavaScript에 타입을 추가한 언어입니다. -문서 화면은 OpenAPI 문서를 읽기 위한 타입을 `src/docs/types.ts`에 두고, 보관 중인 콘솔 데모는 `FilterMode`, `FilterResponse`, `ClientRegisterRequest` 타입으로 실제 API 요청 흐름을 표현합니다. - -### shadcn/ui - -이 샘플에는 shadcn CLI를 아직 적용하지 않았습니다. -대신 shadcn/ui를 적용하면 어떤 식으로 버튼, 입력, 카드 컴포넌트를 분리할지 판단할 수 있도록 화면 구조를 컴포넌트 단위로 잡았습니다. - -실제 프로젝트에서는 다음 단계에서 shadcn/ui를 붙이는 것이 적절합니다. - -```bash -npx shadcn@latest init -npx shadcn@latest add button input textarea select card tabs badge -``` - -### TanStack Query - -서버 API 호출 상태를 관리하는 도구입니다. -현재 공개 문서 화면은 API 서버의 OpenAPI JSON을 직접 읽고, 보관 중인 콘솔 데모는 브라우저 `fetch`로 실제 API를 호출합니다. -요청 캐싱, 재시도, 오류 상태 공유가 필요해지는 시점에 TanStack Query를 붙이면 됩니다. - -### React Hook Form + Zod - -폼 입력값과 검증을 관리하는 조합입니다. -샘플에서는 의존성을 과하게 늘리지 않기 위해 아직 적용하지 않았습니다. -실제 구현에서는 API Key 발급, 문의, 단어 제안 화면부터 적용하면 됩니다. - -## k3s 배포 형태 - -```text -npm run build - -> dist/ - -> Docker image - -> Deployment - -> Service - -> HTTPRoute -``` - -이 샘플의 `Dockerfile`은 Vite 앱을 빌드한 뒤 `nginx:alpine`으로 정적 파일을 서빙합니다. - -## 백엔드 호출 지점 - -현재 공개 앱은 랜딩 페이지와 문서 페이지를 제공합니다. -`src/demo/ConsoleDemo.tsx`는 아래 실제 API를 호출합니다. - -- `issueApiKey`: `POST /api/v1/clients/register` -- `filterText`: `POST /api/v1/filter` - -## 이 샘플의 의도 - -이 샘플은 최종 디자인이 아닙니다. -“Vite + React로 만들면 이런 개발 단위가 생기고, 이런 파일을 고치며 확장한다”를 판단하기 위한 출발점입니다. diff --git a/ui/sample-app/src/App.tsx b/ui/sample-app/src/App.tsx deleted file mode 100644 index 15e9424..0000000 --- a/ui/sample-app/src/App.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { lazy, Suspense, useCallback, useEffect, useState } from "react"; - -import { getCurrentPagePath, type PagePath } from "./constants/pagePath"; -import { OPENAPI_DOCUMENT_URL, OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN } from "./docs/constants"; -import { preloadDocsDocuments } from "./docs/hooks"; -import { HomePage } from "./features/home/HomePage"; -import { RegisterPage } from "./features/register/RegisterPage"; -import { Navigation } from "./components/Navigation"; - -function loadDocsPage() { - const docsPageModule = import("./DocsPage"); - const docsDocuments = preloadDocsDocuments( - OPENAPI_DOCUMENT_URL, - OVERVIEW_MARKDOWN_PATH, - FALLBACK_OVERVIEW_MARKDOWN, - ); - - return Promise.all([ - docsPageModule, - docsDocuments, - ]).then(([module]) => module); -} - -const DocsPage = lazy(loadDocsPage); - -export default function App() { - const [pagePath, setPagePath] = useState(() => getCurrentPagePath(window.location.pathname)); - - const handlePopState = useCallback(() => { - setPagePath(getCurrentPagePath(window.location.pathname)); - }, []); - - useEffect(() => { - window.history.scrollRestoration = "manual"; - window.addEventListener("popstate", handlePopState); - return () => window.removeEventListener("popstate", handlePopState); - }, [handlePopState]); - - useEffect(() => { - window.scrollTo({ top: 0, behavior: "auto" }); - }, [pagePath]); - - const navigate = useCallback((path: PagePath) => { - if (window.location.pathname !== path) { - window.history.pushState({}, "", path); - } - setPagePath(path); - window.scrollTo({ top: 0, behavior: "smooth" }); - }, []); - - return ( -
- - {pagePath.startsWith("/docs") ? ( - }> - - - ) : pagePath === "/register" ? ( - - ) : ( - - )} -
- ); -} - -function DocsFallback() { - return ; -} - -function DocsLoadingOverlay() { - return ( -
-
- -
-
- ); -} diff --git a/ui/sample-app/src/components/Navigation.tsx b/ui/sample-app/src/components/Navigation.tsx deleted file mode 100644 index 63be5d8..0000000 --- a/ui/sample-app/src/components/Navigation.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { PagePath } from "../constants/pagePath"; - -type NavigationProps = { - onNavigate: (path: PagePath) => void; - pagePath: PagePath; -}; - -export function Navigation({ onNavigate, pagePath }: NavigationProps) { - return ( -
- { - event.preventDefault(); - onNavigate("/"); - }} - > - 말조심하세욧 - - -
- ); -} diff --git a/ui/skills/profanity-ui/SKILL.md b/ui/skills/profanity-ui/SKILL.md new file mode 100644 index 0000000..c317646 --- /dev/null +++ b/ui/skills/profanity-ui/SKILL.md @@ -0,0 +1,29 @@ +--- +name: profanity-ui +description: Use when modifying the profanity-filter-api UI under ui/. Follow project engineering policy, design system policy, routing contracts, hook boundaries, and verification rules before changing screens or menus. +--- + +# Profanity UI Skill + +Use this skill for work under `ui/`. + +## Required References + +- Read `policies/engineering.md` before code refactoring, hooks, routing, API integration, or tests. +- Read `policies/design-system.md` before changing layout, colors, navigation, components, copy, or state presentation. + +## Workflow + +1. Check current git status and do not overwrite unrelated changes. +2. Capture or otherwise verify current behavior before risky UI refactors. +3. Add or update a failing test first for behavior changes. +4. Implement the smallest change that satisfies the policy and test. +5. Run `npm run typecheck`, relevant `npm run test:*` scripts, and `npm run build`. +6. Re-check the affected route in a browser after refactoring. + +## Boundaries + +- Keep policy and agent guidance under `ui/policies` and `ui/skills`. +- Keep app runtime code under `ui/app/src`. +- Keep source-of-truth navigation in `src/app/routes.tsx`. +- Do not add future menus or API flows until they are needed. From 76fc5d33d15e1f05d485a655664131ef2041af44 Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Fri, 10 Jul 2026 22:48:22 +0900 Subject: [PATCH 02/13] =?UTF-8?q?chore:=20.gitignore=EC=97=90=20Playwright?= =?UTF-8?q?=20=EA=B4=80=EB=A0=A8=20=ED=95=AD=EB=AA=A9=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ec05b0c..472d739 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ application-prod.yml .omc/ /.env .DS_Store +/.playwright-mcp/ From fcb0aa51c053fdc74ba1c8ee44b9d40c492938ab Mon Sep 17 00:00:00 2001 From: Whale0928 Date: Fri, 17 Jul 2026 00:55:18 +0900 Subject: [PATCH 03/13] =?UTF-8?q?chore:=20UI=20=ED=94=84=EB=A1=9C=ED=86=A0?= =?UTF-8?q?=ED=83=80=EC=9E=85=20=EB=B0=8F=20=EC=A7=80=EC=B9=A8=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/API_AND_USAGE_FLOWS.md | 126 + ui/README.md | 57 - ui/app/DESIGN_GUIDELINES.md | 5 - ui/app/README.md | 42 - ui/app/index.html | 2 +- ui/app/nginx.conf | 6 - ui/app/package-lock.json | 4361 ++--------------- ui/app/package.json | 11 +- ui/app/public/overview.md | 163 - ui/app/scripts/check-docs-layout.mjs | 159 - ui/app/scripts/check-hooks-contract.mjs | 35 - ui/app/scripts/check-policy-docs.mjs | 54 - ui/app/scripts/check-register-flow.mjs | 37 - ui/app/scripts/check-routing-contract.mjs | 27 - ui/app/src/App.tsx | 51 +- ui/app/src/DocsPage.tsx | 263 - ui/app/src/app/routes.tsx | 58 - ui/app/src/components/Navigation.tsx | 41 - ui/app/src/constants/landingContent.ts | 83 - ui/app/src/constants/pagePath.ts | 22 - ui/app/src/demo/ConsoleDemo.tsx | 197 - ui/app/src/demo/types.ts | 43 - ui/app/src/docs/constants.ts | 29 - ui/app/src/docs/hooks.ts | 164 - ui/app/src/docs/types.ts | 58 - ui/app/src/docs/utils.tsx | 321 -- ui/app/src/features/home/HomePage.tsx | 183 - ui/app/src/features/register/RegisterPage.tsx | 209 - ui/app/src/hooks/useAppNavigation.ts | 38 - ui/app/src/hooks/useClipboard.ts | 50 - ui/app/src/main.tsx | 1 - ui/app/src/styles.css | 1759 ------- ui/openapi.yaml | 1251 ----- ui/policies/design-system.md | 82 - ui/policies/engineering.md | 51 - ui/sample-app/src/App.tsx | 82 - ui/sample-app/src/components/Navigation.tsx | 65 - .../src/features/login/LoginPage.tsx | 109 - ui/skills/profanity-ui/SKILL.md | 29 - 39 files changed, 460 insertions(+), 9864 deletions(-) create mode 100644 ui/API_AND_USAGE_FLOWS.md delete mode 100644 ui/README.md delete mode 100644 ui/app/DESIGN_GUIDELINES.md delete mode 100644 ui/app/README.md delete mode 100644 ui/app/public/overview.md delete mode 100644 ui/app/scripts/check-docs-layout.mjs delete mode 100644 ui/app/scripts/check-hooks-contract.mjs delete mode 100644 ui/app/scripts/check-policy-docs.mjs delete mode 100644 ui/app/scripts/check-register-flow.mjs delete mode 100644 ui/app/scripts/check-routing-contract.mjs delete mode 100644 ui/app/src/DocsPage.tsx delete mode 100644 ui/app/src/app/routes.tsx delete mode 100644 ui/app/src/components/Navigation.tsx delete mode 100644 ui/app/src/constants/landingContent.ts delete mode 100644 ui/app/src/constants/pagePath.ts delete mode 100644 ui/app/src/demo/ConsoleDemo.tsx delete mode 100644 ui/app/src/demo/types.ts delete mode 100644 ui/app/src/docs/constants.ts delete mode 100644 ui/app/src/docs/hooks.ts delete mode 100644 ui/app/src/docs/types.ts delete mode 100644 ui/app/src/docs/utils.tsx delete mode 100644 ui/app/src/features/home/HomePage.tsx delete mode 100644 ui/app/src/features/register/RegisterPage.tsx delete mode 100644 ui/app/src/hooks/useAppNavigation.ts delete mode 100644 ui/app/src/hooks/useClipboard.ts delete mode 100644 ui/app/src/styles.css delete mode 100644 ui/openapi.yaml delete mode 100644 ui/policies/design-system.md delete mode 100644 ui/policies/engineering.md delete mode 100644 ui/sample-app/src/App.tsx delete mode 100644 ui/sample-app/src/components/Navigation.tsx delete mode 100644 ui/sample-app/src/features/login/LoginPage.tsx delete mode 100644 ui/skills/profanity-ui/SKILL.md diff --git a/ui/API_AND_USAGE_FLOWS.md b/ui/API_AND_USAGE_FLOWS.md new file mode 100644 index 0000000..63e77a2 --- /dev/null +++ b/ui/API_AND_USAGE_FLOWS.md @@ -0,0 +1,126 @@ +# API와 사용 플로우 + +기준: 2026-07-17 운영 `https://api.kr-filter.com/openapi.json`과 `main` 브랜치의 인증 정책. + +## 1. 제공 API + +인증 경계는 다음 세 가지로 분리된다. + +| 구분 | Credential | 사용 범위 | +| --- | --- | --- | +| Public | 없음 | SSO 시작·교환·갱신, 클라이언트 등록·복구, 문서, 상태 확인 | +| Login JWT | `Authorization: Bearer {accessToken}` | 로그인 사용자 조회와 향후 `/api/v1/dashboard/**` | +| API Key | `x-api-key: {apiKey}` | 필터, 클라이언트 관리, 단어 관리, 동기화 | + +Login JWT를 API Key 대신 사용할 수 없으며, 현재 외부 API용 OAuth2 access token은 지원하지 않는다. + +### SSO 진입 + +| Method | Path | 역할 | +| --- | --- | --- | +| GET | `/oauth2/authorization/github` | GitHub SSO 시작 | +| GET | `/oauth2/authorization/google` | Google SSO 시작 | +| GET | `/login/oauth2/code/{provider}` | 서버가 처리하는 OAuth callback | + +### 로그인 인증 + +| Method | Path | 인증 | 역할 | +| --- | --- | --- | --- | +| POST | `/api/v1/auth/exchange` | Public | 일회용 로그인 코드를 access token과 refresh cookie로 교환 | +| GET | `/api/v1/auth/csrf` | Public | refresh 요청에 사용할 CSRF token 조회 | +| POST | `/api/v1/auth/refresh` | Refresh cookie + CSRF | access token 갱신과 refresh token rotation | +| GET | `/api/v1/auth/me` | Login JWT | 현재 로그인 사용자 조회 | + +현재 `/api/v1/dashboard/**`는 Login JWT 보호 경로만 예약되어 있고 실제 endpoint는 없다. 로그아웃 endpoint도 없다. + +### API Key 발급과 관리 + +| Method | Path | 인증 | 역할 | +| --- | --- | --- | --- | +| POST | `/api/v1/clients/register` | Public | `name`, `email`, `issuerInfo`, `note`로 신규 API Key 발급 | +| GET | `/api/v1/clients/send-email` | Public | `email` query의 기존 등록 이메일로 인증 코드 발송 | +| PUT | `/api/v1/clients/send-email` | Public | 이메일과 인증 코드를 검증하고 기존 API Key 반환 | +| GET | `/api/v1/clients` | API Key | 현재 클라이언트 정보 조회 | +| POST | `/api/v1/clients/update` | API Key | 발급자 정보와 메모 수정 | +| POST | `/api/v1/clients/reissue` | API Key | API Key 재발급 | +| DELETE | `/api/v1/clients` | API Key | 클라이언트 폐기 | + +`send-email` 두 API는 신규 등록 전 이메일 인증이 아니라 이미 등록된 이메일의 API Key 복구 흐름이다. 현재 SSO 사용자와 API Key 클라이언트를 연결하는 API는 없다. + +### 비속어 필터 + +| Method | Path | 인증 | 역할 | +| --- | --- | --- | --- | +| POST | `/api/v1/filter` | API Key | `QUICK`, `NORMAL`, `FILTER` 모드의 문장 필터링 | +| POST | `/api/v1/filter/advanced` | API Key | `word` query 기반 단일 필터링 | + +`/api/v1/filter`에 `callbackUrl`이 없으면 동기 결과를 반환하고, 값이 있으면 접수 결과를 반환한 뒤 같은 `trackingId` 기준으로 callback을 시도한다. + +### 단어 관리 + +| Method | Path | 인증 | 역할 | +| --- | --- | --- | --- | +| POST | `/api/v1/word/request` | API Key | 단어 추가·제거·수정 요청 | +| POST | `/api/v1/word/accept/{requestId}` | API Key + WRITE 권한 | 단어 변경 요청 승인 | + +### 운영 + +| Method | Path | 인증 | 역할 | +| --- | --- | --- | --- | +| GET | `/api/v1/sync` | API Key + `password` query | 비속어 데이터 수동 동기화 | +| GET | `/api/v1/health` | Public | 상태 확인 | +| GET | `/api/v1/ping` | Public | 연결 확인 | +| GET | `/openapi.json` | Public | 운영 OpenAPI 문서 | +| GET | `/overview.md` | Public | API 개요 문서 | +| GET | `/llms.txt`, `/llm.txt` | Public | LLM용 문서 | + +## 2. 사용 플로우 + +### 사람 로그인 + +1. 사용자가 GitHub 또는 Google 로그인을 선택한다. +2. 브라우저를 `/oauth2/authorization/{provider}`로 이동한다. +3. 서버가 OAuth callback을 처리하고 frontend 로그인 경로의 URL fragment에 60초 수명의 일회용 `code`를 전달한다. +4. UI가 `POST /api/v1/auth/exchange`로 코드를 한 번 교환한다. +5. access token은 응답 body에서 받아 메모리에만 보관하고, refresh token은 `HttpOnly` cookie로 유지한다. +6. UI가 access token으로 `GET /api/v1/auth/me`를 호출해 사용자 정보를 확인한다. +7. access token 갱신이 필요하면 `GET /api/v1/auth/csrf` 후 반환된 header 이름과 token으로 `POST /api/v1/auth/refresh`를 호출한다. +8. 갱신 응답의 새 access token으로 메모리 상태를 교체한다. + +### API Key 신규 발급 + +1. 사용자가 이름 또는 조직명, 이메일, 발급자 정보와 메모를 입력한다. +2. UI가 `POST /api/v1/clients/register`를 호출한다. +3. 반환된 API Key를 한 번 명확하게 노출하고 사용자가 안전하게 복사하도록 한다. +4. 이후 외부 API 호출에는 `x-api-key` 헤더를 사용한다. + +신규 발급 전에 `send-email` 인증을 거치지 않는다. + +### 기존 API Key 복구 + +1. 사용자가 기존 등록 이메일을 입력한다. +2. UI가 `GET /api/v1/clients/send-email?email=...`로 인증 코드를 요청한다. +3. 사용자가 이메일로 받은 코드를 입력한다. +4. UI가 이메일과 코드를 `PUT /api/v1/clients/send-email`로 검증한다. +5. 성공 응답의 기존 API Key를 노출하고 복사할 수 있게 한다. + +### 필터 사용 + +1. 사용자가 API Key, 검사 문장, 처리 모드를 입력한다. +2. UI가 `x-api-key` 헤더와 함께 `POST /api/v1/filter`를 호출한다. +3. `QUICK`은 첫 감지, `NORMAL`은 감지 목록, `FILTER`는 마스킹된 문장을 보여준다. +4. HTTP 상태와 별도로 응답 body의 `status.code`를 성공·실패 판단 기준으로 사용한다. +5. `trackingId`, 감지 목록, 필터링 결과와 오류 상세를 구분해 보여준다. +6. 비동기 모드에서는 `callbackUrl`을 전달하고 접수 상태와 최종 callback 결과를 별도 단계로 취급한다. + +### API Key 관리 + +1. API Key로 `GET /api/v1/clients`를 호출해 현재 정보를 확인한다. +2. 정보 수정은 `/update`, 키 교체는 `/reissue`, 폐기는 `DELETE /api/v1/clients`로 각각 분리한다. +3. 재발급과 폐기는 기존 credential에 영향을 주는 작업이므로 실행 전 확인 단계를 둔다. + +### 단어 변경 요청 + +1. 사용자가 단어, 사유, 심각도와 요청 타입을 입력한다. +2. UI가 `POST /api/v1/word/request`로 요청한다. +3. WRITE 권한을 가진 클라이언트만 request ID를 사용해 `/accept/{requestId}`로 승인한다. diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index d37684c..0000000 --- a/ui/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# profanity-filter-api UI - -`ui/`는 `profanity-filter-api`의 프론트엔드 작업 영역이다. 공개 랜딩, API 문서, 키 발급 화면, 이후 추가될 콘솔성 메뉴를 하나의 React 앱에서 점진적으로 확장한다. - -## Structure - -```text -ui/ -├── README.md -├── openapi.yaml -├── policies/ -│ ├── engineering.md -│ └── design-system.md -├── skills/ -│ └── profanity-ui/SKILL.md -└── app/ - ├── package.json - ├── src/ - └── scripts/ -``` - -## Source Of Truth - -- Engineering policy: `policies/engineering.md` -- Design system policy: `policies/design-system.md` -- Agent workflow: `skills/profanity-ui/SKILL.md` -- Runtime app: `app` -- Navigation registry: `app/src/app/routes.tsx` - -## Current Routes - -- `/`: 서비스 소개 랜딩 -- `/register`: 키 발급 화면 -- `/docs`: API 문서 - -## Commands - -```bash -cd ui/app -npm ci -npm run dev -npm run typecheck -npm run test:policy -npm run test:routing -npm run test:hooks -npm run test:register -npm run test:docs -npm run build -``` - -## Working Rules - -- 새 메뉴는 `APP_ROUTES`에 추가한다. -- route matching은 경계 조건을 테스트한다. -- 브라우저 effect는 custom hook으로 분리한다. -- 화면 변경 전후로 주요 route를 브라우저에서 확인한다. -- 디자인 변경은 `policies/design-system.md`를 먼저 따른다. diff --git a/ui/app/DESIGN_GUIDELINES.md b/ui/app/DESIGN_GUIDELINES.md deleted file mode 100644 index 8cfb603..0000000 --- a/ui/app/DESIGN_GUIDELINES.md +++ /dev/null @@ -1,5 +0,0 @@ -# FE Design Guidelines - -Canonical design system rules live in `../policies/design-system.md`. - -Keep this file as a compatibility pointer for local editors. Do not add product rules here. diff --git a/ui/app/README.md b/ui/app/README.md deleted file mode 100644 index cf1bd6e..0000000 --- a/ui/app/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# profanity-filter-api App - -이 앱은 `profanity-filter-api`의 프론트엔드 런타임이다. 정책과 작업 지침은 상위 `ui/` 디렉터리에 둔다. - -## Routes - -- `/`: 서비스 소개 랜딩 -- `/register`: 키 발급 -- `/docs`: API 문서 - -## Commands - -```bash -npm ci -npm run dev -npm run typecheck -npm run test:policy -npm run test:routing -npm run test:hooks -npm run test:register -npm run test:docs -npm run build -``` - -## Runtime Boundaries - -- `src/app/routes.tsx`: 메뉴와 route source of truth -- `src/hooks/useAppNavigation.ts`: history, popstate, scroll restoration -- `src/hooks/useClipboard.ts`: clipboard success/failure feedback -- `src/docs/*`: OpenAPI와 overview 문서 로딩 -- `src/features/*`: route별 화면 - -## Deployment Shape - -```text -npm run build - -> dist/ - -> Docker image - -> k3s Deployment - -> Service - -> HTTPRoute -``` diff --git a/ui/app/index.html b/ui/app/index.html index 9665b16..93e9d1a 100644 --- a/ui/app/index.html +++ b/ui/app/index.html @@ -3,7 +3,7 @@ - 말조심하세욧 + profanity-filter-api UI
diff --git a/ui/app/nginx.conf b/ui/app/nginx.conf index 1f651fb..6d4bdc7 100644 --- a/ui/app/nginx.conf +++ b/ui/app/nginx.conf @@ -9,12 +9,6 @@ server { try_files $uri $uri/ /index.html; } - location = /overview.md { - default_type text/markdown; - charset utf-8; - try_files /overview.md =404; - } - location = /health { access_log off; return 200 "OK\n"; diff --git a/ui/app/package-lock.json b/ui/app/package-lock.json index 422cb60..9b5dcae 100644 --- a/ui/app/package-lock.json +++ b/ui/app/package-lock.json @@ -1,17 +1,15 @@ { - "name": "profanity-filter-console-ui", + "name": "profanity-filter-ui", "version": "0.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "profanity-filter-console-ui", + "name": "profanity-filter-ui", "version": "0.0.0", "dependencies": { - "@scalar/api-reference-react": "^0.9.47", "react": "^19.2.1", - "react-dom": "^19.2.1", - "yaml": "^2.9.0" + "react-dom": "^19.2.1" }, "devDependencies": { "@types/node": "^26.0.0", @@ -22,69 +20,6 @@ "vite": "^7.3.1" } }, - "node_modules/@ai-sdk/gateway": { - "version": "3.0.13", - "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-3.0.13.tgz", - "integrity": "sha512-g7nE4PFtngOZNZSy1lOPpkC+FAiHxqBJXqyRMEG7NUrEVZlz5goBdtHg1YgWRJIX776JTXAmbOI5JreAKVAsVA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.5", - "@vercel/oidc": "3.1.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/provider": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-3.0.2.tgz", - "integrity": "sha512-HrEmNt/BH/hkQ7zpi2o6N3k1ZR1QTb7z85WYhYygiTxOQuaml4CMtHCWRbric5WPU+RNsYI7r1EpyVQMKO1pYw==", - "license": "Apache-2.0", - "dependencies": { - "json-schema": "^0.4.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@ai-sdk/provider-utils": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-4.0.5.tgz", - "integrity": "sha512-Ow/X/SEkeExTTc1x+nYLB9ZHK2WUId8+9TlkamAx7Tl9vxU+cKzWx2dwjgMHeCN6twrgwkLrrtqckQeO4mxgVA==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider": "3.0.2", - "@standard-schema/spec": "^1.1.0", - "eventsource-parser": "^3.0.6" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/@ai-sdk/vue": { - "version": "3.0.33", - "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-3.0.33.tgz", - "integrity": "sha512-czM9Js3a7f+Eo35gjEYEeJYUoPvMg5Dfi4bOLyDBghLqn0gaVg8yTmTaSuHCg+3K/+1xPjyXd4+2XcQIohWWiQ==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/provider-utils": "4.0.5", - "ai": "6.0.33", - "swrv": "^1.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "vue": "^3.3.4" - } - }, "node_modules/@babel/code-frame": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", @@ -231,6 +166,7 @@ "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" @@ -240,6 +176,7 @@ "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" @@ -273,6 +210,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -354,6 +292,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -363,160 +302,6 @@ "node": ">=6.9.0" } }, - "node_modules/@codemirror/autocomplete": { - "version": "6.20.3", - "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", - "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@codemirror/commands": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.3.tgz", - "integrity": "sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.6.0", - "@codemirror/view": "^6.27.0", - "@lezer/common": "^1.1.0" - } - }, - "node_modules/@codemirror/lang-css": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", - "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.0.2", - "@lezer/css": "^1.1.7" - } - }, - "node_modules/@codemirror/lang-html": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", - "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/lang-css": "^6.0.0", - "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/css": "^1.1.0", - "@lezer/html": "^1.3.12" - } - }, - "node_modules/@codemirror/lang-javascript": { - "version": "6.2.5", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.5.tgz", - "integrity": "sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.6.0", - "@codemirror/lint": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.17.0", - "@lezer/common": "^1.0.0", - "@lezer/javascript": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-json": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", - "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", - "license": "MIT", - "dependencies": { - "@codemirror/language": "^6.0.0", - "@lezer/json": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-xml": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", - "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.4.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", - "@lezer/common": "^1.0.0", - "@lezer/xml": "^1.0.0" - } - }, - "node_modules/@codemirror/lang-yaml": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.3.tgz", - "integrity": "sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.2.0", - "@lezer/lr": "^1.0.0", - "@lezer/yaml": "^1.0.0" - } - }, - "node_modules/@codemirror/language": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.3.tgz", - "integrity": "sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.23.0", - "@lezer/common": "^1.5.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0", - "style-mod": "^4.0.0" - } - }, - "node_modules/@codemirror/lint": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.7.tgz", - "integrity": "sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.42.0", - "crelt": "^1.0.5" - } - }, - "node_modules/@codemirror/state": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.6.0.tgz", - "integrity": "sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==", - "license": "MIT", - "dependencies": { - "@marijn/find-cluster-break": "^1.0.0" - } - }, - "node_modules/@codemirror/view": { - "version": "6.43.1", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.1.tgz", - "integrity": "sha512-+BIjw/AG3tDQ4pJgTLPYdAW25eDE66YsvM4LKyVPgGzVgZ4a9Wj1SRX8kPVKgBDdPt8oHtZ15F0qx7p0oOHdHw==", - "license": "MIT", - "dependencies": { - "@codemirror/state": "^6.6.0", - "crelt": "^1.0.6", - "style-mod": "^4.1.0", - "w3c-keyname": "^2.2.4" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.7", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", @@ -959,125 +744,6 @@ "node": ">=18" } }, - "node_modules/@floating-ui/core": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", - "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/core/node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.6", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", - "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.5", - "@floating-ui/utils": "^0.2.11" - } - }, - "node_modules/@floating-ui/dom/node_modules/@floating-ui/utils": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", - "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", - "license": "MIT" - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@floating-ui/vue": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.9.tgz", - "integrity": "sha512-BfNqNW6KA83Nexspgb9DZuz578R7HT8MZw1CfK9I6Ah4QReNWEJsXWHN+SdmOVLNGmTPDi+fDT535Df5PzMLbQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4", - "@floating-ui/utils": "^0.2.10", - "vue-demi": ">=0.13.0" - } - }, - "node_modules/@floating-ui/vue/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@headlessui/tailwindcss": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz", - "integrity": "sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "tailwindcss": "^3.0 || ^4.0" - } - }, - "node_modules/@headlessui/vue": { - "version": "1.7.23", - "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz", - "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==", - "license": "MIT", - "dependencies": { - "@tanstack/vue-virtual": "^3.0.0-beta.60" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "vue": "^3.2.0" - } - }, - "node_modules/@internationalized/date": { - "version": "3.12.2", - "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.2.tgz", - "integrity": "sha512-FY1Y+H64NDs+HAF6omlnWxm3mEpfgaCSWtL5l551ZZfImA+kGjPFgrnJrGjH6lfmLL0g8Z/mBu1R3kufeCp6Jw==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, - "node_modules/@internationalized/number": { - "version": "3.6.7", - "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.7.tgz", - "integrity": "sha512-3ji1fcrT+FPAK86UqEhB/psHixYo6niWPJtt7+qRaYFynt/BaJG8GhAPimtWUpEiVSTq8ZM8L5psMxGquiB/Vg==", - "license": "Apache-2.0", - "dependencies": { - "@swc/helpers": "^0.5.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1114,6 +780,7 @@ "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": { @@ -1127,128 +794,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@lezer/common": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", - "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", - "license": "MIT" - }, - "node_modules/@lezer/css": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.3.tgz", - "integrity": "sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/highlight": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", - "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.3.0" - } - }, - "node_modules/@lezer/html": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", - "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/javascript": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", - "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.1.3", - "@lezer/lr": "^1.3.0" - } - }, - "node_modules/@lezer/json": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", - "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/lr": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", - "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.0.0" - } - }, - "node_modules/@lezer/xml": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", - "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" - } - }, - "node_modules/@lezer/yaml": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", - "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", - "license": "MIT", - "dependencies": { - "@lezer/common": "^1.2.0", - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.4.0" - } - }, - "node_modules/@marijn/find-cluster-break": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", - "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", - "license": "MIT" - }, - "node_modules/@opentelemetry/api": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", - "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@phosphor-icons/core": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@phosphor-icons/core/-/core-2.1.1.tgz", - "integrity": "sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==", - "license": "MIT" - }, - "node_modules/@replit/codemirror-css-color-picker": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/@replit/codemirror-css-color-picker/-/codemirror-css-color-picker-6.3.0.tgz", - "integrity": "sha512-19biDANghUm7Fz7L1SNMIhK48tagaWuCOHj4oPPxc7hxPGkTVY2lU/jVZ8tsbTKQPVG7BO2CBDzs7CBwb20t4A==", - "license": "MIT", - "peerDependencies": { - "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", @@ -1606,3268 +1151,468 @@ "win32" ] }, - "node_modules/@scalar/agent-chat": { - "version": "0.12.11", - "resolved": "https://registry.npmjs.org/@scalar/agent-chat/-/agent-chat-0.12.11.tgz", - "integrity": "sha512-JX1rFryU6kHHcMzzewr9o4Qe0st/27A8AFvka7v0OWsIoNJHcO9aA5gitedci6eZJNsNSBj6bXpH5VrCU1jAfA==", + "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": { - "@ai-sdk/vue": "3.0.33", - "@scalar/api-client": "3.10.4", - "@scalar/components": "0.27.2", - "@scalar/helpers": "0.8.2", - "@scalar/icons": "0.7.3", - "@scalar/json-magic": "0.12.16", - "@scalar/openapi-types": "0.9.1", - "@scalar/schemas": "0.5.0", - "@scalar/themes": "0.16.0", - "@scalar/types": "0.14.0", - "@scalar/use-toasts": "0.10.2", - "@scalar/validation": "0.6.0", - "@scalar/workspace-store": "0.54.4", - "@vueuse/core": "13.9.0", - "ai": "6.0.33", - "js-base64": "^3.7.8", - "neverpanic": "0.0.8", - "truncate-json": "3.0.1", - "vue": "^3.5.30" - }, - "engines": { - "node": ">=22" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" } }, - "node_modules/@scalar/api-client": { - "version": "3.10.4", - "resolved": "https://registry.npmjs.org/@scalar/api-client/-/api-client-3.10.4.tgz", - "integrity": "sha512-TwmbM6hnzPs1vZUzucczNkPTzxTOtV+gu+TIq8/yEZBYF7GC6+6YerxPrGYjTgifp2UbIYf9GlSvmZv8RzSkIQ==", + "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": { - "@headlessui/tailwindcss": "^0.2.2", - "@headlessui/vue": "1.7.23", - "@scalar/components": "0.27.2", - "@scalar/helpers": "0.8.2", - "@scalar/icons": "0.7.3", - "@scalar/json-magic": "0.12.16", - "@scalar/oas-utils": "0.18.5", - "@scalar/openapi-types": "0.9.1", - "@scalar/sidebar": "0.9.23", - "@scalar/snippetz": "0.9.17", - "@scalar/themes": "0.16.0", - "@scalar/typebox": "^0.1.3", - "@scalar/types": "0.14.0", - "@scalar/use-codemirror": "0.14.12", - "@scalar/use-hooks": "0.4.7", - "@scalar/use-toasts": "0.10.2", - "@scalar/workspace-store": "0.54.4", - "@types/har-format": "^1.2.16", - "@vueuse/core": "13.9.0", - "@vueuse/integrations": "13.9.0", - "focus-trap": "^7.8.0", - "fuse.js": "^7.1.0", - "js-base64": "^3.7.8", - "jsonc-parser": "3.3.1", - "nanoid": "^5.1.6", - "pretty-ms": "^9.3.0", - "radix-vue": "^1.9.17", - "set-cookie-parser": "3.1.0", - "vue": "^3.5.30", - "yaml": "^2.8.3", - "zod": "^4.3.5" - }, - "engines": { - "node": ">=22" + "@babel/types": "^7.0.0" } }, - "node_modules/@scalar/api-client/node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "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", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" } }, - "node_modules/@scalar/api-reference": { - "version": "1.60.0", - "resolved": "https://registry.npmjs.org/@scalar/api-reference/-/api-reference-1.60.0.tgz", - "integrity": "sha512-godMRo3E/m6uZtm0zVfDa5xCg1IGD+eEt4uVWzR0v62mbm2O70ZWY9l+ondg8CrPQFsCdtWpWW+nYdzvp1tWTw==", + "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": { - "@headlessui/vue": "1.7.23", - "@scalar/agent-chat": "0.12.11", - "@scalar/api-client": "3.10.4", - "@scalar/code-highlight": "0.3.6", - "@scalar/components": "0.27.2", - "@scalar/helpers": "0.8.2", - "@scalar/icons": "0.7.3", - "@scalar/oas-utils": "0.18.5", - "@scalar/schemas": "0.5.0", - "@scalar/sidebar": "0.9.23", - "@scalar/snippetz": "0.9.17", - "@scalar/themes": "0.16.0", - "@scalar/types": "0.14.0", - "@scalar/use-hooks": "0.4.7", - "@scalar/use-toasts": "0.10.2", - "@scalar/validation": "0.6.0", - "@scalar/workspace-store": "0.54.4", - "@unhead/vue": "^2.1.4", - "@vueuse/core": "13.9.0", - "fuse.js": "^7.1.0", - "microdiff": "^1.5.0", - "nanoid": "^5.1.6", - "vue": "^3.5.30", - "yaml": "^2.8.3" - }, - "engines": { - "node": ">=22" + "@babel/types": "^7.28.2" } }, - "node_modules/@scalar/api-reference-react": { - "version": "0.9.47", - "resolved": "https://registry.npmjs.org/@scalar/api-reference-react/-/api-reference-react-0.9.47.tgz", - "integrity": "sha512-2vT7WCgU2AY2wrB+Xit09b76h5uQsQ/zP68LyTKSPYG8FIYeVsuGkrpIJGbtY/f57d3rQCqrbrZjLb30VzuWKw==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", + "dev": true, "license": "MIT", "dependencies": { - "@scalar/api-reference": "1.60.0", - "@scalar/types": "0.14.0" - }, - "engines": { - "node": ">=22" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" + "undici-types": "~8.3.0" } }, - "node_modules/@scalar/api-reference/node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@scalar/code-highlight": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@scalar/code-highlight/-/code-highlight-0.3.6.tgz", - "integrity": "sha512-G/EtLF6a3IFGfIv5b7Uj5v/yXFY9+ktViVkfAkSoWJdHNBNk0S/Rb2OgRq24kp0D2TDXhLYF3E9OSHzquS7WqQ==", - "license": "MIT", - "dependencies": { - "hast-util-to-text": "^4.0.2", - "highlight.js": "^11.11.1", - "lowlight": "^3.3.0", - "rehype-external-links": "^3.0.0", - "rehype-format": "^5.0.1", - "rehype-parse": "^9.0.1", - "rehype-raw": "^7.0.0", - "rehype-sanitize": "^6.0.0", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "remark-stringify": "^11.0.0", - "unified": "^11.0.5", - "unist-util-visit": "^5.1.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/components": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@scalar/components/-/components-0.27.2.tgz", - "integrity": "sha512-cwIf042ehbD4dZWUIIlXZp0VWvaH7jznj/gpuLEqtuEDla5MrhTWYWj20aufpTpCeGexf1mqgouaWHUZ7lYByQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "0.2.10", - "@floating-ui/vue": "1.1.9", - "@headlessui/tailwindcss": "^0.2.2", - "@headlessui/vue": "1.7.23", - "@scalar/code-highlight": "0.3.6", - "@scalar/helpers": "0.8.2", - "@scalar/icons": "0.7.3", - "@scalar/themes": "0.16.0", - "@scalar/use-hooks": "0.4.7", - "@vueuse/core": "13.9.0", - "cva": "1.0.0-beta.4", - "radix-vue": "^1.9.17", - "vue": "^3.5.30", - "vue-component-type-helpers": "^3.2.6" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/helpers": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@scalar/helpers/-/helpers-0.8.2.tgz", - "integrity": "sha512-qNbqUjSB3S4Gr4A0oANcm5G1Ip+EqBxICYKhe9YzmnaBpbmW6shxqpiivApTvvuDf+uIhR3uMwWyVQbYcGLsxA==", - "license": "MIT", - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/icons": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/@scalar/icons/-/icons-0.7.3.tgz", - "integrity": "sha512-5uSUvumj6yJEAZT7/MKpgrkNl76waDXUpu0kUBBFJ83GhZirBIK+Z9SktShEvJT0+rk/j9Zaer3BHoEhYwAEBQ==", - "license": "MIT", - "dependencies": { - "@phosphor-icons/core": "^2.1.1", - "@types/node": "^24.1.0", - "chalk": "^5.6.2", - "vue": "^3.5.30" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/icons/node_modules/@types/node": { - "version": "24.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", - "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@scalar/icons/node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT" - }, - "node_modules/@scalar/json-magic": { - "version": "0.12.16", - "resolved": "https://registry.npmjs.org/@scalar/json-magic/-/json-magic-0.12.16.tgz", - "integrity": "sha512-w8cDbZhHCzmIblWx92IVWoAXsbI4Fz3m++jiBANTSO1hgphF6UqEPQiCt3wnMPaxaanjMQxjS/iBk1UGXR2EGA==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "pathe": "^2.0.3", - "yaml": "^2.8.3" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/oas-utils": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/@scalar/oas-utils/-/oas-utils-0.18.5.tgz", - "integrity": "sha512-GLL0DPEaKkIrDmkwY83RDnCKQRuzw1u6MjGlEX5iVs/LDjQRvWnkAz9A5iUrH/YHr+ike5NIl3/WzaEWiz6x6A==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "@scalar/themes": "0.16.0", - "@scalar/types": "0.14.0", - "@scalar/workspace-store": "0.54.4", - "flatted": "^3.4.0", - "vue": "^3.5.30", - "yaml": "^2.8.3" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/openapi-types": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/@scalar/openapi-types/-/openapi-types-0.9.1.tgz", - "integrity": "sha512-gkGhSkxSzADaBiNg+ZAbJuwj+ZUmzP2Pg9CWZ7ZP+0fck2WjPeDDM7aAbouAm0aQQMF9xBjSPXSA9a/qTHYaTw==", - "license": "MIT", - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/openapi-upgrader": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/@scalar/openapi-upgrader/-/openapi-upgrader-0.2.9.tgz", - "integrity": "sha512-D5b0rGLLZgmkO9mdW2j/ND1KBlH1u3RCpr87HPxv9P9ZSr6PtM5iLqFOJq0ACiaHjY2mikCrxgDmnUEhTzRpHQ==", - "license": "MIT", - "dependencies": { - "@scalar/openapi-types": "0.9.1" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/schemas": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@scalar/schemas/-/schemas-0.5.0.tgz", - "integrity": "sha512-aV0PWqFpQft9a6d4Z6HOTHKDaA+K4AquxSPfBDI1c5f6lMe5l3zViww/iBQA/Yw+V1+Y2iwSVzhuxaktC1UiMg==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "@scalar/validation": "0.6.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/sidebar": { - "version": "0.9.23", - "resolved": "https://registry.npmjs.org/@scalar/sidebar/-/sidebar-0.9.23.tgz", - "integrity": "sha512-2qC5vh1BGhnARF9/psKr5jPbwWyg4RIfflNmh7q/jHxNyeCrHGr1yyUiV8TsWmURR5ZNMiyUPzHaio4hdAcZNw==", - "license": "MIT", - "dependencies": { - "@scalar/components": "0.27.2", - "@scalar/helpers": "0.8.2", - "@scalar/icons": "0.7.3", - "@scalar/themes": "0.16.0", - "@scalar/use-hooks": "0.4.7", - "@scalar/workspace-store": "0.54.4", - "vue": "^3.5.30" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/snippetz": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/@scalar/snippetz/-/snippetz-0.9.17.tgz", - "integrity": "sha512-BC8ai3E9O5TbJwdGTounh9T6BVSTuYJ/BObZ6wfmXxMK/AL7bDfYED5xBqqzucAGpjVUaN0u796w5EHB82ryrQ==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "@scalar/types": "0.14.0", - "js-base64": "^3.7.8", - "stringify-object": "^6.0.0" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/themes": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@scalar/themes/-/themes-0.16.0.tgz", - "integrity": "sha512-xcd5cdouNKGGI+D3v4SVm5WVLjiLDPT0jBiZrNA6m/h2yDaIeFoXMDrH61aj962Y+P7KR33/2b0xLHgws5Os8Q==", - "license": "MIT", - "dependencies": { - "nanoid": "^5.1.6" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/themes/node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@scalar/typebox": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@scalar/typebox/-/typebox-0.1.3.tgz", - "integrity": "sha512-lU055AUccECZMIfGA0z/C1StYmboAYIPJLDFBzOO81yXBi35Pxdq+I4fWX6iUZ8qcoHneiLGk9jAUM1rA93iEg==", - "license": "MIT" - }, - "node_modules/@scalar/types": { - "version": "0.14.0", - "resolved": "https://registry.npmjs.org/@scalar/types/-/types-0.14.0.tgz", - "integrity": "sha512-aS1FvHbKhBC51mWqXmhkPe6lmhCd9+u//DQ1YTiCWOz4/fXAdWfwHvW9UZ7AzzcGwGiCo9diE81xV+UvSI1qIg==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "nanoid": "^5.1.6", - "type-fest": "^5.3.1", - "zod": "^4.3.5" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/types/node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, - "engines": { - "node": "^18 || >=20" - } - }, - "node_modules/@scalar/use-codemirror": { - "version": "0.14.12", - "resolved": "https://registry.npmjs.org/@scalar/use-codemirror/-/use-codemirror-0.14.12.tgz", - "integrity": "sha512-m+0tmVOHZVAybbcNEizR3m9RYOLtU59AStRkk29J5qaW4J+tsjrz4BDMceeTpoVawL/lxp2vDvQR+w/gKC3miQ==", - "license": "MIT", - "dependencies": { - "@codemirror/autocomplete": "^6.18.3", - "@codemirror/commands": "^6.7.1", - "@codemirror/lang-css": "^6.3.1", - "@codemirror/lang-html": "^6.4.8", - "@codemirror/lang-json": "^6.0.0", - "@codemirror/lang-xml": "^6.0.0", - "@codemirror/lang-yaml": "^6.1.2", - "@codemirror/language": "^6.10.7", - "@codemirror/lint": "^6.8.4", - "@codemirror/state": "^6.5.0", - "@codemirror/view": "^6.35.3", - "@lezer/common": "^1.2.3", - "@lezer/highlight": "^1.2.1", - "@replit/codemirror-css-color-picker": "^6.3.0", - "vue": "^3.5.30" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/use-hooks": { - "version": "0.4.7", - "resolved": "https://registry.npmjs.org/@scalar/use-hooks/-/use-hooks-0.4.7.tgz", - "integrity": "sha512-8zajxhnKMJuO1HF36y8TeVuSIol2pueYdRvITZTEY8TuRbnwxrvJZk25II7YpxGMORAEDr0bwNF88Dr+QUfw1w==", - "license": "MIT", - "dependencies": { - "@scalar/use-toasts": "0.10.2", - "@scalar/validation": "0.6.0", - "@vueuse/core": "13.9.0", - "cva": "1.0.0-beta.4", - "tailwind-merge": "3.5.0", - "vue": "^3.5.30" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/use-toasts": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@scalar/use-toasts/-/use-toasts-0.10.2.tgz", - "integrity": "sha512-1iHQFbDXv0YQRp13aa63S5EcTJ5K8T0ocnLxk+nziloPrLjKt6jdRt6vOHsLSv5sm9kFKcVKNQTQgialmKCOGA==", - "license": "MIT", - "dependencies": { - "vue": "^3.5.30", - "vue-sonner": "^1.3.2" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@scalar/validation": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@scalar/validation/-/validation-0.6.0.tgz", - "integrity": "sha512-tpmmG+/xRE2Kn9RpflU3AIyZv08v10+E1ZrJCx7z6+/91zHVxy0M73kC1LT4/8PbYNt85ywyC8+n+D99JdMcGA==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/@scalar/workspace-store": { - "version": "0.54.4", - "resolved": "https://registry.npmjs.org/@scalar/workspace-store/-/workspace-store-0.54.4.tgz", - "integrity": "sha512-82ffXzHc/TDR6mr9BSzzRkxOaVG8mHQlWLKnPiv8GiFNETgBTQKv9nELEZQpLcG02cJVgVytl+X/TjgZHRDKAw==", - "license": "MIT", - "dependencies": { - "@scalar/helpers": "0.8.2", - "@scalar/json-magic": "0.12.16", - "@scalar/openapi-upgrader": "0.2.9", - "@scalar/schemas": "0.5.0", - "@scalar/snippetz": "0.9.17", - "@scalar/typebox": "0.1.3", - "@scalar/types": "0.14.0", - "@scalar/validation": "0.6.0", - "js-base64": "^3.7.8", - "type-fest": "^5.3.1", - "vue": "^3.5.30", - "yaml": "^2.8.3" - }, - "engines": { - "node": ">=22" - } - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, - "node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@tanstack/virtual-core": { - "version": "3.17.1", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.17.1.tgz", - "integrity": "sha512-VZyW2Uiml5tmBZwPGrSD3Sz73OxzljQMCmzYHsUTPEuTsERf5xwa+uWb01xEzkz3ZSYTjj8NEb/mKHvgKxyZdA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/vue-virtual": { - "version": "3.13.29", - "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.29.tgz", - "integrity": "sha512-MWb9tNHjpar3sP34b8+3A4I5j9akveoPXIYqqp7/ipyWd49a/kso+1S1LqEmAVR/+g/k1WWTJC4ktvdCGWgXYQ==", - "license": "MIT", - "dependencies": { - "@tanstack/virtual-core": "3.17.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "vue": "^2.7.0 || ^3.0.0" - } - }, - "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/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/har-format": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz", - "integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==", - "license": "MIT" - }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", - "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "license": "MIT" - }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "license": "MIT" - }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz", - "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==", - "license": "ISC" - }, - "node_modules/@unhead/vue": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-2.1.15.tgz", - "integrity": "sha512-SSByXfEjhzPn8gXdEdgpYqpLMPSkLUH2HVE0GxZfOtNsJ0GgOHQs0g9T67ZZ1z0kTELLKdtOtYrzrbv9+ffF7g==", - "license": "MIT", - "dependencies": { - "hookable": "^6.0.1", - "unhead": "2.1.15" - }, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - }, - "peerDependencies": { - "vue": ">=3.5.18" - } - }, - "node_modules/@vercel/oidc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", - "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", - "license": "Apache-2.0", - "engines": { - "node": ">= 20" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", - "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@vue/compiler-core": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", - "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/shared": "3.5.38", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", - "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.38", - "@vue/shared": "3.5.38" - } - }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", - "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@vue/compiler-core": "3.5.38", - "@vue/compiler-dom": "3.5.38", - "@vue/compiler-ssr": "3.5.38", - "@vue/shared": "3.5.38", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.15", - "source-map-js": "^1.2.1" - } - }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", - "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.38", - "@vue/shared": "3.5.38" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", - "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.38" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", - "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.38", - "@vue/shared": "3.5.38" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", - "integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.38", - "@vue/runtime-core": "3.5.38", - "@vue/shared": "3.5.38", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", - "integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.38", - "@vue/shared": "3.5.38" - }, - "peerDependencies": { - "vue": "3.5.38" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", - "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-13.9.0.tgz", - "integrity": "sha512-ts3regBQyURfCE2BcytLqzm8+MmLlo5Ln/KLoxDVcsZ2gzIwVNnQpQOL/UKV8alUqjSZOlpFZcRNsLRqj+OzyA==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "13.9.0", - "@vueuse/shared": "13.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/@vueuse/integrations": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-13.9.0.tgz", - "integrity": "sha512-SDobKBbPIOe0cVL7QxMzGkuUGHvWTdihi9zOrrWaWUgFKe15cwEcwfWmgrcNzjT6kHnNmWuTajPHoIzUjYNYYQ==", - "license": "MIT", - "dependencies": { - "@vueuse/core": "13.9.0", - "@vueuse/shared": "13.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^5", - "drauu": "^0.4", - "focus-trap": "^7", - "fuse.js": "^7", - "idb-keyval": "^6", - "jwt-decode": "^4", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^7 || ^8", - "vue": "^3.5.0" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-13.9.0.tgz", - "integrity": "sha512-1AFRvuiGphfF7yWixZa0KwjYH8ulyjDCC0aFgrGRz8+P4kvDFSdXLVfTk5xAN9wEuD1J6z4/myMoYbnHoX07zg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "13.9.0", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-13.9.0.tgz", - "integrity": "sha512-e89uuTLMh0U5cZ9iDpEI2senqPGfbPRTHM/0AaQkcxnpqjkZqDYP8rpfm7edOz8s+pOCOROEy1PIveSW8+fL5g==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "vue": "^3.5.0" - } - }, - "node_modules/ai": { - "version": "6.0.33", - "resolved": "https://registry.npmjs.org/ai/-/ai-6.0.33.tgz", - "integrity": "sha512-bVokbmy2E2QF6Efl+5hOJx5MRWoacZ/CZY/y1E+VcewknvGlgaiCzMu8Xgddz6ArFJjiMFNUPHKxAhIePE4rmg==", - "license": "Apache-2.0", - "dependencies": { - "@ai-sdk/gateway": "3.0.13", - "@ai-sdk/provider": "3.0.2", - "@ai-sdk/provider-utils": "4.0.5", - "@opentelemetry/api": "1.9.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "zod": "^3.25.76 || ^4.1.8" - } - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.38", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", - "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/convert-hrtime": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", - "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/cva": { - "version": "1.0.0-beta.4", - "resolved": "https://registry.npmjs.org/cva/-/cva-1.0.0-beta.4.tgz", - "integrity": "sha512-F/JS9hScapq4DBVQXcK85l9U91M6ePeXoBMSp7vypzShoefUBxjQTo3g3935PUHgQd+IW77DjbPRIxugy4/GCQ==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - }, - "peerDependencies": { - "typescript": ">= 4.5.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decode-named-character-reference": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", - "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", - "license": "MIT", - "dependencies": { - "character-entities": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.376", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", - "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", - "dev": true, - "license": "ISC" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "license": "MIT" - }, - "node_modules/eventsource-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", - "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "license": "ISC" - }, - "node_modules/focus-trap": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", - "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", - "license": "MIT", - "dependencies": { - "tabbable": "^6.4.0" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-timeout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", - "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fuse.js": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.4.2.tgz", - "integrity": "sha512-LVbzjD4WA6UP5B1UnP8wuaXJiLnqMdM/E4fiJXTJ5haJ5b/MBNsK29h2fm6swEoQaVQjvYFWKLE2RanyZIoRVQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/krisk" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-own-enumerable-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-own-enumerable-keys/-/get-own-enumerable-keys-1.0.0.tgz", - "integrity": "sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/guess-json-indent": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/guess-json-indent/-/guess-json-indent-3.0.1.tgz", - "integrity": "sha512-LWZ3Vr8BG7DHE3TzPYFqkhjNRw4vYgFSsv2nfMuHklAlOfiy54/EwiDQuQfFVLxENCVv20wpbjfTayooQHrEhQ==", - "license": "MIT", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/hast-util-embedded": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", - "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-format": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", - "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-minify-whitespace": "^1.0.0", - "hast-util-phrasing": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "html-whitespace-sensitive-tag-names": "^3.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-html": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", - "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.1.0", - "hast-util-from-parse5": "^8.0.0", - "parse5": "^7.0.0", - "vfile": "^6.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-has-property": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", - "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-body-ok-link": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", - "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-is-element": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", - "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-minify-whitespace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", - "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-phrasing": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", - "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-embedded": "^3.0.0", - "hast-util-has-property": "^3.0.0", - "hast-util-is-body-ok-link": "^3.0.0", - "hast-util-is-element": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-sanitize": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.2.tgz", - "integrity": "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "unist-util-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", - "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-text": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", - "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "hast-util-is-element": "^3.0.0", - "unist-util-find-after": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/highlight.js": { - "version": "11.11.1", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", - "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/hookable": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", - "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-whitespace-sensitive-tag-names": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", - "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/identifier-regex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.0.1.tgz", - "integrity": "sha512-ZrYyM0sozNPZlvBvE7Oq9Bn44n0qKGrYu5sQ0JzMUnjIhpgWYE2JB6aBoFwEYdPjqj7jPyxXTMJiHDOxDfd8yw==", - "license": "MIT", - "dependencies": { - "reserved-identifiers": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-absolute-url": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-4.0.1.tgz", - "integrity": "sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-identifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.0.1.tgz", - "integrity": "sha512-HQ5v4rEJ7REUV54bCd2l5FaD299SGDEn2UPoVXaTHAyGviLq2menVUD2udi3trQ32uvB6LdAh/0ck2EuizrtpA==", - "license": "MIT", - "dependencies": { - "identifier-regex": "^1.0.0", - "super-regex": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-3.0.0.tgz", - "integrity": "sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-obj": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-regexp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-3.1.0.tgz", - "integrity": "sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "license": "BSD-3-Clause" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", - "license": "MIT" - }, - "node_modules/longest-streak": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", - "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lowlight": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz", - "integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "devlop": "^1.0.0", - "highlight.js": "~11.11.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/make-asynchronous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", - "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", - "license": "MIT", - "dependencies": { - "p-event": "^6.0.0", - "type-fest": "^4.6.0", - "web-worker": "^1.5.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-asynchronous/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-from-markdown": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", - "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark": "^4.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-phrasing": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", - "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-markdown": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", - "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "longest-streak": "^3.0.0", - "mdast-util-phrasing": "^4.0.0", - "mdast-util-to-string": "^4.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-decode-string": "^2.0.0", - "unist-util-visit": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-to-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", - "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/microdiff": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/microdiff/-/microdiff-1.5.0.tgz", - "integrity": "sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==", - "license": "MIT" - }, - "node_modules/micromark": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", - "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/debug": "^4.0.0", - "debug": "^4.0.0", - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", - "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-destination": "^2.0.0", - "micromark-factory-label": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-title": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-html-tag-name": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-subtokenize": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-factory-destination": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", - "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", - "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", - "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", - "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-chunked": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", - "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", - "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-combine-extensions": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", - "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-chunked": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-numeric-character-reference": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", - "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", - "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "decode-named-character-reference": "^1.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-decode-numeric-character-reference": "^2.0.0", - "micromark-util-symbol": "^2.0.0" - } - }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-html-tag-name": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", - "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-normalize-identifier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", - "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-symbol": "^2.0.0" + "csstype": "^3.2.2" } }, - "node_modules/micromark-util-resolve-all": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", - "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" + "peerDependencies": { + "@types/react": "^19.2.0" } }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/@vitejs/plugin-react": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", + "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, "license": "MIT", "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "@babel/core": "^7.29.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-rc.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/micromark-util-subtokenize": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", - "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/baseline-browser-mapping": { + "version": "2.10.38", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.38.tgz", + "integrity": "sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, "funding": [ { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" }, { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.13", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", - "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", - "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/neverpanic": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/neverpanic/-/neverpanic-0.0.8.tgz", - "integrity": "sha512-vVdkelrLxaow/fdWDumzNBO+jwm6X8bxeLJc34THtpj70u0C5QBkcV6CRCu2X726km7XD45N0A3QtYCla4RvKw==", - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.48", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", - "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/p-event": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", - "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", - "license": "MIT", "dependencies": { - "p-timeout": "^6.1.2" - }, - "engines": { - "node": ">=16.17" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", - "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", - "license": "MIT", - "engines": { - "node": ">=14.16" + "bin": { + "browserslist": "cli.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-ms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", - "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "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==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "funding": [ { "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.12", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } + "license": "CC-BY-4.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==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" }, - "node_modules/pretty-ms": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "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": { - "parse-ms": "^4.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/property-information": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", - "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/radix-vue": { - "version": "1.9.17", - "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.17.tgz", - "integrity": "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.6.7", - "@floating-ui/vue": "^1.1.0", - "@internationalized/date": "^3.5.4", - "@internationalized/number": "^3.5.3", - "@tanstack/vue-virtual": "^3.8.1", - "@vueuse/core": "^10.11.0", - "@vueuse/shared": "^10.11.0", - "aria-hidden": "^1.2.4", - "defu": "^6.1.4", - "fast-deep-equal": "^3.1.3", - "nanoid": "^5.0.7" + "node": ">=6.0" }, - "peerDependencies": { - "vue": ">= 3.2.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/radix-vue/node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/radix-vue/node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } + "node_modules/electron-to-chromium": { + "version": "1.5.376", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.376.tgz", + "integrity": "sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==", + "dev": true, + "license": "ISC" }, - "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/radix-vue/node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, - "node_modules/radix-vue/node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "engines": { + "node": ">=6" } }, - "node_modules/radix-vue/node_modules/@vueuse/shared/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, + "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", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" + "node": ">=12.0.0" }, "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "@vue/composition-api": { + "picomatch": { "optional": true } } }, - "node_modules/radix-vue/node_modules/nanoid": { - "version": "5.1.14", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.14.tgz", - "integrity": "sha512-5c8l8kVzqpnDPaicbEop/fV0Q1w16FmbWtVhMqugTozAwYdlIQojWH5a/M7UfziFmGdQRrUdV+EPzc9Xng3VAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.js" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": "^18 || >=20" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/react": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", - "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=6.9.0" } }, - "node_modules/react-dom": { - "version": "19.2.7", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", - "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.7" - } + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" }, - "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==", + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/rehype-external-links": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rehype-external-links/-/rehype-external-links-3.0.0.tgz", - "integrity": "sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==", + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-is-element": "^3.0.0", - "is-absolute-url": "^4.0.0", - "space-separated-tokens": "^2.0.0", - "unist-util-visit": "^5.0.0" + "bin": { + "json5": "lib/cli.js" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=6" } }, - "node_modules/rehype-format": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", - "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", - "license": "MIT", + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-format": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "yallist": "^3.0.2" } }, - "node_modules/rehype-parse": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", - "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-from-html": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "node_modules/nanoid": { + "version": "3.3.13", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.13.tgz", + "integrity": "sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" + "bin": { + "nanoid": "bin/nanoid.cjs" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/rehype-sanitize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-sanitize/-/rehype-sanitize-6.0.0.tgz", - "integrity": "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==", + "node_modules/node-releases": { + "version": "2.0.48", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.48.tgz", + "integrity": "sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-sanitize": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=18" } }, - "node_modules/rehype-stringify": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", - "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-to-html": "^9.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } + "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/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/remark-parse": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", - "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unified": "^11.0.0" + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": "^10 || ^12 || >=14" } }, - "node_modules/remark-rehype": { - "version": "11.1.2", - "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", - "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", "license": "MIT", "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" + "scheduler": "^0.27.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "peerDependencies": { + "react": "^19.2.7" } }, - "node_modules/reserved-identifiers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", - "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "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": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, "node_modules/rollup": { @@ -4931,163 +1676,16 @@ "semver": "bin/semver.js" } }, - "node_modules/set-cookie-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", - "integrity": "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==", - "license": "MIT" - }, "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/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/string-byte-length": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-byte-length/-/string-byte-length-3.0.1.tgz", - "integrity": "sha512-yJ8vP0HMwZ54CcA8S8mKoXbkezpZHANFtmafFo8lGxZThCQcAwRHjdFabuSLgOzxj9OFJcmssmiAvmcOK4O2Hw==", - "license": "MIT", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/string-byte-slice": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/string-byte-slice/-/string-byte-slice-3.0.1.tgz", - "integrity": "sha512-GWv2K4lYyd2+AhmKH3BV+OVx62xDX+99rSLfKpaqFiQU7uOMaUY1tDjdrRD4gsrCr9lTyjMgjna7tZcCOw+Smg==", - "license": "MIT", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/stringify-object": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-6.0.0.tgz", - "integrity": "sha512-6f94vIED6vmJJfh3lyVsVWxCYSfI5uM+16ntED/Ql37XIyV6kj0mRAAiTeMMc/QLYIaizC3bUprQ8pQnDDrKfA==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-keys": "^1.0.0", - "is-identifier": "^1.0.1", - "is-obj": "^3.0.0", - "is-regexp": "^3.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/style-mod": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", - "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", - "license": "MIT" - }, - "node_modules/super-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", - "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", - "license": "MIT", - "dependencies": { - "function-timeout": "^1.0.1", - "make-asynchronous": "^1.0.1", - "time-span": "^5.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/swrv": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.2.0.tgz", - "integrity": "sha512-lH/g4UcNyj+7lzK4eRGT4C68Q4EhQ6JtM9otPRIASfhhzfLWtbZPHcMuhuba7S9YVYuxkMUGImwMyGpfbkH07A==", - "license": "Apache-2.0", - "peerDependencies": { - "vue": ">=3.2.26 < 4" - } - }, - "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", - "license": "MIT" - }, - "node_modules/tagged-tag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/tailwind-merge": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz", - "integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.1.tgz", - "integrity": "sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==", - "license": "MIT", - "peer": true - }, - "node_modules/time-span": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", - "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", - "license": "MIT", - "dependencies": { - "convert-hrtime": "^5.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -5105,66 +1703,11 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/trough": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", - "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/truncate-json": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/truncate-json/-/truncate-json-3.0.1.tgz", - "integrity": "sha512-QVsbr1WhGLq2F0oDyYbqtOXcf3gcnL8C9H5EX8bBwAr8ZWvWGJzukpPrDrWgJMrNtgDbo74BIjI4kJu3q2xQWw==", - "license": "MIT", - "dependencies": { - "guess-json-indent": "^3.0.1", - "string-byte-length": "^3.0.1", - "string-byte-slice": "^3.0.1" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-fest": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz", - "integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==", - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -5181,119 +1724,6 @@ "dev": true, "license": "MIT" }, - "node_modules/unhead": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/unhead/-/unhead-2.1.15.tgz", - "integrity": "sha512-MCt5T90mCWyr3Z6pUCdM9lVRXoMoVBlL7z7U4CYVIiaDiuzad/UCfLuMqz5MeNmpZUgoBCQnrucJimU7EZR+XA==", - "license": "MIT", - "dependencies": { - "hookable": "^6.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/harlan-zw" - } - }, - "node_modules/unified": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", - "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "bail": "^2.0.0", - "devlop": "^1.0.0", - "extend": "^3.0.0", - "is-plain-obj": "^4.0.0", - "trough": "^2.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-find-after": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", - "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -5325,48 +1755,6 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vite": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.5.tgz", @@ -5442,101 +1830,12 @@ } } }, - "node_modules/vue": { - "version": "3.5.38", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", - "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.38", - "@vue/compiler-sfc": "3.5.38", - "@vue/runtime-dom": "3.5.38", - "@vue/server-renderer": "3.5.38", - "@vue/shared": "3.5.38" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/vue-component-type-helpers": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.5.tgz", - "integrity": "sha512-Fe1jyPJoUGpJOYKOri44jduR7My4yYINOMJISuMAbmrs+L5LbIDUc8NTWZYY3EJLK0yPLuCmcd5zoCsE4k2/KA==", - "license": "MIT" - }, - "node_modules/vue-sonner": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/vue-sonner/-/vue-sonner-1.3.2.tgz", - "integrity": "sha512-UbZ48E9VIya3ToiRHAZUbodKute/z/M1iT8/3fU8zEbwBRE11AKuHikssv18LMk2gTTr6eMQT4qf6JoLHWuj/A==", - "license": "MIT" - }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/web-worker": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", - "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", - "license": "Apache-2.0" - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } } } } diff --git a/ui/app/package.json b/ui/app/package.json index 27fdc33..0c0c92b 100644 --- a/ui/app/package.json +++ b/ui/app/package.json @@ -1,5 +1,5 @@ { - "name": "profanity-filter-console-ui", + "name": "profanity-filter-ui", "private": true, "version": "0.0.0", "type": "module", @@ -7,18 +7,11 @@ "dev": "vite", "build": "tsc --noEmit && vite build", "preview": "vite preview", - "test:register": "node scripts/check-register-flow.mjs", - "test:docs": "node scripts/check-docs-layout.mjs", - "test:hooks": "node scripts/check-hooks-contract.mjs", - "test:policy": "node scripts/check-policy-docs.mjs", - "test:routing": "node scripts/check-routing-contract.mjs", "typecheck": "tsc --noEmit" }, "dependencies": { - "@scalar/api-reference-react": "^0.9.47", "react": "^19.2.1", - "react-dom": "^19.2.1", - "yaml": "^2.9.0" + "react-dom": "^19.2.1" }, "devDependencies": { "@types/node": "^26.0.0", diff --git a/ui/app/public/overview.md b/ui/app/public/overview.md deleted file mode 100644 index 99c5050..0000000 --- a/ui/app/public/overview.md +++ /dev/null @@ -1,163 +0,0 @@ -한국어 비속어 필터링 API는 문장 안의 부적절한 표현을 감지하고, 필요한 경우 검출된 단어를 마스킹해 반환합니다. -포트폴리오, 학습용 프로젝트, 비영리 서비스처럼 기본적인 댓글·게시글 검수가 필요한 환경에서 빠르게 붙여 쓸 수 있도록 설계했습니다. - -## 시작하기 - -1. 클라이언트 등록 API로 API Key를 발급합니다. -2. 인증이 필요한 요청은 `x-api-key` 헤더에 발급받은 API Key를 포함합니다. -3. `/api/v1/filter`에 검사할 `text`와 처리 방식인 `mode`를 전달합니다. -4. 비동기 처리가 필요하면 `callbackUrl`을 함께 전달합니다. - -## 주요 기능 - -- 문장 안의 한국어·영어 비속어를 검출합니다. -- 검출 결과를 목록으로 받거나, 검출된 단어를 `*`로 마스킹한 문장을 받을 수 있습니다. -- `callbackUrl`을 사용해 긴 처리 흐름을 비동기 방식으로 연결할 수 있습니다. -- 응답은 KISO 이용자 보호 시스템과 유사한 `status.code` 중심의 형식을 따릅니다. - -## 처리 모드 - -- `QUICK`: 원색적인 표현을 빠르게 확인합니다. -- `NORMAL`: 등록된 비속어 데이터를 기준으로 감지된 단어 목록을 반환합니다. -- `FILTER`: 감지된 단어를 마스킹한 문장을 반환합니다. - -## 비동기 처리 - -`/api/v1/filter` 요청에 `callbackUrl`을 포함하면 서버는 요청을 접수한 뒤 같은 `trackingId`로 비동기 결과 전달을 시도합니다. -즉시 결과가 필요한 경우에는 `callbackUrl` 없이 요청하면 됩니다. - -## 응답 읽기 - -HTTP 상태 코드는 기본적으로 `200`으로 응답하고, 실제 처리 결과는 응답 본문의 `status.code`와 `status.message`에서 확인합니다. -각 요청의 추적에는 `trackingId`를 사용할 수 있습니다. - - -# Error Model - -이 API는 HTTP 상태 코드보다 응답 본문의 `status` 객체를 기준으로 처리 결과를 전달합니다. -대부분의 실패 응답은 HTTP `200`으로 반환되고, 인증 필터에서 차단된 요청은 HTTP `401`이 함께 설정될 수 있습니다. - -## 공통 오류 응답 - -필터 API를 제외한 대부분의 엔드포인트는 `ApiResponse` 형식을 사용합니다. -오류 응답에서는 `data`가 `null`이며, 상세 원인은 `status.DetailDescription`에서 확인합니다. - -```json -{ - "status": { - "code": 4000, - "message": "Bad_request", - "description": "처리에 실패하였습니다. 요청이 잘못 되었거나 필수 파라미터가 누락된 경우 발생 합니다. Description에서 보다 상세한 오류 메세지를 확인할 수 있습니다.", - "DetailDescription": "text: 필터링 대상 문자열은 필수입니다." - }, - "data": null -} -``` - -## 필터 API 오류 응답 - -`/api/v1/filter`와 `/api/v1/filter/advanced`는 `FilterApiResponse` 형식을 사용합니다. -실패하더라도 요청 추적을 위해 `trackingId`가 포함될 수 있고, `detected`는 빈 배열, `filtered`는 빈 문자열로 반환됩니다. - -```json -{ - "trackingId": "018f4fd8-9f6f-7d1a-9b80-3f1f8dd7c002", - "status": { - "code": 4001, - "message": "Invalid_callback_url", - "description": "콜백 URL 형식이 올바르지 않습니다. 콜백 URL을 확인해 주세요.", - "DetailDescription": "" - }, - "detected": [], - "filtered": "", - "elapsed": "0.00000000 s / 0.00000 ms / 0.000 µs" -} -``` - -## 주요 상태 코드 - -| Code | Message | 의미 | -| --- | --- | --- | -| `2000` | `Ok` | 요청이 정상 처리되었습니다. | -| `2020` | `Accepted` | 비동기 요청이 접수되었습니다. 결과는 callback URL로 전달됩니다. | -| `4000` | `Bad_request` | 필수 파라미터 누락, 유효성 검증 실패, JSON 파싱 실패 등 요청 형식 오류입니다. | -| `4001` | `Invalid_callback_url` | callback URL 형식이 올바르지 않습니다. | -| `4010` | `Unauthorized` | API Key가 누락되었습니다. | -| `4030` | `Forbidden` | 클라이언트 권한이 부적절하거나 차단·폐기된 클라이언트입니다. | -| `4031` | `Not_found_client` | API Key에 매칭되는 클라이언트 정보를 찾을 수 없습니다. | -| `4032` | `Invalid_api_key` | API Key가 유효하지 않습니다. | -| `4290` | `Too_many_requests` | 요청 횟수 제한을 초과했습니다. | -| `5000` | `Internal_server_error` | 서버 내부 오류입니다. | - -## 상세 오류 메시지 - -`status.DetailDescription`은 요청별 상세 원인을 담습니다. -Bean Validation 오류는 필드별 메시지를 이어 붙여 반환하고, JSON 파싱 오류나 타입 불일치 오류는 요청 데이터 형식 문제를 설명합니다. - - -# Authentication - -인증이 필요한 API는 API Key를 HTTP 헤더에 포함해야 합니다. -클라이언트 등록과 이메일 인증, 헬스 체크, OpenAPI 문서 조회는 인증 없이 호출할 수 있습니다. - -## API Key 헤더 - -```http -x-api-key: this_is_test_key_example -``` - -서버 구현은 대소문자를 구분하지 않는 HTTP 헤더 특성에 따라 `X-API-KEY` 값을 읽고, OpenAPI 문서에는 `x-api-key` 이름으로 노출합니다. - -## 추가 예정: OAuth 2.0 Client Credentials - -현재 인증 방식은 API Key 헤더 기반입니다. -향후에는 Toss API처럼 OAuth 2.0 Client Credentials Grant 방식의 액세스 토큰 발급을 추가할 예정입니다. -이 방식이 추가되면 클라이언트는 발급받은 `client_id`와 `client_secret`으로 액세스 토큰을 요청하고, 보호된 API 호출 시 `Authorization: Bearer {access_token}` 헤더를 사용할 수 있게 됩니다. - -아직 이 흐름은 제공되지 않습니다. -현재 운영 가능한 인증 방식은 `x-api-key` 헤더입니다. - -## 인증 흐름 - -1. 클라이언트 등록 API로 API Key를 발급합니다. -2. 보호된 API 호출 시 `x-api-key` 헤더에 API Key를 전달합니다. -3. 서버는 API Key로 클라이언트 메타데이터를 조회합니다. -4. 클라이언트 권한은 Spring Security 권한인 `ROLE_READ`, `ROLE_WRITE`, `ROLE_DELETE`, `ROLE_BLOCK`, `ROLE_DISCARD` 형태로 변환됩니다. -5. `@VerifiedClientOnly`가 적용된 API는 차단 또는 폐기된 클라이언트를 한 번 더 검사합니다. - -## 인증이 필요하지 않은 경로 - -- `GET /` -- `GET /index.html` -- `POST /api/v1/clients/register` -- `GET /api/v1/clients/send-email` -- `PUT /api/v1/clients/send-email` -- `GET /api/v1/health` -- `GET /api/v1/ping` -- `GET /openapi.json` -- `GET /overview.md` -- `GET /llms.txt` -- `GET /llm.txt` - -## 인증 실패 - -API Key가 없으면 `4010 Unauthorized` 상태 코드가 반환됩니다. -API Key가 유효하지 않거나 클라이언트 정보를 찾을 수 없으면 `4032 Invalid_api_key` 또는 `4031 Not_found_client`가 반환됩니다. -차단되거나 폐기된 클라이언트는 `4030 Forbidden`으로 응답합니다. - -```json -{ - "status": { - "code": 4010, - "message": "Unauthorized", - "description": "인증 키가 누락 되었습니다.", - "DetailDescription": "" - }, - "data": null -} -``` - -## CORS - -현재 서버는 모든 origin을 허용하고, `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `OPTIONS` 메서드를 허용합니다. -브라우저 클라이언트에서는 `x-api-key` 헤더를 포함해 요청할 수 있습니다. diff --git a/ui/app/scripts/check-docs-layout.mjs b/ui/app/scripts/check-docs-layout.mjs deleted file mode 100644 index 0b83038..0000000 --- a/ui/app/scripts/check-docs-layout.mjs +++ /dev/null @@ -1,159 +0,0 @@ -import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { resolve } from "node:path"; -const appRoot = resolve(import.meta.dirname, ".."); - -const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8"); -const docsPage = readFileSync(resolve(appRoot, "src", "DocsPage.tsx"), "utf8"); -const docsConstants = readFileSync(resolve(appRoot, "src", "docs", "constants.ts"), "utf8"); -const docsHooks = readFileSync(resolve(appRoot, "src", "docs", "hooks.ts"), "utf8"); -const docsUtils = readFileSync(resolve(appRoot, "src", "docs", "utils.tsx"), "utf8"); -const homePage = readFileSync(resolve(appRoot, "src", "features", "home", "HomePage.tsx"), "utf8"); -const landingContent = readFileSync(resolve(appRoot, "src", "constants", "landingContent.ts"), "utf8"); -const designPolicy = readFileSync(resolve(appRoot, "..", "policies", "design-system.md"), "utf8"); -const styles = readFileSync(resolve(appRoot, "src", "styles.css"), "utf8"); -const overview = readFileSync(resolve(appRoot, "public", "overview.md"), "utf8"); -const appSources = [app, homePage, landingContent].join("\n"); -const docsSources = [docsPage, docsConstants, docsHooks, docsUtils].join("\n"); - -assert.match(overview, /시작하기/, "Overview markdown must include a getting-started section."); - -assert.match(docsPage, /ApiReferenceReact/, "DocsPage must render the OpenAPI body with Scalar."); -assert.match( - docsConstants, - /OPENAPI_DOCUMENT_URL = "https:\/\/api\.kr-filter\.com\/openapi\.json"/, - "Scalar must read the OpenAPI JSON document from the API server.", -); -assert.ok( - !existsSync(resolve(appRoot, "public", "openapi.json")), - "The UI bundle must not ship a stale local OpenAPI JSON copy.", -); -assert.match(docsConstants, /OVERVIEW_MARKDOWN_PATH = "https:\/\/api\.kr-filter\.com\/overview\.md"/, "Docs overview must read the API server markdown file."); -assert.match(docsHooks, /documentPromiseCache/, "Remote docs must cache in-flight and completed document loads for the app runtime."); -assert.match(app, /preloadDocsDocuments/, "Docs route loader must start remote document loading before DocsPage renders."); -assert.match(app, /Promise\.all\(\[\s*docsPageModule,\s*docsDocuments,\s*\]\)/, "Docs route loader must wait for the page chunk and both docs with one Suspense loading state."); -assert.match(docsHooks, /preloadDocsDocuments/, "Docs route must expose one preload function that starts every remote docs request together."); -assert.match(docsHooks, /Promise\.allSettled/, "Docs preload must start OpenAPI and overview requests in parallel."); -assert.match(docsHooks, /getCachedDocumentResult/, "Docs hooks must synchronously reuse resolved preload results for initial state."); -assert.doesNotMatch(docsPage, /preloadDocsDocuments\(\s*OPENAPI_DOCUMENT_URL,\s*OVERVIEW_MARKDOWN_PATH,\s*FALLBACK_OVERVIEW_MARKDOWN,\s*\)/, "DocsPage module must not start a second loading phase after the route loader already preloaded docs."); -assert.doesNotMatch(docsSources, /Date\.now\(\)/, "Remote docs must not bypass browser/runtime caching with timestamp query strings."); -assert.match(docsPage, /api-docs-sidebar/, "Docs page must keep the existing fixed sidebar shell."); -assert.match(docsPage, /showSidebar:\s*false/, "Scalar native sidebar must stay hidden inside the fixed docs shell."); -assert.match(docsSources, /buildSections/, "Docs sidebar must be built from OpenAPI tag sections."); -assert.match(docsSources, /parseMarkdownRootHeadings/, "Docs sidebar must include only root overview headings from markdown."); -assert.match(docsUtils, /isMarkdownHorizontalRule/, "Docs markdown parser must detect horizontal rule separator lines."); -assert.match(docsUtils, /
]*>\s*Overview\s*]*>\s*OpenAPI\s*\s*Overview<\/span>\s*<\/a>/, "Overview root group must not keep the old fixed Overview text link."); -assert.match(docsPage, /href=\{`\/docs#\$\{encodeURIComponent\(link\.anchor\)\}`\}/, "Overview child links must still navigate by hash."); -assert.match(docsPage, /hideTestRequestButton:\s*true/, "Interactive request execution must stay hidden."); -assert.match(docsPage, /defaultOpenAllTags:\s*true/, "Scalar body must keep all OpenAPI tag sections expanded so operation hash navigation can scroll within one page."); -assert.match(docsPage, /defaultOpenFirstTag:\s*false/, "Scalar body must not rely on only the first tag being open."); -assert.match(docsPage, /import\s+\{\s*ApiReferenceReact/, "DocsPage route chunk must include Scalar so OpenAPI rendering does not show a second loading overlay."); -assert.doesNotMatch(docsPage, /lazy\(\s*\(\)\s*=>\s*import\("@scalar\/api-reference-react"\)/, "Scalar React renderer must not create a second lazy loading phase inside DocsPage."); -assert.match(docsPage, /hideClientButton:\s*true/, "Scalar client button must stay hidden."); -assert.match(docsPage, /documentDownloadType:\s*"none"/, "Scalar document download UI must stay hidden."); -assert.doesNotMatch(docsPage, /문서<\/strong>/, "Docs sidebar must not show a meaningless document group label."); -assert.doesNotMatch(docsPage, />API 레퍼런스OpenAPI<\/p>/, "Selected API group body must not add a custom OpenAPI label above Scalar."); -assert.doesNotMatch(docsPage, /

\{selectedSection\.name\}<\/h1>/, "Selected API group body must let Scalar render the tag heading."); -assert.doesNotMatch(docsPage, /DocsSubnav/, "Docs page must not add a separate top sub-navigation."); -assert.match(docsPage, /id="overview"/, "Overview and OpenAPI must share one docs page with a hash target for overview."); -assert.match(styles, /\.api-docs-sidebar\b/, "Docs CSS must style the existing fixed sidebar shell."); -assert.match(styles, /\.api-docs-root-group\b/, "Docs CSS must visually separate Overview and OpenAPI root groups."); -assert.match(styles, /\.api-docs-root-label\b/, "Docs CSS must style docs sidebar root group labels."); -assert.match(styles, /\.docs-loading-bar\b/, "Docs loading state must show a centered loading bar instead of text-only loading."); -assert.doesNotMatch(docsPage, /}>/, "Scalar body must not show a second docs loading overlay after the route loader finishes."); -assert.match(app, //, "App docs route loading must use the shared viewport-centered loading overlay."); -assert.match(docsPage, //, "Docs internal loading must use the same viewport-centered loading frame."); -assert.match(extractRule(styles, ".docs-loading-overlay"), /position:\s*fixed/, "Docs loading overlay must stay fixed to the browser viewport."); -assert.match(extractRule(styles, ".docs-loading-overlay"), /inset:\s*0/, "Docs loading overlay must cover the browser viewport."); -assert.match(extractRule(styles, ".docs-loading-overlay"), /place-items:\s*center/, "Docs loading overlay must center the loading bar consistently."); -assert.match(styles, /\.docs-markdown-article hr\b/, "Docs markdown horizontal rules must be styled as separators."); -assert.match(styles, /\.api-docs-sidebar-children\b/, "Docs CSS must style expanded endpoint children."); -assert.match(styles, /--scalar-custom-header-height:\s*0px/, "Scalar must render inside the content area without its own sticky header offset."); -assert.match(styles, /--scalar-sidebar-width:\s*0px/, "Scalar internal sidebar must stay hidden inside the fixed docs shell."); - -const docsPageBlock = extractRule(styles, ".api-docs-page"); -assert.match(docsPageBlock, /grid-template-columns:\s*280px minmax\(0, 1fr\)/, "Docs page must keep the fixed sidebar column."); - -const copyActionsBlock = extractRule(styles, ".api-docs-copy-actions"); -assert.match(copyActionsBlock, /position:\s*sticky/, "Docs copy actions must stay in the content flow."); - -assert.match(styles, /\.api-docs-overview/, "Docs CSS must style the markdown overview body inside the fixed shell."); -assert.match(styles, /\.docs-markdown-article/, "Docs markdown CSS must avoid Scalar's markdown-article class name."); -assert.doesNotMatch(styles, /\.markdown-article\b/, "Docs markdown CSS must not collide with Scalar's markdown-article class."); -assert.match(extractRule(styles, ".api-docs-content"), /max-width:\s*100%/, "Docs content must not exceed the available width."); -assert.match(extractRule(styles, ".api-docs-main"), /max-width:\s*100%/, "Docs main rail must not exceed the available width."); -assert.match(extractRule(styles, ".api-docs-overview"), /min-width:\s*0/, "Docs overview must be allowed to shrink."); -assert.match(extractRule(styles, ".docs-markdown-article"), /max-width:\s*100%/, "Docs markdown must not exceed the content width."); -assert.match(extractRule(styles, ".markdown-table-wrap"), /overflow-x:\s*auto/, "Wide markdown tables must scroll inside their own container."); - -const mobileBlock = extractMedia(styles, "@media (max-width: 767px)"); -assert.match(mobileBlock, /\.api-docs-content\s*{[^}]*padding:/s, "Mobile docs layout must keep readable content padding."); -assert.doesNotMatch(mobileBlock, /\.api-docs-content\s*{[^}]*padding:\s*0\s*;/s, "Mobile docs content must keep horizontal padding instead of touching viewport edges."); -assert.doesNotMatch(mobileBlock, /\.api-docs-sidebar\s*{[^}]*position:\s*fixed/s, "Mobile sidebar must not become a fixed overlay."); - -assert.match(designPolicy, /첫 화면은 4개 블록으로 구성한다\./, "Design policy must define the four-block landing structure."); -assert.match(designPolicy, /2번 블록은 프로젝트 정체성을 짧게 설명한다\./, "Design policy must define the second block as the project identity section."); -assert.match(designPolicy, /3번 블록은 사용 시나리오를 영상형 흐름으로 보여준다\./, "Design policy must define the third block as the scenario reel section."); -assert.match(styles, /--landing-letterbox:\s*16vw/, "Landing sections must define a ratio-based side letterbox gutter."); -assert.match(styles, /--landing-rail-width:\s*68vw/, "Landing sections must use a ratio-based readable rail."); -assert.match(styles, /--landing-section-height:\s*calc\(100dvh - var\(--landing-nav-height\)\)/, "Landing story sections must target one viewport below the fixed nav."); -assert.doesNotMatch(styles, /--landing-rail-width:\s*\d+px/, "Landing rail must not use a fixed pixel width."); -assert.match(styles, /calc\(100vw - var\(--landing-letterbox\) - var\(--landing-letterbox\)\)/, "Landing sections must reserve side letterbox space."); -assert.match(styles, /\.story-section\s*{[^}]*min-height:\s*var\(--landing-section-height\)/s, "Second landing block must fit inside the viewport section height."); -assert.match(styles, /\.start-section\s*{[^}]*min-height:\s*var\(--landing-section-height\)/s, "Third landing block must fit inside the viewport section height."); -assert.match(styles, /\.story-section,[\s\S]*?\.start-section,[\s\S]*?\.footer-cta\s*{[^}]*scroll-margin-top:\s*var\(--landing-nav-height\)/s, "Landing sections must account for the fixed nav when anchored."); -assert.doesNotMatch(styles, /scroll-snap-type:\s*y/, "Landing pages must not force scroll snapping between sections."); -assert.doesNotMatch(styles, /\.story-block\s*{[^}]*680px/s, "Story block must not rely on a fixed pixel minimum height."); -assert.match(appSources, /한국어 문장을 API로 필터링합니다/, "Second landing block must explain what the project does."); -assert.match(appSources, /role="tablist"/, "Second landing block must expose type options as tabs."); -assert.match(appSources, /무료\/실용/, "Second landing block must include the free practical positioning tab."); -assert.match(appSources, /한국어 중심/, "Second landing block must include the Korean-focused positioning tab."); -assert.match(appSources, /개발자 연동/, "Second landing block must include the developer integration positioning tab."); -assert.doesNotMatch(appSources, /운영 신뢰/, "Second landing block must not include the operational trust positioning tab."); -assert.match(styles, /\.identity-tabs/, "Second landing block must style the identity tab control."); -assert.match(appSources, /proofItems/, "Second landing block must define proof row items for each positioning tab."); -assert.match(appSources, /필터 API 호출/, "Second landing block must phrase endpoint proof as natural Korean."); -assert.match(appSources, /응답 코드 확인/, "Second landing block must phrase response proof as natural Korean."); -assert.doesNotMatch(appSources, /visualLabel:\s*"position"|visualLabel:\s*"engine"|visualLabel:\s*"request"|visualLabel:\s*"response"/, "Second landing block visual labels must not expose raw English keys."); -assert.doesNotMatch(appSources, /visualTitle:\s*"free API"|visualTitle:\s*"Korean trie"|visualTitle:\s*"before save"|visualTitle:\s*"tracked"/, "Second landing block visual titles must be natural Korean sentences."); -assert.doesNotMatch(appSources, /proofItems:\s*\[[^\]]*"text"|"mode"|"X-API-KEY"|"status\.code"|"trackingId"|"records"/s, "Second landing block proof rows must avoid raw API field names."); -assert.match(styles, /\.identity-proof-row/, "Second landing block must style the proof row."); -assert.match(appSources, /사용 시나리오/, "Third landing block must introduce a simple usage scenario."); -assert.doesNotMatch(appSources, /시나리오 영상/, "Scenario panel must not label itself as a video."); -assert.match(styles, /animation:\s*scenario-card-one\s+10s/, "Input card must use the shared 10s scenario loop."); -assert.match(styles, /animation:\s*scenario-card-two\s+10s/, "API card must use the shared 10s scenario loop."); -assert.match(styles, /animation:\s*scenario-card-three\s+10s/, "Result card must use the shared 10s scenario loop."); -assert.doesNotMatch(styles, /animation-delay:\s*1\.4s|animation-delay:\s*2\.8s/, "Scenario card timing must not rely on per-card animation delays."); -assert.match(appSources, /신청하기/, "Footer CTA must include an apply button."); -assert.match(appSources, /문서 보기/, "Footer CTA must include a docs button."); -assert.doesNotMatch(appSources, /title:\s*"2번 블럭"|title:\s*"3번 블럭"|body:\s*"2번 블럭"|body:\s*"3번 블럭"/, "Landing sections must not keep placeholder copy."); - -function extractRule(source, selector) { - const start = source.indexOf(`${selector} {`); - assert.notEqual(start, -1, `${selector} rule not found.`); - const end = source.indexOf("}", start); - assert.notEqual(end, -1, `${selector} rule is not closed.`); - return source.slice(start, end + 1); -} - -function extractMedia(source, media) { - const start = source.indexOf(media); - assert.notEqual(start, -1, `${media} block not found.`); - const next = source.indexOf("@media", start + media.length); - return next === -1 ? source.slice(start) : source.slice(start, next); -} diff --git a/ui/app/scripts/check-hooks-contract.mjs b/ui/app/scripts/check-hooks-contract.mjs deleted file mode 100644 index f029e54..0000000 --- a/ui/app/scripts/check-hooks-contract.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const appRoot = resolve(import.meta.dirname, ".."); - -const navigationHookPath = resolve(appRoot, "src", "hooks", "useAppNavigation.ts"); -const clipboardHookPath = resolve(appRoot, "src", "hooks", "useClipboard.ts"); - -assert.ok(existsSync(navigationHookPath), "useAppNavigation hook must exist."); -assert.ok(existsSync(clipboardHookPath), "useClipboard hook must exist."); - -const navigationHook = readFileSync(navigationHookPath, "utf8"); -const clipboardHook = readFileSync(clipboardHookPath, "utf8"); -const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8"); -const docsPage = readFileSync(resolve(appRoot, "src", "DocsPage.tsx"), "utf8"); -const registerPage = readFileSync(resolve(appRoot, "src", "features", "register", "RegisterPage.tsx"), "utf8"); - -assert.match(navigationHook, /export function useAppNavigation/, "Navigation hook must export useAppNavigation."); -assert.match(navigationHook, /addEventListener\("popstate"/, "Navigation hook must own popstate synchronization."); -assert.match(navigationHook, /scrollRestoration\s*=\s*"manual"/, "Navigation hook must own scroll restoration."); -assert.match(navigationHook, /getRouteForPathname/, "Navigation hook must use route registry."); -assert.match(navigationHook, /navigate/, "Navigation hook must return a navigate command."); - -assert.match(clipboardHook, /export function useClipboard/, "Clipboard hook must export useClipboard."); -assert.match(clipboardHook, /ClipboardStatus\s*=\s*"idle" \| "copied" \| "failed"/, "Clipboard state must include failed status."); -assert.match(clipboardHook, /try\s*{[\s\S]*writeText[\s\S]*}\s*catch/s, "Clipboard writes must handle failures."); - -assert.match(app, /useAppNavigation/, "App must use navigation hook."); -assert.doesNotMatch(app, /addEventListener\("popstate"/, "App must not own popstate directly."); - -assert.match(docsPage, /useClipboard/, "Docs page must use clipboard hook."); -assert.match(registerPage, /useClipboard/, "Register page must use clipboard hook."); -assert.doesNotMatch(docsPage, /navigator\.clipboard\.writeText/, "Docs page must not call clipboard directly."); -assert.doesNotMatch(registerPage, /navigator\.clipboard\.writeText/, "Register page must not call clipboard directly."); diff --git a/ui/app/scripts/check-policy-docs.mjs b/ui/app/scripts/check-policy-docs.mjs deleted file mode 100644 index 325cea1..0000000 --- a/ui/app/scripts/check-policy-docs.mjs +++ /dev/null @@ -1,54 +0,0 @@ -import assert from "node:assert/strict"; -import { existsSync, readFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const uiRoot = resolve(import.meta.dirname, "..", ".."); -const appRoot = resolve(import.meta.dirname, ".."); - -assert.equal(appRoot.endsWith(`${resolve(uiRoot, "app")}`), true, "Runtime app directory must be ui/app."); -assert.equal( - existsSync(resolve(uiRoot, ["sample", "app"].join("-"))), - false, - "Runtime app directory must not use the old temporary name.", -); - -const requiredFiles = [ - "policies/engineering.md", - "policies/design-system.md", - "skills/profanity-ui/SKILL.md", -]; - -for (const filePath of requiredFiles) { - assert.ok(existsSync(resolve(uiRoot, filePath)), `${filePath} must live under ui/.`); -} - -const uiReadme = readFileSync(resolve(uiRoot, "README.md"), "utf8"); -const appReadme = readFileSync(resolve(appRoot, "README.md"), "utf8"); -const engineeringPolicy = readFileSync(resolve(uiRoot, "policies", "engineering.md"), "utf8"); -const designPolicy = readFileSync(resolve(uiRoot, "policies", "design-system.md"), "utf8"); -const skill = readFileSync(resolve(uiRoot, "skills", "profanity-ui", "SKILL.md"), "utf8"); - -for (const [name, content] of [ - ["ui/README.md", uiReadme], - ["app/README.md", appReadme], -]) { - assert.doesNotMatch(content, new RegExp(["샘", "플"].join("")), `${name} must describe the product UI, not a temporary app.`); - assert.doesNotMatch(content, /실험|데모|demo/i, `${name} must avoid prototype/demo framing.`); -} - -assert.match(engineeringPolicy, /YAGNI/, "Engineering policy must explicitly define YAGNI."); -assert.match(engineeringPolicy, /OOP/, "Engineering policy must explicitly define OOP boundaries."); -assert.match(engineeringPolicy, /Tell, Don'?t Ask/, "Engineering policy must include Tell, Don't Ask."); -assert.match(engineeringPolicy, /Rules of Hooks/, "Engineering policy must cite hook rules."); -assert.match(engineeringPolicy, /Red.*Green.*Refactor/s, "Engineering policy must define TDD flow."); - -assert.match(designPolicy, /Design Tokens/, "Design policy must define design tokens."); -assert.match(designPolicy, /--ivory/, "Design policy must keep the existing ivory token."); -assert.match(designPolicy, /--pine/, "Design policy must keep the existing pine token."); -assert.match(designPolicy, /--sage/, "Design policy must keep the existing sage token."); -assert.match(designPolicy, /상태/, "Design policy must cover UI state rules."); -assert.match(designPolicy, /메뉴/, "Design policy must cover progressive menu growth."); - -assert.match(skill, /^---\nname: profanity-ui\n/m, "Project skill must have a stable name."); -assert.match(skill, /policies\/engineering\.md/, "Project skill must route agents to engineering policy."); -assert.match(skill, /policies\/design-system\.md/, "Project skill must route agents to design policy."); diff --git a/ui/app/scripts/check-register-flow.mjs b/ui/app/scripts/check-register-flow.mjs deleted file mode 100644 index 9bc8832..0000000 --- a/ui/app/scripts/check-register-flow.mjs +++ /dev/null @@ -1,37 +0,0 @@ -import assert from "node:assert/strict"; -import { readFileSync } from "node:fs"; -import { resolve } from "node:path"; - -const appRoot = resolve(import.meta.dirname, ".."); - -const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8"); -const pagePath = readFileSync(resolve(appRoot, "src", "constants", "pagePath.ts"), "utf8"); -const routes = readFileSync(resolve(appRoot, "src", "app", "routes.tsx"), "utf8"); -const homePage = readFileSync(resolve(appRoot, "src", "features", "home", "HomePage.tsx"), "utf8"); -const registerPage = readFileSync(resolve(appRoot, "src", "features", "register", "RegisterPage.tsx"), "utf8"); -const styles = readFileSync(resolve(appRoot, "src", "styles.css"), "utf8"); - -assert.match(pagePath, /REGISTER_PATH(?:\s*:\s*PagePath)?\s*=\s*"\/register"/, "Register route must be defined as a first-class page path."); -assert.match(app, /currentRoute\.render/, "App must render selected routes through the route registry."); -assert.match(routes, /id:\s*"register"[\s\S]*/, "Route registry must render the register page for the register route."); -assert.match(homePage, /onNavigate\("\/register"\)/, "Home apply CTA must navigate to the register page."); - -assert.match(registerPage, /function createLocalApiKey/, "Register page must create an API key locally until the API is connected."); -assert.match(registerPage, /crypto\.getRandomValues/, "Local key issuance must use browser cryptographic randomness."); -assert.match(registerPage, /type="submit"/, "Register page must expose a form submit flow."); -assert.match(registerPage, /인증번호 발송/, "Register page must require an email verification send step."); -assert.match(registerPage, /인증번호 확인/, "Register page must require an email verification confirm step."); -assert.match(registerPage, /emailVerified/, "Register page must track verified email state before issuing a key."); -assert.match(registerPage, /canIssue/, "Register page must gate key issuance on email verification."); -assert.doesNotMatch(registerPage, /const canIssue = Boolean\([^)]*formState\.email\.trim\(\)[^)]*\)/s, "Email input alone must not be enough to issue a key."); -assert.doesNotMatch(registerPage, /\bfetch\s*\(/, "Register page must not send real API requests yet."); -assert.doesNotMatch(registerPage, /\bXMLHttpRequest\b/, "Register page must not use XMLHttpRequest."); -assert.doesNotMatch(registerPage, /https:\/\/api\.kr-filter\.com\/api\/v1\/clients\/register/, "Register page must not hard-code the production register API."); -assert.doesNotMatch(registerPage, /mock|모의/i, "Register page must not present the key issuance flow as fake UI."); -assert.doesNotMatch(registerPage, /API Key|POST \/api\/v1\/clients\/register|ClientsRegistResponse|이름, 이메일, 발급자 정보를 입력해 키를 발급합니다/, "Register page must not show implementation or document-copy text."); -assert.doesNotMatch(registerPage, /responsePreview|
/, "Register page must not show raw response JSON.");
-assert.match(registerPage, /useClipboard/, "Issued keys must be copyable through the shared clipboard hook.");
-
-assert.match(styles, /\.register-page\b/, "Register page shell must be styled.");
-assert.match(styles, /\.register-form\b/, "Register form must be styled.");
-assert.match(styles, /\.register-result\b/, "Register result must be styled.");
diff --git a/ui/app/scripts/check-routing-contract.mjs b/ui/app/scripts/check-routing-contract.mjs
deleted file mode 100644
index 6c56d2f..0000000
--- a/ui/app/scripts/check-routing-contract.mjs
+++ /dev/null
@@ -1,27 +0,0 @@
-import assert from "node:assert/strict";
-import { readFileSync } from "node:fs";
-import { resolve } from "node:path";
-
-const appRoot = resolve(import.meta.dirname, "..");
-
-const routes = readFileSync(resolve(appRoot, "src", "app", "routes.tsx"), "utf8");
-const app = readFileSync(resolve(appRoot, "src", "App.tsx"), "utf8");
-const navigation = readFileSync(resolve(appRoot, "src", "components", "Navigation.tsx"), "utf8");
-const pagePath = readFileSync(resolve(appRoot, "src", "constants", "pagePath.ts"), "utf8");
-
-assert.match(routes, /export const APP_ROUTES/, "Routes must be centralized in APP_ROUTES.");
-assert.match(routes, /showInNavigation:\s*true/, "Navigation visibility must be route metadata.");
-assert.match(routes, /matchPath/, "Each route must expose a matchPath contract.");
-assert.match(routes, /pathname === DOCS_PATH \|\| pathname\.startsWith\(`\$\{DOCS_PATH\}\/`\)/, "Docs route must not match docs-admin style paths.");
-assert.match(routes, /getRouteForPathname/, "Route selection must be centralized.");
-assert.match(routes, /getNavigationItems/, "Navigation item selection must be centralized.");
-
-assert.match(app, /currentRoute\.render/, "App must render through route registry.");
-assert.doesNotMatch(app, /pagePath\.startsWith\("\/docs"\)/, "App must not hard-code docs prefix matching.");
-assert.doesNotMatch(app, /pagePath === "\/register"/, "App must not hard-code register routing.");
-
-assert.match(navigation, /getNavigationItems/, "Navigation must read menu items from route registry.");
-assert.doesNotMatch(navigation, /href="\/register"[\s\S]*href="\/docs"/, "Navigation must not hard-code menu anchors.");
-
-assert.match(pagePath, /DOCS_PATH/, "Page path constants must expose DOCS_PATH.");
-assert.doesNotMatch(pagePath, /pathname\.startsWith\(DOCS_PREFIX\)/, "Page path parsing must not use broad docs prefix matching.");
diff --git a/ui/app/src/App.tsx b/ui/app/src/App.tsx
index 7bab7c1..1b523d7 100644
--- a/ui/app/src/App.tsx
+++ b/ui/app/src/App.tsx
@@ -1,52 +1,3 @@
-import { lazy, Suspense } from "react";
-
-import { OPENAPI_DOCUMENT_URL, OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN } from "./docs/constants";
-import { preloadDocsDocuments } from "./docs/hooks";
-import { Navigation } from "./components/Navigation";
-import { useAppNavigation } from "./hooks/useAppNavigation";
-
-function loadDocsPage() {
-  const docsPageModule = import("./DocsPage");
-  const docsDocuments = preloadDocsDocuments(
-    OPENAPI_DOCUMENT_URL,
-    OVERVIEW_MARKDOWN_PATH,
-    FALLBACK_OVERVIEW_MARKDOWN,
-  );
-
-  return Promise.all([
-    docsPageModule,
-    docsDocuments,
-  ]).then(([module]) => module);
-}
-
-const DocsPage = lazy(loadDocsPage);
-
 export default function App() {
-  const { currentRoute, navigate, pagePath } = useAppNavigation();
-  const docsElement = (
-    }>
-      
-    
-  );
-
-  return (
-    
- - {currentRoute.render({ docsElement, navigate })} -
- ); -} - -function DocsFallback() { - return ; -} - -function DocsLoadingOverlay() { - return ( -
-
- -
-
- ); + return null; } diff --git a/ui/app/src/DocsPage.tsx b/ui/app/src/DocsPage.tsx deleted file mode 100644 index 5c20436..0000000 --- a/ui/app/src/DocsPage.tsx +++ /dev/null @@ -1,263 +0,0 @@ -import { ApiReferenceReact, type AnyApiReferenceConfiguration } from "@scalar/api-reference-react"; -import "@scalar/api-reference-react/style.css"; -import { useEffect, useMemo, useState } from "react"; - -import { OPENAPI_DOCUMENT_URL, OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN } from "./docs/constants"; -import { - buildSections, - buildHashNavigation, - createOperationAnchor, - createScalarOperationSlug, - createTagAnchor, - getSectionForHash, - parseMarkdown, -} from "./docs/utils"; -import { useMarkdownDocument, useOpenApiDocument } from "./docs/hooks"; -import { useClipboard } from "./hooks/useClipboard"; - -function getDecodedHash() { - const rawHash = window.location.hash.replace(/^#/, ""); - if (!rawHash) { - return ""; - } - try { - return decodeURIComponent(rawHash); - } catch { - return rawHash; - } -} - -export default function DocsPage() { - const [activeHash, setActiveHash] = useState(getDecodedHash); - const [openSectionSlugs, setOpenSectionSlugs] = useState>(() => new Set()); - const { document, error } = useOpenApiDocument(OPENAPI_DOCUMENT_URL); - const overview = useMarkdownDocument(OVERVIEW_MARKDOWN_PATH, FALLBACK_OVERVIEW_MARKDOWN); - const { copy, status: copyStatus } = useClipboard(); - - useEffect(() => { - const syncHash = () => setActiveHash(getDecodedHash()); - window.addEventListener("hashchange", syncHash); - window.addEventListener("popstate", syncHash); - return () => { - window.removeEventListener("hashchange", syncHash); - window.removeEventListener("popstate", syncHash); - }; - }, []); - - const sections = useMemo(() => (document ? buildSections(document) : []), [document]); - const overviewLinks = useMemo(() => buildHashNavigation(overview.content), [overview.content]); - const selectedSection = useMemo(() => getSectionForHash(activeHash, sections), [activeHash, sections]); - - const scalarConfiguration = useMemo(() => { - if (!document) { - return null; - } - - return { - content: document, - title: document.info?.title ?? "API 문서", - slug: "docs", - theme: "none", - layout: "modern", - forceDarkModeState: "light", - defaultOpenAllTags: true, - defaultOpenFirstTag: false, - hideDarkModeToggle: true, - documentDownloadType: "none", - hideTestRequestButton: true, - hideClientButton: true, - hideModels: true, - hideSearch: true, - showSidebar: false, - showDeveloperTools: "never", - searchHotKey: "k", - withDefaultFonts: false, - agent: { disabled: true }, - mcp: { disabled: true }, - generateTagSlug: ({ name }) => slugify(name ?? ""), - generateOperationSlug: ({ method, path, operationId, summary }) => - createScalarOperationSlug({ method, path, operationId, summary }), - }; - }, [document]); - - useEffect(() => { - if (!selectedSection) { - return; - } - setOpenSectionSlugs((current) => { - if (current.has(selectedSection.slug)) { - return current; - } - const next = new Set(current); - next.add(selectedSection.slug); - return next; - }); - }, [selectedSection]); - - useEffect(() => { - const targetHash = activeHash || "overview"; - const timers: number[] = []; - let attempts = 0; - - const scrollWhenReady = () => { - const target = window.document.getElementById(targetHash); - if (target) { - target.scrollIntoView({ block: "start" }); - return; - } - if (attempts < 30) { - attempts += 1; - timers.push(window.setTimeout(scrollWhenReady, 120)); - } - }; - - timers.push(window.setTimeout(scrollWhenReady, 0)); - return () => timers.forEach((timer) => window.clearTimeout(timer)); - }, [activeHash, selectedSection?.slug, overview.content]); - - const copyOpenApiUrl = async () => { - await copy(OPENAPI_DOCUMENT_URL); - }; - - if (error) { - return ( -
-
{error}
-
- ); - } - - if (!document) { - return ( -
- -
- ); - } - - const isOverview = !activeHash.startsWith("docs/tag/"); - const getLinkClassName = (anchor: string, extraClassName?: string) => - [activeHash === anchor ? "active" : "", extraClassName].filter(Boolean).join(" ") || undefined; - const toggleOpenApiSection = (sectionSlug: string) => { - setOpenSectionSlugs((current) => { - const next = new Set(current); - if (next.has(sectionSlug)) { - next.delete(sectionSlug); - } else { - next.add(sectionSlug); - } - return next; - }); - }; - - return ( -
- - -
-
- {isOverview ? (overview.source === "local" ? "Markdown" : "fallback") : document.openapi} - -
- -
- {isOverview ? ( -
-

- Overview - {overview.source === "local" ? overview.url : "fallback"} -

- -
- ) : ( -
- {scalarConfiguration ? : null} -
- )} -
-
-
- ); -} - -function DocsLoadingFrame() { - return ( -
- -
- ); -} - -function DocsLoadingBar() { - return ( -
- -
- ); -} - -function MarkdownArticle({ content }: { content: string }) { - return
{parseMarkdown(content)}
; -} - -function slugify(value: string) { - return value - .trim() - .toLowerCase() - .replace(/&/g, "and") - .replace(/[^a-z0-9가-힣]+/g, "-") - .replace(/^-+|-+$/g, ""); -} diff --git a/ui/app/src/app/routes.tsx b/ui/app/src/app/routes.tsx deleted file mode 100644 index 5fb1482..0000000 --- a/ui/app/src/app/routes.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { ReactNode } from "react"; - -import { DOCS_PATH, HOME_PATH, REGISTER_PATH, type PagePath } from "../constants/pagePath"; -import { HomePage } from "../features/home/HomePage"; -import { RegisterPage } from "../features/register/RegisterPage"; - -type Navigate = (path: PagePath) => void; - -type RouteRenderContext = { - docsElement: ReactNode; - navigate: Navigate; -}; - -export type AppRoute = { - id: "home" | "register" | "docs"; - label: string; - matchPath: (pathname: string) => boolean; - path: PagePath; - render: (context: RouteRenderContext) => ReactNode; - showInNavigation: boolean; -}; - -export const APP_ROUTES: AppRoute[] = [ - { - id: "home", - label: "홈", - matchPath: (pathname) => pathname === HOME_PATH, - path: HOME_PATH, - render: ({ navigate }) => , - showInNavigation: true, - }, - { - id: "register", - label: "시작", - matchPath: (pathname) => pathname === REGISTER_PATH, - path: REGISTER_PATH, - render: ({ navigate }) => , - showInNavigation: true, - }, - { - id: "docs", - label: "문서", - matchPath: (pathname) => pathname === DOCS_PATH || pathname.startsWith(`${DOCS_PATH}/`), - path: DOCS_PATH, - render: ({ docsElement }) => docsElement, - showInNavigation: true, - }, -]; - -export const HOME_ROUTE = APP_ROUTES[0]; - -export function getRouteForPathname(pathname: string) { - return APP_ROUTES.find((route) => route.matchPath(pathname)) ?? HOME_ROUTE; -} - -export function getNavigationItems() { - return APP_ROUTES.filter((route) => route.showInNavigation); -} diff --git a/ui/app/src/components/Navigation.tsx b/ui/app/src/components/Navigation.tsx deleted file mode 100644 index cfcd858..0000000 --- a/ui/app/src/components/Navigation.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { PagePath } from "../constants/pagePath"; -import { getNavigationItems } from "../app/routes"; - -type NavigationProps = { - onNavigate: (path: PagePath) => void; - pagePath: PagePath; -}; - -export function Navigation({ onNavigate, pagePath }: NavigationProps) { - const navigationItems = getNavigationItems(); - - return ( -
- { - event.preventDefault(); - onNavigate("/"); - }} - > - 말조심하세욧 - - -
- ); -} diff --git a/ui/app/src/constants/landingContent.ts b/ui/app/src/constants/landingContent.ts deleted file mode 100644 index a59a14b..0000000 --- a/ui/app/src/constants/landingContent.ts +++ /dev/null @@ -1,83 +0,0 @@ -export type IdentityTabOption = { - id: "free-practical" | "korean-focused" | "developer-integration"; - tabLabel: string; - label: string; - intro: string; - title: string; - body: string; - points: readonly string[]; - proofItems: readonly string[]; - visualLabel: string; - visualTitle: string; - visualLines: readonly string[]; -}; - -export type ScenarioStep = { - id: "scenario-1" | "scenario-2" | "scenario-3"; - label: string; - title: string; - body: string; -}; - -export const IDENTITY_OPTIONS: readonly IdentityTabOption[] = [ - { - id: "free-practical", - tabLabel: "무료/실용", - label: "작은 서비스", - intro: "비용 때문에 입력 안전망을 미루지 않게", - title: "작은 서비스도 한국어 입력을 안전하게 다룰 수 있게", - body: "포트폴리오, 학습, 비영리 서비스가 댓글과 게시글 저장 전에 기본 비속어 필터링을 붙일 수 있도록 만든 무료 REST API입니다.", - points: ["무료로 시작", "키 발급 후 호출", "현실적인 첫 안전망"], - proofItems: ["키 발급 후 바로 호출", "문서 보고 붙이기", "상업용은 KISO 권장"], - visualLabel: "추천 대상", - visualTitle: "무료로 시작하는 입력 안전망", - visualLines: ["학습 서비스", "비영리 프로젝트", "사이드 프로젝트"], - }, - { - id: "korean-focused", - tabLabel: "한국어 중심", - label: "한국어 필터", - intro: "영어권 moderation이 놓치는 한국어 입력을 기준으로", - title: "한국어 비속어 사전을 빠르게 검색합니다", - body: "Aho-Corasick Trie로 등록된 비속어 목록을 한 번에 매칭하고, 초성/한글/영문이 섞인 사용자 입력에서도 검출 위치를 계산합니다.", - points: ["빠른 사전 검색", "검출부터 마스킹까지", "단어 목록 동기화"], - proofItems: ["첫 단어만 빠르게 확인", "전체 검출", "마스킹 문장 반환"], - visualLabel: "검출 기준", - visualTitle: "한국어 문장을 기준으로", - visualLines: ["초성 입력", "한글 문장", "영문 혼합"], - }, - { - id: "developer-integration", - tabLabel: "개발자 연동", - label: "REST API", - intro: "서비스 흐름을 크게 바꾸지 않고", - title: "저장 전에 API 한 번으로 붙입니다", - body: "댓글, 채팅, 게시글 저장 직전에 필터 API를 호출하고, 검출 결과와 마스킹된 문장, 응답 코드를 확인하면 됩니다.", - points: ["문장 전달", "동기 또는 콜백 처리", "문서 기반 연동"], - proofItems: ["필터 API 호출", "문장 전달", "응답 코드 확인", "키로 인증"], - visualLabel: "연동 흐름", - visualTitle: "저장 전에 한 번 확인", - visualLines: ["입력 받기", "필터 호출", "결과 반영"], - }, -] as const; - -export const SCENARIO_STEPS: readonly ScenarioStep[] = [ - { - id: "scenario-1", - label: "01", - title: "사용자가 댓글을 입력", - body: "서비스는 저장 전에 필터 API를 호출합니다.", - }, - { - id: "scenario-2", - label: "02", - title: "FILTER 모드로 마스킹", - body: "비속어 위치를 찾고 노출 가능한 문장으로 바꿉니다.", - }, - { - id: "scenario-3", - label: "03", - title: "정리된 문장을 저장", - body: "응답 결과를 댓글, 채팅, 게시글 흐름에 그대로 반영합니다.", - }, -] as const; diff --git a/ui/app/src/constants/pagePath.ts b/ui/app/src/constants/pagePath.ts deleted file mode 100644 index 4c7c00c..0000000 --- a/ui/app/src/constants/pagePath.ts +++ /dev/null @@ -1,22 +0,0 @@ -export type PagePath = "/" | "/login" | "/register" | `/docs${string}`; - -export const HOME_PATH: PagePath = "/"; -export const DOCS_PREFIX = "/docs"; -export const LOGIN_PATH: PagePath = "/login"; -export const REGISTER_PATH: PagePath = "/register"; - -export function getCurrentPagePath(pathname: string): PagePath { - if (pathname.startsWith(DOCS_PREFIX)) { - return pathname as PagePath; - } - - if (pathname === LOGIN_PATH) { - return LOGIN_PATH; - } - - if (pathname === REGISTER_PATH) { - return REGISTER_PATH; - } - - return HOME_PATH; -} diff --git a/ui/app/src/demo/ConsoleDemo.tsx b/ui/app/src/demo/ConsoleDemo.tsx deleted file mode 100644 index 86348c3..0000000 --- a/ui/app/src/demo/ConsoleDemo.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { FormEvent, useMemo, useState } from "react"; - -import type { ClientRegisterRequest, ConsoleResponse, FilterMode, RequestKind } from "./types"; - -const API_BASE_URL = "https://api.kr-filter.com"; - -async function requestJson(endpoint: string, init: RequestInit): Promise { - const response = await fetch(`${API_BASE_URL}${endpoint}`, { - ...init, - headers: { - "Content-Type": "application/json", - ...(init.headers ?? {}), - }, - }); - const contentType = response.headers.get("content-type") ?? ""; - const body = contentType.includes("application/json") ? await response.json() : await response.text(); - - if (!response.ok) { - return { - endpoint, - error: `HTTP ${response.status}`, - ...(typeof body === "object" && body !== null ? body : { status: { code: response.status, message: String(body) } }), - } as TResponse; - } - - return body as TResponse; -} - -export function ConsoleDemo() { - const [activeRequest, setActiveRequest] = useState("filter"); - const [serviceName, setServiceName] = useState(""); - const [email, setEmail] = useState(""); - const [purpose, setPurpose] = useState(""); - const [apiKey, setApiKey] = useState(""); - const [text, setText] = useState(""); - const [mode, setMode] = useState("FILTER"); - const [isSubmitting, setIsSubmitting] = useState(false); - const [result, setResult] = useState(null); - - const requestPreview = useMemo(() => { - if (activeRequest === "api-key") { - return { - endpoint: "POST /api/v1/clients/register", - baseUrl: API_BASE_URL, - body: buildClientRegisterRequest(serviceName, email, purpose), - }; - } - return { - endpoint: "POST /api/v1/filter", - baseUrl: API_BASE_URL, - headers: { "x-api-key": apiKey }, - body: { text, mode }, - }; - }, [activeRequest, apiKey, email, mode, purpose, serviceName, text]); - - async function issueApiKey(event: FormEvent) { - event.preventDefault(); - setActiveRequest("api-key"); - setIsSubmitting(true); - try { - const response = await requestJson("/api/v1/clients/register", { - method: "POST", - body: JSON.stringify(buildClientRegisterRequest(serviceName, email, purpose)), - }); - setResult(response); - } catch (error) { - setResult(toClientError("/api/v1/clients/register", error)); - } finally { - setIsSubmitting(false); - } - } - - async function filter(event: FormEvent) { - event.preventDefault(); - setActiveRequest("filter"); - setIsSubmitting(true); - try { - const response = await requestJson("/api/v1/filter", { - method: "POST", - headers: { "x-api-key": apiKey }, - body: JSON.stringify({ text, mode }), - }); - setResult(response); - } catch (error) { - setResult(toClientError("/api/v1/filter", error)); - } finally { - setIsSubmitting(false); - } - } - - return ( -
-
-
-

profanity-filter-api console

-

API Key 발급과 필터 요청을 실제 서버로 확인합니다

-

- 입력한 값으로 공개 API 서버에 요청을 보내고, 서버 응답 JSON을 그대로 확인합니다. -

-
-
- - API 서버 - {API_BASE_URL} -
-
- -
-
-
- -
- - setServiceName(event.target.value)} /> - - - setEmail(event.target.value)} /> - - -