diff --git a/.claude/hooks/kontext-deps-hook.sh b/.claude/hooks/kontext-deps-hook.sh new file mode 100644 index 000000000..ad066e80d --- /dev/null +++ b/.claude/hooks/kontext-deps-hook.sh @@ -0,0 +1,3 @@ +#!/bin/bash +HOOK_DIR="$(cd "$(dirname "$0")" && pwd)" +bun run "$HOOK_DIR/kontext-deps-hook.ts" diff --git a/.claude/hooks/kontext-deps-hook.ts b/.claude/hooks/kontext-deps-hook.ts new file mode 100644 index 000000000..fefca374b --- /dev/null +++ b/.claude/hooks/kontext-deps-hook.ts @@ -0,0 +1,109 @@ +import { readFileSync, existsSync, statSync, readdirSync } from "node:fs"; +import { resolve, relative, join } from "node:path"; +import { execSync } from "node:child_process"; + +const input = readFileSync("/dev/stdin", "utf-8"); +const { tool_response } = JSON.parse(input); +const filePath: string = tool_response?.filePath; + +if (!filePath) process.exit(0); + +const rootDir = resolve(import.meta.dirname, "../.."); +const relPath = relative(rootDir, resolve(filePath)); + +// kontext 자체 파일 편집은 무시 +if (relPath.startsWith("ecosystem/kontext/") || relPath.startsWith(".kontext/")) { + process.exit(0); +} + +// #4: 그래프 로드 + 캐시 invalidation (kontext.yaml이 더 새로우면 재빌드) +const graphPath = resolve(rootDir, ".kontext/graph.json"); + +function needsRebuild(): boolean { + if (!existsSync(graphPath)) return true; + const graphMtime = statSync(graphPath).mtimeMs; + // kontext.yaml 파일들의 최신 mtime 찾기 + try { + const findYaml = (dir: string): number => { + let maxMtime = 0; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (["node_modules", ".git", "dist", "lib"].includes(entry.name)) continue; + const full = join(dir, entry.name); + if (entry.isDirectory()) maxMtime = Math.max(maxMtime, findYaml(full)); + else if (entry.name === "kontext.yaml") + maxMtime = Math.max(maxMtime, statSync(full).mtimeMs); + } + return maxMtime; + }; + return findYaml(rootDir) > graphMtime; + } catch { + return true; + } +} + +if (needsRebuild()) { + try { + execSync("bun ecosystem/kontext/cli/bin/kontext.mjs build", { + cwd: rootDir, + stdio: "ignore", + timeout: 10000, + }); + } catch { + process.exit(0); + } +} +if (!existsSync(graphPath)) process.exit(0); + +interface GraphEdge { + source: string; + target: string; + reason?: string; + generated: boolean; + command?: string; +} + +interface KontextGraph { + edges: GraphEdge[]; +} + +let graph: KontextGraph; +try { + graph = JSON.parse(readFileSync(graphPath, "utf-8")); +} catch { + process.exit(0); +} + +// 해당 파일이 source인 엣지 찾기 +const deps = graph.edges.filter((e) => e.source === relPath); +if (deps.length === 0) process.exit(0); + +// 결과 포맷팅 +const autoItems = deps.filter((d) => d.generated); +const manualItems = deps.filter((d) => !d.generated); + +const lines: string[] = []; +lines.push(`KONTEXT: ${relPath}을(를) 수정했습니다.`); + +if (manualItems.length > 0) { + lines.push(" 영향받는 파일:"); + for (const item of manualItems) { + const exists = existsSync(resolve(rootDir, item.target)); + const icon = exists ? "●" : "○"; + lines.push(` ${icon} ${item.target}${item.reason ? ` (${item.reason})` : ""}`); + } +} + +if (autoItems.length > 0) { + lines.push(" 자동 생성 (명령 실행 필요):"); + for (const item of autoItems) { + lines.push(` ⚡ ${item.target} → ${item.command ?? "bun generate:all"}`); + } +} + +// exit 0 + stdout JSON으로 additionalContext 전달 +// 에이전트가 다음 행동에 이 맥락을 참고함 +const output = { + additionalContext: lines.join("\n"), +}; +console.log(JSON.stringify(output)); +process.exit(0); diff --git a/.claude/settings.json b/.claude/settings.json index e0e0f900f..1abf36221 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -68,6 +68,16 @@ "command": "bash .claude/hooks/post-edit-tasks.sh" } ] + }, + { + "matcher": "Write|Edit|MultiEdit", + "hooks": [ + { + "type": "command", + "command": "bash .claude/hooks/kontext-deps-hook.sh", + "timeout": 5000 + } + ] } ], "Stop": [ diff --git a/.gitignore b/.gitignore index 36565764a..f120643b6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *.log *.tsbuildinfo .ultra.cache.json +.kontext/ .nyc_output/ @@ -23,3 +24,7 @@ scripts/data .claude/worktrees/* .codex +.playwright-mcp +.superpowers/ +.impeccable.md +.gstack/ diff --git a/bun.lock b/bun.lock index db27122a6..4d060ade6 100644 --- a/bun.lock +++ b/bun.lock @@ -152,6 +152,68 @@ "typescript": "^5.9.2", }, }, + "ecosystem/kontext/cli": { + "name": "@kontext/cli", + "version": "0.0.0", + "bin": { + "kontext": "./bin/kontext.mjs", + }, + "dependencies": { + "@clack/prompts": "^1.0.0", + "@kontext/core": "0.0.0", + "cac": "^6.7.14", + "picocolors": "^1.1.1", + }, + "devDependencies": { + "typescript": "^5.9.2", + }, + }, + "ecosystem/kontext/core": { + "name": "@kontext/core", + "version": "0.0.0", + "dependencies": { + "minimatch": "^10.0.1", + "yaml": "^2.7.1", + }, + "devDependencies": { + "typescript": "^5.9.2", + }, + }, + "ecosystem/kontext/dashboard": { + "name": "@kontext/dashboard", + "version": "0.0.0", + "dependencies": { + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.2.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "geist": "^1.7.0", + "lucide-react": "^1.8.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "shiki": "^4.0.2", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.2", + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "^5.9.2", + "vite": "^6.3.4", + }, + }, "ecosystem/postcss-engaged": { "name": "@seed-design/postcss-engaged", "version": "0.0.0", @@ -1613,10 +1675,12 @@ "@dnd-kit/utilities": ["@dnd-kit/utilities@3.2.2", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg=="], - "@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], + "@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], "@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + "@emotion/hash": ["@emotion/hash@0.9.2", "", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], "@emotion/is-prop-valid": ["@emotion/is-prop-valid@1.2.2", "", { "dependencies": { "@emotion/memoize": "^0.8.1" } }, "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw=="], @@ -1863,6 +1927,12 @@ "@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="], + "@kontext/cli": ["@kontext/cli@workspace:ecosystem/kontext/cli"], + + "@kontext/core": ["@kontext/core@workspace:ecosystem/kontext/core"], + + "@kontext/dashboard": ["@kontext/dashboard@workspace:ecosystem/kontext/dashboard"], + "@kwsites/file-exists": ["@kwsites/file-exists@1.1.1", "", { "dependencies": { "debug": "^4.1.1" } }, "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw=="], "@kwsites/promise-deferred": ["@kwsites/promise-deferred@1.1.1", "", {}, "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="], @@ -2107,10 +2177,20 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], + "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], + "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], + + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], + + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], @@ -2119,20 +2199,38 @@ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="], "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="], + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw=="], + "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="], "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], + "@radix-ui/react-form": ["@radix-ui/react-form@0.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ=="], + + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], + "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], + + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], + + "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="], + "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], + "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg=="], + + "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], @@ -2143,14 +2241,36 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], + + "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], + + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], + + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="], + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="], + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], + + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], + + "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], + + "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.11" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg=="], + + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], + "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="], @@ -2159,6 +2279,8 @@ "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], + "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], + "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], @@ -2205,7 +2327,7 @@ "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.12", "", { "os": "win32", "cpu": "x64" }, "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@rollup/plugin-commonjs": ["@rollup/plugin-commonjs@28.0.9", "", { "dependencies": { "@rollup/pluginutils": "^5.0.1", "commondir": "^1.0.1", "estree-walker": "^2.0.2", "fdir": "^6.2.0", "is-reference": "1.2.1", "magic-string": "^0.30.3", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^2.68.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA=="], @@ -2533,23 +2655,23 @@ "@sentry/react": ["@sentry/react@8.55.0", "", { "dependencies": { "@sentry/browser": "8.55.0", "@sentry/core": "8.55.0", "hoist-non-react-statics": "^3.3.2" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-/qNBvFLpvSa/Rmia0jpKfJdy16d4YZaAnH/TuKLAtm0BWlsPQzbXCU4h8C5Hsst0Do0zG613MEtEmWpWrVOqWA=="], - "@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - "@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="], + "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ=="], + "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], - "@shikijs/langs": ["@shikijs/langs@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA=="], + "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="], "@shikijs/rehype": ["@shikijs/rehype@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@types/hast": "^3.0.4", "hast-util-to-string": "^3.0.1", "shiki": "4.0.2", "unified": "^11.0.5", "unist-util-visit": "^5.1.0" } }, "sha512-cmPlKLD8JeojasNFoY64162ScpEdEdQUMuVodPCrv1nx1z3bjmGwoKWDruQWa/ejSznImlaeB0Ty6Q3zPaVQAA=="], - "@shikijs/themes": ["@shikijs/themes@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ=="], + "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], "@shikijs/transformers": ["@shikijs/transformers@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/types": "4.0.2" } }, "sha512-1+L0gf9v+SdDXs08vjaLb3mBFa8U7u37cwcBQIv/HCocLwX69Tt6LpUCjtB+UUTvQxI7BnjZKhN/wMjhHBcJGg=="], - "@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + "@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], @@ -2629,36 +2751,38 @@ "@swc/types": ["@swc/types@0.1.25", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g=="], - "@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], + "@tailwindcss/node": ["@tailwindcss/node@4.2.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="], - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="], + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="], - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="], + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="], - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="], + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="], - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="], + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="], - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="], + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="], - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="], + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="], - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="], + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="], - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="], + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="], - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="], + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="], - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="], + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="], - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="], + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="], - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="], + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="], - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="], + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="], "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.16", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.16", "@tailwindcss/oxide": "4.1.16", "postcss": "^8.4.41", "tailwindcss": "4.1.16" } }, "sha512-Qn3SFGPXYQMKR/UtqS+dqvPrzEeBZHrFA92maT4zijCVggdsXnDBMsPFJo1eArX3J+O+Gi+8pV4PkqjLCNBk3A=="], + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="], + "@tanstack/react-table": ["@tanstack/react-table@8.21.3", "", { "dependencies": { "@tanstack/table-core": "8.21.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww=="], "@tanstack/react-virtual": ["@tanstack/react-virtual@3.13.16", "", { "dependencies": { "@tanstack/virtual-core": "3.13.16" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y4xLKvLu6UZWiGdNcgk3yYlzCznYIV0m8dSyUzr3eAC0dHLos5V74qhUHxutYddFGgGU8sWLkp6H5c2RCrsrXw=="], @@ -2763,7 +2887,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], "@types/node-forge": ["@types/node-forge@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw=="], @@ -2871,7 +2995,7 @@ "@vitejs/plugin-legacy": ["@vitejs/plugin-legacy@8.0.1", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/preset-env": "^7.29.2", "babel-plugin-polyfill-corejs3": "^0.14.2", "babel-plugin-polyfill-regenerator": "^0.6.8", "browserslist": "^4.28.1", "browserslist-to-esbuild": "^2.1.1", "core-js": "^3.49.0", "magic-string": "^0.30.21", "regenerator-runtime": "^0.14.1", "systemjs": "^6.15.1" }, "peerDependencies": { "terser": "^5.16.0", "vite": "^8.0.0" } }, "sha512-8zeDeuNPqXd49rIVgFgluQYB8vQICHR7l+W2I3CxYK4gTjTorajVr0wLvSjALIwEwLRxBn68EgNVyGP4j6hP7w=="], - "@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + "@vitejs/plugin-react": ["@vitejs/plugin-react@4.7.0", "", { "dependencies": { "@babel/core": "^7.28.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.27", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA=="], "@vitest/expect": ["@vitest/expect@3.2.4", "", { "dependencies": { "@types/chai": "^5.2.2", "@vitest/spy": "3.2.4", "@vitest/utils": "3.2.4", "chai": "^5.2.0", "tinyrainbow": "^2.0.0" } }, "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig=="], @@ -3205,6 +3329,8 @@ "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], + "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], "codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="], @@ -3731,6 +3857,8 @@ "fzf": ["fzf@0.5.2", "", {}, "sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q=="], + "geist": ["geist@1.7.0", "", { "peerDependencies": { "next": ">=13.2.0" } }, "sha512-ZaoiZwkSf0DwwB1ncdLKp+ggAldqxl5L1+SXaNIBGkPAqcu+xjVJLxlf3/S8vLt9UHx1xu5fz3lbzKCj5iOVdQ=="], + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], "generic-names": ["generic-names@4.0.0", "", { "dependencies": { "loader-utils": "^3.2.0" } }, "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A=="], @@ -4173,7 +4301,7 @@ "lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="], - "lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="], + "lucide-react": ["lucide-react@1.8.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw=="], "lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="], @@ -4731,6 +4859,8 @@ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], + "radix-ui": ["radix-ui@1.4.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.16", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-dropdown-menu": "2.1.16", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.8", "@radix-ui/react-hover-card": "1.1.15", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-menubar": "1.1.16", "@radix-ui/react-navigation-menu": "1.2.14", "@radix-ui/react-one-time-password-field": "0.1.8", "@radix-ui/react-password-toggle-field": "0.1.3", "@radix-ui/react-popover": "1.1.15", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.8", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-scroll-area": "1.2.10", "@radix-ui/react-select": "2.2.6", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.6", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.6", "@radix-ui/react-tabs": "1.1.13", "@radix-ui/react-toast": "1.2.15", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-toggle-group": "1.1.11", "@radix-ui/react-toolbar": "1.1.11", "@radix-ui/react-tooltip": "1.2.8", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA=="], + "raf": ["raf@3.4.1", "", { "dependencies": { "performance-now": "^2.1.0" } }, "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA=="], "randombytes": ["randombytes@2.1.0", "", { "dependencies": { "safe-buffer": "^5.1.0" } }, "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ=="], @@ -4773,7 +4903,7 @@ "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], - "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + "react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], @@ -4995,7 +5125,7 @@ "shellwords-ts": ["shellwords-ts@3.0.1", "", {}, "sha512-GabK4ApLMqHFRGlpgNqg8dmtHTnYHt0WUUJkIeMd3QaDrUUBEDXHSSNi3I0PzMimg8W+I0EN4TshQxsnHv1cwg=="], - "shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="], + "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], @@ -5031,6 +5161,8 @@ "sockjs": ["sockjs@0.3.24", "", { "dependencies": { "faye-websocket": "^0.11.3", "uuid": "^8.3.2", "websocket-driver": "^0.7.4" } }, "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ=="], + "sonner": ["sonner@2.0.7", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w=="], + "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], @@ -5139,7 +5271,7 @@ "tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="], - "tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], + "tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="], "tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], @@ -5273,7 +5405,7 @@ "undici": ["undici@7.14.0", "", {}, "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ=="], - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "unenv": ["unenv@2.0.0-rc.21", "", { "dependencies": { "defu": "^6.1.4", "exsolve": "^1.0.7", "ohash": "^2.0.11", "pathe": "^2.0.3", "ufo": "^1.6.1" } }, "sha512-Wj7/AMtE9MRnAXa6Su3Lk0LNCfqDYgfwVjwRFVum9U7wsto1imuHqk4kTm7Jni+5A0Hn7dttL6O/zjvUvoo+8A=="], @@ -5365,7 +5497,7 @@ "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], - "vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], + "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], "vite-node": ["vite-node@3.2.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.1", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg=="], @@ -5657,10 +5789,6 @@ "@cspotcode/source-map-support/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - "@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - - "@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], @@ -5791,18 +5919,30 @@ "@poppinss/dumper/supports-color": ["supports-color@10.2.2", "", {}, "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g=="], + "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-dialog/react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], + "@radix-ui/react-menu/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-menu/react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], - "@radix-ui/react-popover/react-remove-scroll": ["react-remove-scroll@2.7.1", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA=="], + "@radix-ui/react-popover/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + + "@radix-ui/react-select/react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + + "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + + "@radix-ui/react-toolbar/@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + + "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@rspack/dev-server/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -5877,10 +6017,16 @@ "@sanity/uuid/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], + "@seed-design/codemod/@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + "@seed-design/docs/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "@seed-design/docs/shiki": ["shiki@3.20.0", "", { "dependencies": { "@shikijs/core": "3.20.0", "@shikijs/engine-javascript": "3.20.0", "@shikijs/engine-oniguruma": "3.20.0", "@shikijs/langs": "3.20.0", "@shikijs/themes": "3.20.0", "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg=="], + "@seed-design/docs/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@seed-design/docs-mcp/@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + "@seed-design/figma-extractor/@figma/rest-api-spec": ["@figma/rest-api-spec@0.27.0", "", {}, "sha512-jRGYwBLdybit9X7puOmztx8XUt1dq9a7jtBO2KEK4OrxP8GKGX5yY2+efV/XNRHnqTbzFue0qdZklSBs/EaGNw=="], "@seed-design/mcp/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], @@ -5889,17 +6035,23 @@ "@seed-design/qvism-core/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - "@seed-design/tailwind3-plugin/tailwindcss": ["tailwindcss@3.4.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ=="], + "@seed-design/react/@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + + "@seed-design/react/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], - "@shikijs/primitive/@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], + "@seed-design/stackflow/@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], - "@shikijs/rehype/@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], + "@seed-design/stackflow/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], - "@shikijs/rehype/shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], + "@seed-design/stackflow-spa/@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], - "@shikijs/transformers/@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], + "@seed-design/stackflow-spa/@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], - "@shikijs/transformers/@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], + "@seed-design/stackflow-spa/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], + + "@seed-design/tailwind3-plugin/tailwindcss": ["tailwindcss@3.4.18", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ=="], + + "@seed-design/vite-plugin/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], "@sindresorhus/slugify/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -5921,20 +6073,26 @@ "@storybook/react-docgen-typescript-plugin/flat-cache": ["flat-cache@3.2.0", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } }, "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw=="], - "@tailwindcss/node/lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], + "@tailwindcss/node/enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="], "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/node": ["@tailwindcss/node@4.1.16", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.19", "source-map-js": "^1.2.1", "tailwindcss": "4.1.16" } }, "sha512-BX5iaSsloNuvKNHRN3k2RcCuTEgASTo77mofW0vmeHkfrDWaoFAFvNHpEgtu0eqyypcyiBkDWzSMxJhp3AUVcw=="], + + "@tailwindcss/postcss/@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.16", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.16", "@tailwindcss/oxide-darwin-arm64": "4.1.16", "@tailwindcss/oxide-darwin-x64": "4.1.16", "@tailwindcss/oxide-freebsd-x64": "4.1.16", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.16", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.16", "@tailwindcss/oxide-linux-arm64-musl": "4.1.16", "@tailwindcss/oxide-linux-x64-gnu": "4.1.16", "@tailwindcss/oxide-linux-x64-musl": "4.1.16", "@tailwindcss/oxide-wasm32-wasi": "4.1.16", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.16", "@tailwindcss/oxide-win32-x64-msvc": "4.1.16" } }, "sha512-2OSv52FRuhdlgyOQqgtQHuCgXnS8nFSYRp2tJ+4WZXKgTxqPy7SMSls8c3mPT5pkZ17SBToGM5LHEJBO7miEdg=="], + + "@tailwindcss/postcss/tailwindcss": ["tailwindcss@4.1.16", "", {}, "sha512-pONL5awpaQX4LN5eiv7moSiSPd/DLDzKVRJz8Q9PgzmAdd1R4307GQS2ZpfiN7ZmekdQrfhZZiSE5jkLR4WNaA=="], + "@testing-library/jest-dom/aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], "@testing-library/jest-dom/dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], @@ -6001,15 +6159,15 @@ "@vanilla-extract/integration/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], - "@vanilla-extract/vite-plugin/vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="], - "@vitejs/plugin-legacy/@babel/preset-env": ["@babel/preset-env@7.29.2", "", { "dependencies": { "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-import-assertions": "^7.28.6", "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-class-static-block": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-computed-properties": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.15", "babel-plugin-polyfill-corejs3": "^0.14.0", "babel-plugin-polyfill-regenerator": "^0.6.6", "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw=="], "@vitejs/plugin-legacy/core-js": ["core-js@3.49.0", "", {}, "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg=="], - "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "@vitejs/plugin-legacy/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], + + "@vitejs/plugin-react/react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], - "@vitest/mocker/vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="], + "@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "@vue/compiler-core/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], @@ -6151,6 +6309,18 @@ "figma-api/@figma/rest-api-spec": ["@figma/rest-api-spec@0.27.0", "", {}, "sha512-jRGYwBLdybit9X7puOmztx8XUt1dq9a7jtBO2KEK4OrxP8GKGX5yY2+efV/XNRHnqTbzFue0qdZklSBs/EaGNw=="], + "figma-mcp/@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + + "figma-mcp/@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + + "figma-mcp/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], + + "figma-v3-migration/@types/node": ["@types/node@25.1.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA=="], + + "figma-v3-migration/@vitejs/plugin-react": ["@vitejs/plugin-react@6.0.1", "", { "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.7" }, "peerDependencies": { "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", "babel-plugin-react-compiler": "^1.0.0", "vite": "^8.0.0" }, "optionalPeers": ["@rolldown/plugin-babel", "babel-plugin-react-compiler"] }, "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ=="], + + "figma-v3-migration/vite": ["vite@8.0.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.12", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-nmu43Qvq9UopTRfMx2jOYW5l16pb3iDC1JH6yMuPkpVbzK0k+L7dfsEDH4jRgYFmsg0sTAqkojoZgzLMlwHsCQ=="], + "file-entry-cache/flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="], @@ -6179,8 +6349,6 @@ "fumadocs-core/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], - "fumadocs-core/shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="], - "fumadocs-docgen/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], "fumadocs-docgen/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="], @@ -6191,6 +6359,10 @@ "fumadocs-typescript/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "fumadocs-ui/lucide-react": ["lucide-react@0.577.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A=="], + + "fumadocs-ui/react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], + "get-uri/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "get-uri/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -6351,6 +6523,10 @@ "pumpify/pump": ["pump@2.0.1", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA=="], + "radix-ui/@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], + + "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], "react-docgen/@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="], @@ -6507,8 +6683,6 @@ "vite-plugin-checker/npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], - "vite-plugin-singlefile/vite": ["vite@7.3.0", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg=="], - "webpack/browserslist": ["browserslist@4.27.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", "electron-to-chromium": "^1.5.238", "node-releases": "^2.0.26", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw=="], "webpack-bundle-analyzer/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -6715,10 +6889,10 @@ "@create-figma-plugin/build/globby/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], - "@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@eslint/eslintrc/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + "@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "@inquirer/checkbox/@inquirer/core/mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="], "@inquirer/checkbox/@inquirer/core/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], @@ -6811,6 +6985,8 @@ "@octokit/plugin-rest-endpoint-methods/@octokit/types/@octokit/openapi-types": ["@octokit/openapi-types@20.0.0", "", {}, "sha512-EtqRBEjp1dL/15V7WiX5LJMIxxkdiGJnabzYx5Apx4FkQIFgAfKumXeYAqqJCj1s+BMX4cPFIFC4OLCR6stlnA=="], + "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="], + "@rspack/dev-server/chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "@rspack/dev-server/chokidar/readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="], @@ -6883,6 +7059,42 @@ "@sanity/ui/motion/framer-motion": ["framer-motion@12.23.24", "", { "dependencies": { "motion-dom": "^12.23.23", "motion-utils": "^12.23.6", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w=="], + "@seed-design/codemod/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@seed-design/docs-mcp/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@seed-design/docs/shiki/@shikijs/core": ["@shikijs/core@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g=="], + + "@seed-design/docs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg=="], + + "@seed-design/docs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ=="], + + "@seed-design/docs/shiki/@shikijs/langs": ["@shikijs/langs@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA=="], + + "@seed-design/docs/shiki/@shikijs/themes": ["@shikijs/themes@3.20.0", "", { "dependencies": { "@shikijs/types": "3.20.0" } }, "sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ=="], + + "@seed-design/docs/shiki/@shikijs/types": ["@shikijs/types@3.20.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw=="], + + "@seed-design/react/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "@seed-design/react/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "@seed-design/react/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "@seed-design/stackflow-spa/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@seed-design/stackflow-spa/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "@seed-design/stackflow-spa/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "@seed-design/stackflow-spa/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "@seed-design/stackflow/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "@seed-design/stackflow/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "@seed-design/stackflow/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "@seed-design/tailwind3-plugin/tailwindcss/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="], "@seed-design/tailwind3-plugin/tailwindcss/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], @@ -6893,15 +7105,9 @@ "@seed-design/tailwind3-plugin/tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - "@shikijs/rehype/shiki/@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - - "@shikijs/rehype/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - - "@shikijs/rehype/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], + "@seed-design/vite-plugin/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "@shikijs/rehype/shiki/@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], - - "@shikijs/rehype/shiki/@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], + "@seed-design/vite-plugin/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], "@so-ric/colorspace/color/color-convert": ["color-convert@3.1.2", "", { "dependencies": { "color-name": "^2.0.0" } }, "sha512-UNqkvCDXstVck3kdowtOTWROIJQwafjOfXSmddoDrXo4cewMKmusCeF22Q24zvjR8nwWib/3S/dfyzPItPEiJg=="], @@ -6937,46 +7143,70 @@ "@storybook/react/react-docgen/strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], - "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], - "@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.16", "", { "os": "android", "cpu": "arm64" }, "sha512-8+ctzkjHgwDJ5caq9IqRSgsP70xhdhJvm+oueS/yhD5ixLhqTw9fSL1OurzMUhBwE5zK26FXLCz2f/RtkISqHA=="], - "@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.16", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C3oZy5042v2FOALBZtY0JTDnGNdS6w7DxL/odvSny17ORUnaRKhyTse8xYi3yKGyfnTUOdavRCdmc8QqJYwFKA=="], - "@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.16", "", { "os": "darwin", "cpu": "x64" }, "sha512-vjrl/1Ub9+JwU6BP0emgipGjowzYZMjbWCDqwA2Z4vCa+HBSpP4v6U2ddejcHsolsYxwL5r4bPNoamlV0xDdLg=="], - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.16", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TSMpPYpQLm+aR1wW5rKuUuEruc/oOX3C7H0BTnPDn7W/eMw8W+MRMpiypKMkXZfwH8wqPIRKppuZoedTtNj2tg=="], - "@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.16", "", { "os": "linux", "cpu": "arm" }, "sha512-p0GGfRg/w0sdsFKBjMYvvKIiKy/LNWLWgV/plR4lUgrsxFAoQBFrXkZ4C0w8IOXfslB9vHK/JGASWD2IefIpvw=="], - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-DoixyMmTNO19rwRPdqviTrG1rYzpxgyYJl8RgQvdAQUzxC1ToLRqtNJpU/ATURSKgIg6uerPw2feW0aS8SNr/w=="], - "@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.16", "", { "os": "linux", "cpu": "arm64" }, "sha512-H81UXMa9hJhWhaAUca6bU2wm5RRFpuHImrwXBUvPbYb+3jo32I9VIwpOX6hms0fPmA6f2pGVlybO6qU8pF4fzQ=="], - "@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-ZGHQxDtFC2/ruo7t99Qo2TTIvOERULPl5l0K1g0oK6b5PGqjYMga+FcY1wIUnrUxY56h28FxybtDEla+ICOyew=="], - "@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.16", "", { "os": "linux", "cpu": "x64" }, "sha512-Oi1tAaa0rcKf1Og9MzKeINZzMLPbhxvm7rno5/zuP1WYmpiG0bEHq4AcRUiG2165/WUzvxkW4XDYCscZWbTLZw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.16", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-B01u/b8LteGRwucIBmCQ07FVXLzImWESAIMcUU6nvFt/tYsQ6IHz8DmZ5KtvmwxD+iTYBtM1xwoGXswnlu9v0Q=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.16", "", { "os": "win32", "cpu": "arm64" }, "sha512-zX+Q8sSkGj6HKRTMJXuPvOcP8XfYON24zJBRPlszcH1Np7xuHXhWn8qfFjIujVzvH3BHU+16jBXwgpl20i+v9A=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.16", "", { "os": "win32", "cpu": "x64" }, "sha512-m5dDFJUEejbFqP+UXVstd4W/wnxA4F61q8SoL+mqTypId2T2ZpuxosNSgowiCnLp2+Z+rivdU0AqpfgiD7yCBg=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/body-parser/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], + "@types/bonjour/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], + "@types/connect-history-api-fallback/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@types/connect/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@types/express-serve-static-core/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/follow-redirects/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/fs-extra/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/http-proxy/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/jsonfile/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/node-forge/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "@types/react-is/@types/react/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + "@types/send/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/serve-static/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/sockjs/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/tar-stream/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "@types/ws/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], @@ -7135,6 +7365,10 @@ "@vitejs/plugin-legacy/@babel/preset-env/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@vitejs/plugin-legacy/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "@vitejs/plugin-legacy/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "@vue/language-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], "accepts/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], @@ -7149,6 +7383,8 @@ "browserify-sign/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "bun-types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "bunchee/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "bunchee/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], @@ -7213,12 +7449,30 @@ "eslint/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "eval/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], "execa/npm-run-path/unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], "express/mime-types/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + "figma-mcp/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "figma-mcp/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "figma-mcp/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "figma-mcp/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + + "figma-v3-migration/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + + "figma-v3-migration/@vitejs/plugin-react/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.7", "", {}, "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA=="], + + "figma-v3-migration/vite/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "figma-v3-migration/vite/postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], + "file-entry-cache/flat-cache/keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], @@ -7245,18 +7499,6 @@ "ftp/readable-stream/string_decoder": ["string_decoder@0.10.31", "", {}, "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ=="], - "fumadocs-core/shiki/@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="], - - "fumadocs-core/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="], - - "fumadocs-core/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="], - - "fumadocs-core/shiki/@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="], - - "fumadocs-core/shiki/@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="], - - "fumadocs-core/shiki/@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="], - "get-uri/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], "get-uri/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -7269,6 +7511,8 @@ "gunzip-maybe/through2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "happy-dom/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "hosted-git-info/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "hpack.js/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -7297,6 +7541,8 @@ "inquirer/@inquirer/prompts/@inquirer/select": ["@inquirer/select@4.4.2", "", { "dependencies": { "@inquirer/ansi": "^1.0.2", "@inquirer/core": "^10.3.2", "@inquirer/figures": "^1.0.15", "@inquirer/type": "^3.0.10", "yoctocolors-cjs": "^2.1.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w=="], + "jest-worker/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "jscodeshift/@babel/core/@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="], "jscodeshift/@babel/core/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="], @@ -7797,15 +8043,39 @@ "@storybook/react/react-docgen/@babel/traverse/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="], - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], + + "@tailwindcss/postcss/@tailwindcss/node/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="], @@ -8031,7 +8301,21 @@ "@storybook/react/react-docgen/@babel/core/@babel/helper-module-transforms/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="], - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core": ["@emnapi/core@1.7.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@vanilla-extract/babel-plugin-debug-ids/@babel/core/@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ=="], @@ -8131,6 +8415,16 @@ "@storybook/react/react-docgen/@babel/core/@babel/helper-compilation-targets/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/runtime/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "@vanilla-extract/integration/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "eslint/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], @@ -8144,5 +8438,7 @@ "sanity/@vitejs/plugin-react/@babel/core/@babel/helper-compilation-targets/browserslist/node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="], "sanity/@vitejs/plugin-react/@babel/core/@babel/helper-compilation-targets/browserslist/update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="], + + "@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], } } diff --git a/docs/superpowers/specs/2026-04-15-kontext-dashboard-redesign.md b/docs/superpowers/specs/2026-04-15-kontext-dashboard-redesign.md new file mode 100644 index 000000000..c548ad793 --- /dev/null +++ b/docs/superpowers/specs/2026-04-15-kontext-dashboard-redesign.md @@ -0,0 +1,262 @@ +# Kontext Dashboard Redesign + +## Context + +The Kontext dashboard (`kontext serve`) currently has two views — a cytoscape.js graph (697 nodes, 873 edges, poor readability) and a completeness matrix table. The graph visualization is too dense to be useful, and the matrix only shows component-level coverage. + +The team needs a dashboard that clearly shows **which files affect which other files** based on kontext.yaml declarations, with both forward (source → targets) and reverse (target → sources) navigation. Additionally, the team wants the ability to **create and edit kontext.yaml files visually** instead of writing YAML by hand. + +The visual redesign adopts a modern dark UI with dithering texture accents and pixel font headings, inspired by Vercel's Geist Pixel aesthetic — not retro game art, but contemporary design with textural depth. + +## Scope + +**In scope:** +- Replace existing Graph and Matrix views with a single Explorer view +- Add an Editor view for visual kontext.yaml creation/editing +- Full Tailwind CSS + shadcn/ui migration (remove all inline styles) +- Dithering background textures + Geist Pixel font accents +- Extend serve.ts API for file browsing and config read/write + +**Out of scope:** +- Real-time file watching / hot reload of graph +- Drag-and-drop between Explorer view nodes +- Multi-user / collaboration features +- Mobile responsive layout (desktop-only tool) + +## Architecture + +### Pages + +Two pages accessible via tab navigation in the header: + +1. **Explorer** — Read-only visualization of kontext.yaml dependency relationships +2. **Editor** — Visual editor for creating and modifying kontext.yaml files + +### API Endpoints (serve.ts) + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/graph` | Existing — full dependency graph | +| GET | `/api/files?dir=` | List directory contents (name, type, path) | +| GET | `/api/config/` | Read kontext.yaml for a package | +| POST | `/api/config/` | Write kontext.yaml for a package | +| POST | `/api/rebuild` | Rebuild graph from current kontext.yaml files | + +Security: All file paths validated against repo root (existing `startsWith` guard pattern). `/api/files` only serves directories under the repo root. `/api/config` only reads/writes `kontext.yaml` files. + +### Data Flow + +``` +kontext.yaml files → buildGraph() → /api/graph → Explorer view + ↕ +Editor view → /api/config POST → kontext.yaml → /api/rebuild → updated graph +``` + +## Explorer View + +### Layout: Sidebar + Detail Panel + +**Left sidebar (240px fixed):** +- Package list showing only packages with kontext.yaml +- Each item: package name + relation count badge +- Selected package highlighted +- Packages without kontext.yaml shown dimmed at bottom (optional section) + +**Right detail panel (remaining width):** +- Package header: name, kontext.yaml file path, total relation/edge count +- Relations grouped by `when` pattern, each in a collapsible card: + - Card header: glob pattern in monospace font + - Card body: list of affected files with: + - Full relative path + - Existence indicator: checkmark (exists), X (missing), warning (optional) + - Generated badge with command tooltip + - Reason text in muted color + - Override sections shown inline with distinct styling +- Empty state when no package selected + +### Search (Command+K) + +Global search dialog (shadcn Command component): +- Input: file path substring +- Results split into two groups: + - **"Affects" (forward)**: files this source affects (uses `findDeps` from core) + - **"Affected by" (reverse)**: sources that affect this file (uses `findAffectedBy` from core) +- Clicking a result navigates to the relevant package and highlights the relation + +### shadcn Components Used + +- `Tabs` — page navigation (Explorer / Editor) +- `ScrollArea` — sidebar and detail panel scrolling +- `Collapsible` — when-group cards +- `Badge` — relation counts, status indicators +- `Command` — global search dialog (cmdk) +- `Tooltip` — reason text, command details +- `Button` — actions +- `Separator` — visual dividers + +## Editor View + +### Layout: 3-Panel + +**Left panel — File Tree (240px):** +- Directory tree loaded via `/api/files` endpoint +- Lazy-loaded: expand directories on click +- Draggable items (files and folders) for dropping into affects zones +- Visual indicator on packages that have kontext.yaml + +**Center panel — Relation Editor (flexible):** +- Package selector at top (dropdown of all packages) +- If kontext.yaml exists: loads current relations +- If not: starts with empty state + "Create kontext.yaml" prompt +- Each relation as an editable card: + - `when` field: text input for glob pattern + - `affects` area: drop zone for files from tree + manual path input + - Per-affect options: reason (text), optional (toggle), generated (toggle + command input) + - Delete relation button +- "Add Relation" button at bottom +- Package-level `ignore` patterns editor (collapsible section) + +**Right panel — YAML Preview (320px):** +- Live-rendered YAML from current editor state +- Syntax highlighted with Geist Mono font +- Schema validation indicators (green = valid, red = errors) +- "Save" button: POST to `/api/config/:packageDir` +- After save: automatically calls `/api/rebuild` and shows success toast + +### Drag & Drop + +- Files/folders dragged from left tree into center "affects" drop zones +- On drop: auto-generates the relative path from package root +- Supports dropping folders (creates glob pattern like `dropped-folder/**`) +- Visual feedback: drop zone highlights on dragover + +## Visual Design + +### Design Philosophy + +Modern, clean dark UI with dithering texture accents and pixel typography as decorative elements. NOT retro game art — think Vercel's design language with textural depth. + +### Typography + +| Role | Font | Usage | +|------|------|-------| +| Logo / Page titles | GeistPixelSquare | Header "KONTEXT", page titles | +| Code / File paths | Geist Mono | YAML preview, glob patterns, file paths | +| Body text | Geist Sans | Labels, descriptions, UI text | + +Font installation via `geist` npm package. + +### Color Palette (Dark Theme) + +| Token | Value | Usage | +|-------|-------|-------| +| Background | `#0a0a0a` | Page background | +| Surface | `#111111` | Cards, panels | +| Border | `#222222` | Card borders, dividers | +| Text primary | `#e0e0e0` | Main text | +| Text secondary | `#888888` | Labels, descriptions | +| Text muted | `#555555` | Dimmed content | +| Accent blue | `#4a9eff` | Links, highlights | +| Success green | `#22c55e` | File exists indicator | +| Warning amber | `#f59e0b` | Optional indicator | +| Error red | `#ef4444` | Missing file indicator | + +### Package Colors (carried from existing) + +| Package | Color | +|---------|-------| +| packages/rootage | `#4a9eff` | +| packages/qvism-preset | `#22c55e` | +| packages/css | `#a78bfa` | +| packages/react | `#f472b6` | +| packages/react-headless | `#fb923c` | +| packages/cli | `#facc15` | +| docs | `#38bdf8` | +| ecosystem | `#6b7280` | + +### Dithering Effects + +- **Page background**: Subtle Bayer matrix dithering pattern as CSS repeating background (SVG tile or CSS gradient). Low contrast against base color. +- **Card hover**: Slight dithering intensity shift on hover transition +- **Section dividers**: Dithering gradient fade between sections +- **Implementation**: React Bits dither background component or Aceternity dither shader, evaluated during implementation for best fit + +### Status Indicators + +Pixel-styled but small and inline: +- Exists: `checkmark` icon in green +- Missing: `x` icon in red +- Optional: `minus` icon in amber +- Generated: `zap` icon in blue + +## Tech Stack Changes + +### Added Dependencies + +- `tailwindcss` (latest via bun add) +- `@tailwindcss/vite` (Vite plugin) +- shadcn/ui components (installed via CLI) +- `geist` (font package) +- `react-bits` or `aceternity` dither component (evaluate during implementation) + +### Removed Dependencies + +- `cytoscape` — no longer needed (Graph view removed) + +### File Changes + +**Removed:** +- `src/views/GraphExplorer.tsx` +- `src/views/CompletenessMatrix.tsx` + +**Added:** +- `src/views/Explorer.tsx` — main explorer view +- `src/views/Editor.tsx` — YAML editor view +- `src/components/PackageSidebar.tsx` — package list sidebar +- `src/components/RelationCard.tsx` — when→affects group card +- `src/components/FileTree.tsx` — draggable file tree +- `src/components/RelationEditor.tsx` — individual relation form +- `src/components/YamlPreview.tsx` — live YAML preview +- `src/components/SearchDialog.tsx` — Command+K search +- `src/components/DitherBackground.tsx` — background texture +- `src/hooks/useGraph.ts` — updated with rebuild capability +- `src/hooks/useFileTree.ts` — file tree data fetching +- `src/hooks/useConfig.ts` — kontext.yaml CRUD +- `src/lib/utils.ts` — shadcn utility (cn function) +- `components.json` — shadcn configuration +- `tailwind.config.ts` (or CSS-based Tailwind v4 config) +- `src/index.css` — global styles + Tailwind imports + +**Modified:** +- `src/App.tsx` — new layout with Tabs, header redesign +- `src/types.ts` — add FileEntry, ConfigState types +- `../cli/src/commands/serve.ts` — new API endpoints + +## Verification + +### Manual Testing + +1. `bun ecosystem/kontext/cli/bin/kontext.mjs build` — graph builds +2. `bun ecosystem/kontext/cli/bin/kontext.mjs serve` — dashboard opens +3. Explorer view: + - Packages listed in sidebar with correct relation counts + - Click package → relations display with correct when/affects mapping + - File existence indicators match actual filesystem + - Cmd+K search finds files in both directions +4. Editor view: + - File tree loads repo structure + - Existing kontext.yaml loads into editor + - Drag file from tree → adds to affects list + - YAML preview updates in real-time + - Save → writes valid kontext.yaml to disk + - After save → Explorer view reflects changes +5. Visual: + - Dithering background visible + - Geist Pixel font on headers + - Geist Mono on code/paths + - Dark theme consistent across views + +### Automated + +- Existing core tests pass: `cd ecosystem/kontext/core && bun test` +- Dashboard builds without errors: `cd ecosystem/kontext/dashboard && bun run build` diff --git a/docs/superpowers/specs/2026-04-16-kontext-unified-view-design.md b/docs/superpowers/specs/2026-04-16-kontext-unified-view-design.md new file mode 100644 index 000000000..d06d8c2ad --- /dev/null +++ b/docs/superpowers/specs/2026-04-16-kontext-unified-view-design.md @@ -0,0 +1,136 @@ +# Kontext Dashboard: Unified View Design + +## Context + +Kontext는 모노레포 내 파일 간 의존성을 `kontext.yaml`로 정의하고, `kontext serve`로 대시보드를 띄워 탐색/편집하는 도구다. + +기존 대시보드는 Explorer(탐색)와 Editor(편집) 두 탭으로 나뉘어 있었다. Explorer는 3번의 반복(리스트 → React Flow 그래프 → 듀얼 파일 트리)을 거쳤지만, 모두 근본적인 문제가 있었다: +- 파일 트리 안에 화살표를 그리는 건 업계에서 성공 사례가 없음 (스파게티, 스크롤 컨텍스트 유실, 폴더 접힘 문제) +- Explorer와 Editor가 같은 파일 트리를 중복으로 보여줌 +- 두 뷰가 분리되어 있어 "편집하면서 영향 범위 확인"이 불가 + +## Decision + +Explorer를 제거하고, Editor에 의존성 시각화를 통합한 **단일 뷰**로 재작성한다. + +## Layout + +3패널 단일 뷰 (탭 없음): + +``` +┌──────────────┬──────────────────────────┬──────────────────┐ +│ Left Panel │ Center Panel │ Right Panel │ +│ (resizable) │ (flex-1) │ (resizable) │ +│ │ │ │ +│ KONTEXTS [+] │ Relation editor │ YAML PREVIEW │ +│ ✨ cli │ (when/affects forms) │ (live preview) │ +│ ✨ react │ │ [Save] │ +│ │ Each affects item shows │ │ +│ ──────────── │ ✓/✗/? badge │ │ +│ REPOSITORY │ │ │ +│ │ ignore patterns │ │ +│ (File tree │ │ │ +│ with inline │ │ │ +│ highlights) │ │ │ +└──────────────┴──────────────────────────┴──────────────────┘ +``` + +## Core Interactions + +### 1. kontext.yaml 선택 → 트리 하이라이트 + +KONTEXTS 섹션에서 kontext.yaml을 클릭하면: +- 가운데 패널: when/affects 편집 폼 로드 (기존 동작) +- 왼쪽 트리: 해당 kontext.yaml에 정의된 **모든 when/affects 파일** 하이라이트 + - 🔵 blue dot: when 파일 (소스) + - 🟢 green: affects 파일, exists + - 🔴 red: affects 파일, missing + - 🟡 yellow: affects 파일, optional +- 해당 파일이 있는 폴더 **자동 펼침** (loadDir 호출 후 expand) + +하이라이트 데이터 흐름: +``` +selectedPackage + → config (kontext.yaml 내용) + → relations[] (파싱된 when/affects) + → highlightedPaths: Map + → FileTree에 전달 +``` + +### 2. 편집 패널 내 상태 표시 + +각 affects 항목에: +- `✓` / `✗` / `?` 아이콘 (graph.nodes에서 exists 체크) +- 클릭 시 왼쪽 트리에서 해당 파일로 스크롤 + 강조 깜빡임 (scrollIntoView + flash animation) + +### 3. 파일 트리에서 역방향 탐색 + +트리에서 임의의 파일 클릭 시: +- 해당 파일이 어떤 kontext.yaml에 포함되어 있는지 graph.edges에서 검색 +- 포함된 kontext.yaml이 있으면 → 자동으로 해당 패키지 선택 + 관련 relation 하이라이트 + +### 4. 기존 기능 유지 + +- when/affects 폼 편집, per-affect reason 입력 +- 파일 트리에서 affects 영역으로 드래그앤드롭 +- Undo/Redo (Cmd+Z / Cmd+Shift+Z) +- YAML 프리뷰 (실시간) + Save 버튼 +- New kontext.yaml 생성 (+ 버튼) +- Cmd+K 검색 +- Sonner 토스트 피드백 + +## Components + +### New: `useHighlightedPaths` hook + +```typescript +function useHighlightedPaths( + relations: EditableRelation[], + graph: KontextGraph, +): Map +``` + +relations 배열에서 모든 when/affects 경로를 추출하고, graph.nodes에서 exists 여부를 확인해서 상태별 색상 맵을 반환한다. + +### Modified: `FileTree.tsx` + +기존 `highlightedPaths: Set` → `highlightedPaths: Map`으로 변경해서 상태별 다른 색상 적용. + +- `'when'`: blue dot + blue tint +- `'exists'`: green dot + green tint +- `'missing'`: red dot + red tint +- `'optional'`: yellow dot + yellow tint + +폴더 자동 펼침: `autoExpandPaths: string[]` prop 추가. 하이라이트된 파일의 부모 폴더를 자동으로 loadDir + expand. + +### Modified: `App.tsx` + +Explorer/Editor 탭 제거. 단일 KontextView 컴포넌트만 렌더링. + +### New: `KontextView.tsx` + +기존 Editor.tsx의 로직을 가져오되, highlightedPaths 통합. Explorer.tsx와 관련 파일(DependencyTree, ConnectionLines) 삭제. + +## Files to Change + +| File | Action | +|------|--------| +| `src/views/KontextView.tsx` | New: Editor + highlight 통합 단일 뷰 | +| `src/hooks/useHighlightedPaths.ts` | New: relations → highlight map 변환 | +| `src/components/FileTree.tsx` | Modify: Set→Map, 상태별 색상, autoExpandPaths | +| `src/App.tsx` | Modify: 탭 제거, KontextView만 렌더링 | +| `src/views/Explorer.tsx` | Delete | +| `src/components/DependencyTree.tsx` | Delete | +| `src/components/ConnectionLines.tsx` | Delete | + +## Verification + +1. `bun vite build` 성공 +2. `kontext serve` → 단일 뷰 로드 +3. KONTEXTS에서 `cli` 클릭 → 편집 패널 + 트리 하이라이트 동시 동작 +4. affects 파일에 ✓/✗/? 배지 표시 +5. affects 항목 클릭 → 트리에서 해당 파일로 스크롤 +6. 파일 편집 (when/affects 추가/삭제) + Undo/Redo 동작 +7. Save → YAML 저장 성공 +8. 드래그앤드롭 동작 +9. Cmd+K 검색 동작 diff --git a/ecosystem/kontext/cli/bin/kontext.mjs b/ecosystem/kontext/cli/bin/kontext.mjs new file mode 100644 index 000000000..d903a07e3 --- /dev/null +++ b/ecosystem/kontext/cli/bin/kontext.mjs @@ -0,0 +1,549 @@ +#!/usr/bin/env node +import { cac } from 'cac'; +import { mkdirSync, writeFileSync, readFileSync, existsSync, statSync, readdirSync } from 'node:fs'; +import { resolve, isAbsolute, relative, join, extname, dirname, normalize } from 'node:path'; +import { buildGraph, checkCompleteness, findDeps, lint } from '@kontext/core'; +import pc from 'picocolors'; +import { createServer } from 'node:http'; + +function buildCommand(cli) { + cli.command("build", "Build the dependency graph from all kontext.yaml files").option("--root ", "Repository root directory", { + default: process.cwd() + }).action((options)=>{ + const rootDir = resolve(options.root); + console.log(pc.dim("Scanning for kontext.yaml files...")); + const graph = buildGraph({ + rootDir + }); + const outDir = resolve(rootDir, ".kontext"); + mkdirSync(outDir, { + recursive: true + }); + const outPath = resolve(outDir, "graph.json"); + writeFileSync(outPath, JSON.stringify(graph, null, 2)); + console.log(`${pc.green("✓")} Graph built: ${pc.bold(String(graph.nodes.length))} nodes, ${pc.bold(String(graph.edges.length))} edges`); + console.log(` ${pc.dim("Packages:")} ${graph.packages.join(", ")}`); + console.log(` ${pc.dim("Output:")} ${outPath}`); + }); +} + +function renderDepsTree(filePath, deps) { + const lines = []; + lines.push(pc.bold(filePath)); + for(let i = 0; i < deps.length; i++){ + const dep = deps[i]; + const isLast = i === deps.length - 1; + const prefix = isLast ? "└─" : "├─"; + const existsIcon = dep.exists ? pc.green("●") : pc.red("○"); + let label = ""; + if (dep.generated) { + label = `${pc.cyan("[auto]")} ${dep.path}`; + if (dep.command) { + label += ` ${pc.dim(`→ ${dep.command}`)}`; + } + } else { + label = dep.path; + if (dep.reason) { + label += ` ${pc.dim(`— ${dep.reason}`)}`; + } + } + lines.push(`${prefix} ${existsIcon} ${label}`); + } + return lines.join("\n"); +} +function renderCheckResults(results) { + const lines = []; + // definedBy별로 그룹핑 + const bySource = new Map(); + for (const result of results){ + const key = result.definedBy; + const list = bySource.get(key) ?? []; + list.push(result); + bySource.set(key, list); + } + for (const [definedBy, group] of bySource){ + lines.push(pc.dim(`── ${definedBy} ──`)); + for (const result of group){ + if (result.missing.length === 0) { + lines.push(`${pc.green("✅")} ${result.source}: ${result.existing}/${result.total}`); + } else { + lines.push(`${pc.yellow("⚠️")} ${result.source}: ${result.existing}/${result.total} — missing:`); + for (const m of result.missing){ + lines.push(` ${pc.dim("└─")} ${pc.red(m)}`); + } + } + } + lines.push(""); + } + return lines.join("\n"); +} +function renderDepsJson(deps) { + return JSON.stringify(deps, null, 2); +} +function renderLintResults(result) { + const lines = []; + if (result.suggestions.length > 0) { + lines.push(pc.bold("Discovered relationships:")); + lines.push(""); + // 레이어별 그룹핑 + for (const layer of [ + "naming", + "import", + "co-change" + ]){ + const items = result.suggestions.filter((s)=>s.layer === layer); + if (items.length === 0) continue; + lines.push(pc.cyan(` [${layer}] ${items.length} suggestions`)); + for (const item of items.slice(0, 10)){ + const conf = `${(item.confidence * 100).toFixed(0)}%`; + lines.push(` ${pc.dim("├─")} ${item.source} ${pc.dim("↔")} ${item.target}`); + lines.push(` ${pc.dim("│")} ${pc.dim(item.detail)} ${pc.dim(`(${conf})`)}`); + } + if (items.length > 10) { + lines.push(` ${pc.dim(`└─ ... and ${items.length - 10} more`)}`); + } + lines.push(""); + } + } else { + lines.push(pc.green("No undeclared relationships found.")); + } + if (result.staleWarnings.length > 0) { + lines.push(pc.bold("Stale relationships:")); + lines.push(""); + for (const warn of result.staleWarnings.slice(0, 10)){ + lines.push(` ${pc.yellow("⚠")} ${warn.source} ${pc.dim("→")} ${warn.target}`); + lines.push(` ${pc.dim(warn.reason)}`); + } + if (result.staleWarnings.length > 10) { + lines.push(` ${pc.dim(`... and ${result.staleWarnings.length - 10} more`)}`); + } + } + return lines.join("\n"); +} + +function loadOrBuildGraph(rootDir) { + const graphPath = resolve(rootDir, ".kontext", "graph.json"); + try { + const raw = readFileSync(graphPath, "utf-8"); + return JSON.parse(raw); + } catch { + return buildGraph({ + rootDir + }); + } +} + +function checkCommand(cli) { + cli.command("check", "Verify all affected paths exist").option("--root ", "Repository root directory", { + default: process.cwd() + }).option("--ci", "Exit with code 1 if any files are missing").action((options)=>{ + const rootDir = resolve(options.root); + const graph = loadOrBuildGraph(rootDir); + const results = checkCompleteness(graph); + const hasMissing = results.some((r)=>r.missing.length > 0); + console.log(renderCheckResults(results)); + if (hasMissing) { + console.log(""); + console.log(pc.yellow("Some affected files are missing.")); + if (options.ci) { + process.exit(1); + } + } else { + console.log(""); + console.log(pc.green("All affected files exist.")); + } + }); +} + +function depsCommand(cli) { + cli.command("deps ", "Show all files affected by changes to ").option("--root ", "Repository root directory", { + default: process.cwd() + }).option("--json", "Output as JSON").action((file, options)=>{ + const rootDir = resolve(options.root); + const graph = loadOrBuildGraph(rootDir); + const absFile = isAbsolute(file) ? file : resolve(rootDir, file); + const relFile = relative(rootDir, absFile); + const deps = findDeps(graph, relFile); + if (deps.length === 0) { + console.log(`No relations found for ${relFile}`); + console.log("Tip: Make sure the file matches a 'when' pattern in a kontext.yaml"); + return; + } + if (options.json) { + console.log(renderDepsJson(deps)); + } else { + console.log(renderDepsTree(relFile, deps)); + } + }); +} + +function lintCommand(cli) { + cli.command("lint", "Discover undeclared relationships from git history, naming patterns, and imports").option("--root ", "Repository root directory", { + default: process.cwd() + }).option("--commits ", "Number of commits to analyze", { + default: 200 + }).option("--threshold ", "Jaccard similarity threshold", { + default: 0.7 + }).option("--min-co ", "Minimum co-occurrences", { + default: 3 + }).option("--json", "Output as JSON").option("--fix", "Auto-apply suggestions to kontext.yaml files (experimental, not yet implemented)").action((options)=>{ + const rootDir = resolve(options.root); + if (!options.json) { + console.log(pc.dim("Analyzing repository...")); + } + const result = lint({ + rootDir, + commitCount: options.commits, + jaccardThreshold: options.threshold, + minCoOccurrences: options.minCo + }); + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + console.log(renderLintResults(result)); + if (result.suggestions.length > 0 && !options.fix) { + console.log(""); + console.log(pc.dim("Apply suggestions with: kontext lint --fix")); + } + if (options.fix && result.suggestions.length > 0) { + console.log(""); + console.log(pc.yellow("--fix is not yet implemented. Coming soon.")); + } + }); +} + +const MIME_TYPES = { + ".html": "text/html", + ".js": "application/javascript", + ".mjs": "application/javascript", + ".css": "text/css", + ".json": "application/json", + ".woff2": "font/woff2", + ".woff": "font/woff", + ".ttf": "font/ttf", + ".svg": "image/svg+xml" +}; +const SKIP_DIRS = new Set([ + "node_modules", + ".git", + "dist", + "lib", + ".kontext", + ".next", + ".turbo" +]); +const BINARY_EXTS = new Set([ + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".ico", + ".svg", + ".woff", + ".woff2", + ".ttf", + ".eot", + ".otf", + ".wasm", + ".zip", + ".tar", + ".gz", + ".br", + ".pdf", + ".mp3", + ".mp4", + ".webm" +]); +function jsonResponse(res, statusCode, data) { + res.writeHead(statusCode, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + }); + res.end(JSON.stringify(data)); +} +function parseBody(req) { + return new Promise((resolve, reject)=>{ + let body = ""; + req.on("data", (chunk)=>{ + body += chunk; + if (body.length > 1_000_000) { + reject(new Error("Body too large")); + } + }); + req.on("end", ()=>resolve(body)); + req.on("error", reject); + }); +} +function isPathSafe(targetPath, rootDir) { + const normalized = normalize(targetPath); + const rootPrefix = rootDir + "/"; + return normalized === rootDir || normalized.startsWith(rootPrefix); +} +function serveCommand(cli) { + cli.command("serve", "Start the Kontext dashboard").option("--root ", "Repository root directory", { + default: process.cwd() + }).option("--port ", "Port number", { + default: 4321 + }).action(async (options)=>{ + const rootDir = resolve(options.root); + const dashboardDir = resolve(rootDir, "ecosystem/kontext/dashboard/dist"); + const dashboardDirPrefix = dashboardDir + "/"; + // Mutable graph cache + let graph = buildGraph({ + rootDir + }); + let graphJson = JSON.stringify(graph); + const server = createServer(async (req, res)=>{ + const rawUrl = req.url ?? "/"; + const [urlPath, queryString] = rawUrl.split("?"); + const url = urlPath ?? "/"; + const method = req.method ?? "GET"; + const params = new URLSearchParams(queryString ?? ""); + // CORS headers for API + if (url.startsWith("/api/")) { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type"); + if (method === "OPTIONS") { + res.writeHead(204); + res.end(); + return; + } + } + try { + // --- API: GET /api/graph --- + if (url === "/api/graph" && method === "GET") { + res.writeHead(200, { + "Content-Type": "application/json" + }); + res.end(graphJson); + return; + } + // --- API: GET /api/workspaces --- + if (url === "/api/workspaces" && method === "GET") { + try { + const pkgPath = join(rootDir, "package.json"); + const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8")); + const workspaceGlobs = pkgJson.workspaces ?? []; + const workspaces = []; + for (const pattern of workspaceGlobs){ + if (pattern.endsWith("/*")) { + // Expand glob + const dir = join(rootDir, pattern.slice(0, -2)); + if (existsSync(dir) && statSync(dir).isDirectory()) { + for (const entry of readdirSync(dir, { + withFileTypes: true + })){ + if (entry.isDirectory()) { + const wsPath = join(dir, entry.name, "package.json"); + if (existsSync(wsPath)) { + workspaces.push(pattern.slice(0, -2) + "/" + entry.name); + } + } + } + } + } else { + // Direct path + const wsPath = join(rootDir, pattern, "package.json"); + if (existsSync(wsPath)) { + workspaces.push(pattern); + } + } + } + jsonResponse(res, 200, workspaces.sort()); + } catch { + jsonResponse(res, 200, []); + } + return; + } + // --- API: GET /api/files --- + if (url === "/api/files" && method === "GET") { + const dir = params.get("dir") ?? ""; + const targetDir = dir ? join(rootDir, dir) : rootDir; + if (!isPathSafe(targetDir, rootDir)) { + jsonResponse(res, 403, { + error: "Forbidden" + }); + return; + } + if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) { + jsonResponse(res, 404, { + error: "Directory not found" + }); + return; + } + const entries = readdirSync(targetDir, { + withFileTypes: true + }).filter((e)=>!SKIP_DIRS.has(e.name) && !e.name.startsWith(".")).map((e)=>({ + name: e.name, + type: e.isDirectory() ? "directory" : "file", + path: dir ? `${dir}/${e.name}` : e.name + })).sort((a, b)=>{ + if (a.type !== b.type) return a.type === "directory" ? -1 : 1; + return a.name.localeCompare(b.name); + }); + jsonResponse(res, 200, entries); + return; + } + // --- API: GET /api/file-content --- + if (url === "/api/file-content" && method === "GET") { + const filePath = params.get("path") ?? ""; + if (!filePath) { + jsonResponse(res, 400, { + error: "Missing path parameter" + }); + return; + } + const targetPath = join(rootDir, filePath); + if (!isPathSafe(targetPath, rootDir)) { + jsonResponse(res, 403, { + error: "Forbidden" + }); + return; + } + let stat; + try { + stat = statSync(targetPath); + } catch { + jsonResponse(res, 404, { + error: "File not found" + }); + return; + } + if (!stat.isFile()) { + jsonResponse(res, 404, { + error: "File not found" + }); + return; + } + if (stat.size > 512 * 1024) { + jsonResponse(res, 413, { + error: "File too large (max 512KB)" + }); + return; + } + const ext = extname(targetPath).toLowerCase(); + if (BINARY_EXTS.has(ext)) { + jsonResponse(res, 415, { + error: "Binary file", + binary: true + }); + return; + } + try { + const content = readFileSync(targetPath, "utf-8"); + jsonResponse(res, 200, { + content, + path: filePath, + size: stat.size, + extension: ext + }); + } catch { + jsonResponse(res, 500, { + error: "Failed to read file" + }); + } + return; + } + // --- API: GET/POST /api/config/:packageDir --- + const configMatch = url.match(/^\/api\/config\/(.+)$/); + if (configMatch) { + const packageDir = decodeURIComponent(configMatch[1]); + const configPath = join(rootDir, packageDir, "kontext.yaml"); + if (!isPathSafe(configPath, rootDir)) { + jsonResponse(res, 403, { + error: "Forbidden" + }); + return; + } + if (method === "GET") { + if (existsSync(configPath) && statSync(configPath).isFile()) { + const content = readFileSync(configPath, "utf-8"); + jsonResponse(res, 200, { + content, + exists: true + }); + } else { + jsonResponse(res, 200, { + content: "", + exists: false + }); + } + return; + } + if (method === "POST") { + const body = await parseBody(req); + const { content } = JSON.parse(body); + const dir = dirname(configPath); + mkdirSync(dir, { + recursive: true + }); + writeFileSync(configPath, content, "utf-8"); + jsonResponse(res, 200, { + success: true + }); + return; + } + } + // --- API: POST /api/rebuild --- + if (url === "/api/rebuild" && method === "POST") { + graph = buildGraph({ + rootDir + }); + graphJson = JSON.stringify(graph); + jsonResponse(res, 200, graph); + return; + } + // --- Static file serving --- + const sanitizedUrl = normalize(url).replace(/^(\.\.[/\\])+/, ""); + const filePath = url === "/" ? join(dashboardDir, "index.html") : join(dashboardDir, sanitizedUrl); + const resolvedPath = resolve(filePath); + if (resolvedPath !== dashboardDir && !resolvedPath.startsWith(dashboardDirPrefix)) { + res.writeHead(403); + res.end("Forbidden"); + return; + } + if (existsSync(resolvedPath) && statSync(resolvedPath).isFile()) { + const ext = extname(resolvedPath); + const mime = MIME_TYPES[ext] ?? "application/octet-stream"; + res.writeHead(200, { + "Content-Type": mime + }); + res.end(readFileSync(resolvedPath)); + } else { + const indexPath = join(dashboardDir, "index.html"); + if (existsSync(indexPath)) { + res.writeHead(200, { + "Content-Type": "text/html" + }); + res.end(readFileSync(indexPath)); + } else { + res.writeHead(404); + res.end("Dashboard not built. Run: bun --filter @kontext/dashboard build"); + } + } + } catch (err) { + console.error(pc.red("Server error:"), err); + jsonResponse(res, 500, { + error: err instanceof Error ? err.message : "Internal error" + }); + } + }); + server.listen(options.port, ()=>{ + console.log(`${pc.green("●")} Kontext dashboard running at ${pc.cyan(`http://localhost:${options.port}`)}`); + console.log(` ${pc.dim(`${graph.nodes.length} nodes, ${graph.edges.length} edges`)}`); + }); + }); +} + +const cli = cac("kontext"); +buildCommand(cli); +depsCommand(cli); +checkCommand(cli); +lintCommand(cli); +serveCommand(cli); +cli.version("0.0.0", "-v, --version"); +cli.help(); +cli.parse(); diff --git a/ecosystem/kontext/cli/package.json b/ecosystem/kontext/cli/package.json new file mode 100644 index 000000000..74d0b7738 --- /dev/null +++ b/ecosystem/kontext/cli/package.json @@ -0,0 +1,39 @@ +{ + "name": "@kontext/cli", + "version": "0.0.0", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/daangn/seed-design.git", + "directory": "ecosystem/kontext/cli" + }, + "license": "MIT", + "sideEffects": false, + "type": "module", + "bin": { + "kontext": "./bin/kontext.mjs" + }, + "files": [ + "src", + "bin" + ], + "scripts": { + "clean": "rm -rf bin", + "build": "bunchee", + "kontext:build": "bun run build", + "dev": "bunchee --watch", + "lint:publish": "bun publint" + }, + "dependencies": { + "@kontext/core": "0.0.0", + "@clack/prompts": "^1.0.0", + "cac": "^6.7.14", + "picocolors": "^1.1.1" + }, + "devDependencies": { + "typescript": "^5.9.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/ecosystem/kontext/cli/src/bin/kontext.ts b/ecosystem/kontext/cli/src/bin/kontext.ts new file mode 100644 index 000000000..a6121d8e0 --- /dev/null +++ b/ecosystem/kontext/cli/src/bin/kontext.ts @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +import { cac } from "cac"; +import { buildCommand } from "../commands/build.js"; +import { checkCommand } from "../commands/check.js"; +import { depsCommand } from "../commands/deps.js"; +import { lintCommand } from "../commands/lint.js"; +import { serveCommand } from "../commands/serve.js"; + +const cli = cac("kontext"); + +buildCommand(cli); +depsCommand(cli); +checkCommand(cli); +lintCommand(cli); +serveCommand(cli); + +cli.version("0.0.0", "-v, --version"); +cli.help(); +cli.parse(); diff --git a/ecosystem/kontext/cli/src/commands/build.ts b/ecosystem/kontext/cli/src/commands/build.ts new file mode 100644 index 000000000..ce73e344c --- /dev/null +++ b/ecosystem/kontext/cli/src/commands/build.ts @@ -0,0 +1,30 @@ +import type { CAC } from "cac"; +import { writeFileSync, mkdirSync } from "node:fs"; +import { resolve } from "node:path"; +import { buildGraph } from "@kontext/core"; +import pc from "picocolors"; +import type { RootOption } from "../utils/graph.js"; + +export function buildCommand(cli: CAC) { + cli + .command("build", "Build the dependency graph from all kontext.yaml files") + .option("--root ", "Repository root directory", { default: process.cwd() }) + .action((options: RootOption) => { + const rootDir = resolve(options.root); + + console.log(pc.dim("Scanning for kontext.yaml files...")); + const graph = buildGraph({ rootDir }); + + const outDir = resolve(rootDir, ".kontext"); + mkdirSync(outDir, { recursive: true }); + + const outPath = resolve(outDir, "graph.json"); + writeFileSync(outPath, JSON.stringify(graph, null, 2)); + + console.log( + `${pc.green("✓")} Graph built: ${pc.bold(String(graph.nodes.length))} nodes, ${pc.bold(String(graph.edges.length))} edges`, + ); + console.log(` ${pc.dim("Packages:")} ${graph.packages.join(", ")}`); + console.log(` ${pc.dim("Output:")} ${outPath}`); + }); +} diff --git a/ecosystem/kontext/cli/src/commands/check.ts b/ecosystem/kontext/cli/src/commands/check.ts new file mode 100644 index 000000000..9a0756c9b --- /dev/null +++ b/ecosystem/kontext/cli/src/commands/check.ts @@ -0,0 +1,34 @@ +import type { CAC } from "cac"; +import { resolve } from "node:path"; +import { checkCompleteness } from "@kontext/core"; +import pc from "picocolors"; +import { renderCheckResults } from "../utils/render.js"; +import type { RootOption } from "../utils/graph.js"; +import { loadOrBuildGraph } from "../utils/graph.js"; + +export function checkCommand(cli: CAC) { + cli + .command("check", "Verify all affected paths exist") + .option("--root ", "Repository root directory", { default: process.cwd() }) + .option("--ci", "Exit with code 1 if any files are missing") + .action((options: RootOption & { ci?: boolean }) => { + const rootDir = resolve(options.root); + const graph = loadOrBuildGraph(rootDir); + const results = checkCompleteness(graph); + + const hasMissing = results.some((r) => r.missing.length > 0); + + console.log(renderCheckResults(results)); + + if (hasMissing) { + console.log(""); + console.log(pc.yellow("Some affected files are missing.")); + if (options.ci) { + process.exit(1); + } + } else { + console.log(""); + console.log(pc.green("All affected files exist.")); + } + }); +} diff --git a/ecosystem/kontext/cli/src/commands/deps.ts b/ecosystem/kontext/cli/src/commands/deps.ts new file mode 100644 index 000000000..3c2957c72 --- /dev/null +++ b/ecosystem/kontext/cli/src/commands/deps.ts @@ -0,0 +1,34 @@ +import type { CAC } from "cac"; +import { resolve, relative, isAbsolute } from "node:path"; +import { findDeps } from "@kontext/core"; +import { renderDepsJson, renderDepsTree } from "../utils/render.js"; +import type { RootOption } from "../utils/graph.js"; +import { loadOrBuildGraph } from "../utils/graph.js"; + +export function depsCommand(cli: CAC) { + cli + .command("deps ", "Show all files affected by changes to ") + .option("--root ", "Repository root directory", { default: process.cwd() }) + .option("--json", "Output as JSON") + .action((file: string, options: RootOption & { json?: boolean }) => { + const rootDir = resolve(options.root); + const graph = loadOrBuildGraph(rootDir); + + const absFile = isAbsolute(file) ? file : resolve(rootDir, file); + const relFile = relative(rootDir, absFile); + + const deps = findDeps(graph, relFile); + + if (deps.length === 0) { + console.log(`No relations found for ${relFile}`); + console.log("Tip: Make sure the file matches a 'when' pattern in a kontext.yaml"); + return; + } + + if (options.json) { + console.log(renderDepsJson(deps)); + } else { + console.log(renderDepsTree(relFile, deps)); + } + }); +} diff --git a/ecosystem/kontext/cli/src/commands/lint.ts b/ecosystem/kontext/cli/src/commands/lint.ts new file mode 100644 index 000000000..37c7fc2e2 --- /dev/null +++ b/ecosystem/kontext/cli/src/commands/lint.ts @@ -0,0 +1,62 @@ +import type { CAC } from "cac"; +import { resolve } from "node:path"; +import { lint } from "@kontext/core"; +import pc from "picocolors"; +import { renderLintResults } from "../utils/render.js"; + +export function lintCommand(cli: CAC) { + cli + .command( + "lint", + "Discover undeclared relationships from git history, naming patterns, and imports", + ) + .option("--root ", "Repository root directory", { default: process.cwd() }) + .option("--commits ", "Number of commits to analyze", { default: 200 }) + .option("--threshold ", "Jaccard similarity threshold", { default: 0.7 }) + .option("--min-co ", "Minimum co-occurrences", { default: 3 }) + .option("--json", "Output as JSON") + .option( + "--fix", + "Auto-apply suggestions to kontext.yaml files (experimental, not yet implemented)", + ) + .action( + (options: { + root: string; + commits: number; + threshold: number; + minCo: number; + json?: boolean; + fix?: boolean; + }) => { + const rootDir = resolve(options.root); + + if (!options.json) { + console.log(pc.dim("Analyzing repository...")); + } + + const result = lint({ + rootDir, + commitCount: options.commits, + jaccardThreshold: options.threshold, + minCoOccurrences: options.minCo, + }); + + if (options.json) { + console.log(JSON.stringify(result, null, 2)); + return; + } + + console.log(renderLintResults(result)); + + if (result.suggestions.length > 0 && !options.fix) { + console.log(""); + console.log(pc.dim("Apply suggestions with: kontext lint --fix")); + } + + if (options.fix && result.suggestions.length > 0) { + console.log(""); + console.log(pc.yellow("--fix is not yet implemented. Coming soon.")); + } + }, + ); +} diff --git a/ecosystem/kontext/cli/src/commands/serve.ts b/ecosystem/kontext/cli/src/commands/serve.ts new file mode 100644 index 000000000..de0f7f8e2 --- /dev/null +++ b/ecosystem/kontext/cli/src/commands/serve.ts @@ -0,0 +1,314 @@ +import type { CAC } from "cac"; +import { createServer, type IncomingMessage } from "node:http"; +import { readFileSync, existsSync, statSync, readdirSync, writeFileSync, mkdirSync } from "node:fs"; +import { resolve, join, extname, normalize, dirname } from "node:path"; +import { buildGraph } from "@kontext/core"; +import pc from "picocolors"; + +const MIME_TYPES: Record = { + ".html": "text/html", + ".js": "application/javascript", + ".mjs": "application/javascript", + ".css": "text/css", + ".json": "application/json", + ".woff2": "font/woff2", + ".woff": "font/woff", + ".ttf": "font/ttf", + ".svg": "image/svg+xml", +}; + +const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "lib", ".kontext", ".next", ".turbo"]); + +const BINARY_EXTS = new Set([ + ".png", + ".jpg", + ".jpeg", + ".gif", + ".webp", + ".ico", + ".svg", + ".woff", + ".woff2", + ".ttf", + ".eot", + ".otf", + ".wasm", + ".zip", + ".tar", + ".gz", + ".br", + ".pdf", + ".mp3", + ".mp4", + ".webm", +]); + +function jsonResponse(res: import("node:http").ServerResponse, statusCode: number, data: unknown) { + res.writeHead(statusCode, { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }); + res.end(JSON.stringify(data)); +} + +function parseBody(req: IncomingMessage): Promise { + return new Promise((resolve, reject) => { + let body = ""; + req.on("data", (chunk) => { + body += chunk; + if (body.length > 1_000_000) { + reject(new Error("Body too large")); + } + }); + req.on("end", () => resolve(body)); + req.on("error", reject); + }); +} + +function isPathSafe(targetPath: string, rootDir: string): boolean { + const normalized = normalize(targetPath); + const rootPrefix = rootDir + "/"; + return normalized === rootDir || normalized.startsWith(rootPrefix); +} + +export function serveCommand(cli: CAC) { + cli + .command("serve", "Start the Kontext dashboard") + .option("--root ", "Repository root directory", { + default: process.cwd(), + }) + .option("--port ", "Port number", { default: 4321 }) + .action(async (options: { root: string; port: number }) => { + const rootDir = resolve(options.root); + const dashboardDir = resolve(rootDir, "ecosystem/kontext/dashboard/dist"); + const dashboardDirPrefix = dashboardDir + "/"; + + // Mutable graph cache + let graph = buildGraph({ rootDir }); + let graphJson = JSON.stringify(graph); + + const server = createServer(async (req, res) => { + const rawUrl = req.url ?? "/"; + const [urlPath, queryString] = rawUrl.split("?"); + const url = urlPath ?? "/"; + const method = req.method ?? "GET"; + const params = new URLSearchParams(queryString ?? ""); + + // CORS headers for API + if (url.startsWith("/api/")) { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); + res.setHeader("Access-Control-Allow-Headers", "Content-Type"); + if (method === "OPTIONS") { + res.writeHead(204); + res.end(); + return; + } + } + + try { + // --- API: GET /api/graph --- + if (url === "/api/graph" && method === "GET") { + res.writeHead(200, { "Content-Type": "application/json" }); + res.end(graphJson); + return; + } + + // --- API: GET /api/workspaces --- + if (url === "/api/workspaces" && method === "GET") { + try { + const pkgPath = join(rootDir, "package.json"); + const pkgJson = JSON.parse(readFileSync(pkgPath, "utf-8")); + const workspaceGlobs: string[] = pkgJson.workspaces ?? []; + const workspaces: string[] = []; + + for (const pattern of workspaceGlobs) { + if (pattern.endsWith("/*")) { + // Expand glob + const dir = join(rootDir, pattern.slice(0, -2)); + if (existsSync(dir) && statSync(dir).isDirectory()) { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + if (entry.isDirectory()) { + const wsPath = join(dir, entry.name, "package.json"); + if (existsSync(wsPath)) { + workspaces.push(pattern.slice(0, -2) + "/" + entry.name); + } + } + } + } + } else { + // Direct path + const wsPath = join(rootDir, pattern, "package.json"); + if (existsSync(wsPath)) { + workspaces.push(pattern); + } + } + } + + jsonResponse(res, 200, workspaces.sort()); + } catch { + jsonResponse(res, 200, []); + } + return; + } + + // --- API: GET /api/files --- + if (url === "/api/files" && method === "GET") { + const dir = params.get("dir") ?? ""; + const targetDir = dir ? join(rootDir, dir) : rootDir; + + if (!isPathSafe(targetDir, rootDir)) { + jsonResponse(res, 403, { error: "Forbidden" }); + return; + } + + if (!existsSync(targetDir) || !statSync(targetDir).isDirectory()) { + jsonResponse(res, 404, { error: "Directory not found" }); + return; + } + + const entries = readdirSync(targetDir, { withFileTypes: true }) + .filter((e) => !SKIP_DIRS.has(e.name) && !e.name.startsWith(".")) + .map((e) => ({ + name: e.name, + type: e.isDirectory() ? "directory" : "file", + path: dir ? `${dir}/${e.name}` : e.name, + })) + .sort((a, b) => { + if (a.type !== b.type) return a.type === "directory" ? -1 : 1; + return a.name.localeCompare(b.name); + }); + + jsonResponse(res, 200, entries); + return; + } + + // --- API: GET /api/file-content --- + if (url === "/api/file-content" && method === "GET") { + const filePath = params.get("path") ?? ""; + if (!filePath) { + jsonResponse(res, 400, { error: "Missing path parameter" }); + return; + } + + const targetPath = join(rootDir, filePath); + if (!isPathSafe(targetPath, rootDir)) { + jsonResponse(res, 403, { error: "Forbidden" }); + return; + } + + let stat: ReturnType; + try { + stat = statSync(targetPath); + } catch { + jsonResponse(res, 404, { error: "File not found" }); + return; + } + if (!stat.isFile()) { + jsonResponse(res, 404, { error: "File not found" }); + return; + } + if (stat.size > 512 * 1024) { + jsonResponse(res, 413, { error: "File too large (max 512KB)" }); + return; + } + + const ext = extname(targetPath).toLowerCase(); + if (BINARY_EXTS.has(ext)) { + jsonResponse(res, 415, { error: "Binary file", binary: true }); + return; + } + + try { + const content = readFileSync(targetPath, "utf-8"); + jsonResponse(res, 200, { content, path: filePath, size: stat.size, extension: ext }); + } catch { + jsonResponse(res, 500, { error: "Failed to read file" }); + } + return; + } + + // --- API: GET/POST /api/config/:packageDir --- + const configMatch = url.match(/^\/api\/config\/(.+)$/); + if (configMatch) { + const packageDir = decodeURIComponent(configMatch[1]!); + const configPath = join(rootDir, packageDir, "kontext.yaml"); + + if (!isPathSafe(configPath, rootDir)) { + jsonResponse(res, 403, { error: "Forbidden" }); + return; + } + + if (method === "GET") { + if (existsSync(configPath) && statSync(configPath).isFile()) { + const content = readFileSync(configPath, "utf-8"); + jsonResponse(res, 200, { content, exists: true }); + } else { + jsonResponse(res, 200, { content: "", exists: false }); + } + return; + } + + if (method === "POST") { + const body = await parseBody(req); + const { content } = JSON.parse(body) as { content: string }; + + const dir = dirname(configPath); + mkdirSync(dir, { recursive: true }); + writeFileSync(configPath, content, "utf-8"); + jsonResponse(res, 200, { success: true }); + return; + } + } + + // --- API: POST /api/rebuild --- + if (url === "/api/rebuild" && method === "POST") { + graph = buildGraph({ rootDir }); + graphJson = JSON.stringify(graph); + jsonResponse(res, 200, graph); + return; + } + + // --- Static file serving --- + const sanitizedUrl = normalize(url).replace(/^(\.\.[/\\])+/, ""); + const filePath = + url === "/" ? join(dashboardDir, "index.html") : join(dashboardDir, sanitizedUrl); + + const resolvedPath = resolve(filePath); + if (resolvedPath !== dashboardDir && !resolvedPath.startsWith(dashboardDirPrefix)) { + res.writeHead(403); + res.end("Forbidden"); + return; + } + + if (existsSync(resolvedPath) && statSync(resolvedPath).isFile()) { + const ext = extname(resolvedPath); + const mime = MIME_TYPES[ext] ?? "application/octet-stream"; + res.writeHead(200, { "Content-Type": mime }); + res.end(readFileSync(resolvedPath)); + } else { + const indexPath = join(dashboardDir, "index.html"); + if (existsSync(indexPath)) { + res.writeHead(200, { "Content-Type": "text/html" }); + res.end(readFileSync(indexPath)); + } else { + res.writeHead(404); + res.end("Dashboard not built. Run: bun --filter @kontext/dashboard build"); + } + } + } catch (err) { + console.error(pc.red("Server error:"), err); + jsonResponse(res, 500, { + error: err instanceof Error ? err.message : "Internal error", + }); + } + }); + + server.listen(options.port, () => { + console.log( + `${pc.green("●")} Kontext dashboard running at ${pc.cyan(`http://localhost:${options.port}`)}`, + ); + console.log(` ${pc.dim(`${graph.nodes.length} nodes, ${graph.edges.length} edges`)}`); + }); + }); +} diff --git a/ecosystem/kontext/cli/src/utils/graph.ts b/ecosystem/kontext/cli/src/utils/graph.ts new file mode 100644 index 000000000..a40474540 --- /dev/null +++ b/ecosystem/kontext/cli/src/utils/graph.ts @@ -0,0 +1,18 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { buildGraph } from "@kontext/core"; +import type { KontextGraph } from "@kontext/core"; + +export interface RootOption { + root: string; +} + +export function loadOrBuildGraph(rootDir: string): KontextGraph { + const graphPath = resolve(rootDir, ".kontext", "graph.json"); + try { + const raw = readFileSync(graphPath, "utf-8"); + return JSON.parse(raw) as KontextGraph; + } catch { + return buildGraph({ rootDir }); + } +} diff --git a/ecosystem/kontext/cli/src/utils/render.ts b/ecosystem/kontext/cli/src/utils/render.ts new file mode 100644 index 000000000..f45dd77af --- /dev/null +++ b/ecosystem/kontext/cli/src/utils/render.ts @@ -0,0 +1,111 @@ +import pc from "picocolors"; +import type { CheckResult, DepResult, LintResult } from "@kontext/core"; + +export function renderDepsTree(filePath: string, deps: DepResult[]): string { + const lines: string[] = []; + lines.push(pc.bold(filePath)); + + for (let i = 0; i < deps.length; i++) { + const dep = deps[i]!; + const isLast = i === deps.length - 1; + const prefix = isLast ? "└─" : "├─"; + const existsIcon = dep.exists ? pc.green("●") : pc.red("○"); + + let label = ""; + if (dep.generated) { + label = `${pc.cyan("[auto]")} ${dep.path}`; + if (dep.command) { + label += ` ${pc.dim(`→ ${dep.command}`)}`; + } + } else { + label = dep.path; + if (dep.reason) { + label += ` ${pc.dim(`— ${dep.reason}`)}`; + } + } + + lines.push(`${prefix} ${existsIcon} ${label}`); + } + + return lines.join("\n"); +} + +export function renderCheckResults(results: CheckResult[]): string { + const lines: string[] = []; + + // definedBy별로 그룹핑 + const bySource = new Map(); + for (const result of results) { + const key = result.definedBy; + const list = bySource.get(key) ?? []; + list.push(result); + bySource.set(key, list); + } + + for (const [definedBy, group] of bySource) { + lines.push(pc.dim(`── ${definedBy} ──`)); + + for (const result of group) { + if (result.missing.length === 0) { + lines.push(`${pc.green("✅")} ${result.source}: ${result.existing}/${result.total}`); + } else { + lines.push( + `${pc.yellow("⚠️")} ${result.source}: ${result.existing}/${result.total} — missing:`, + ); + for (const m of result.missing) { + lines.push(` ${pc.dim("└─")} ${pc.red(m)}`); + } + } + } + + lines.push(""); + } + + return lines.join("\n"); +} + +export function renderDepsJson(deps: DepResult[]): string { + return JSON.stringify(deps, null, 2); +} + +export function renderLintResults(result: LintResult): string { + const lines: string[] = []; + + if (result.suggestions.length > 0) { + lines.push(pc.bold("Discovered relationships:")); + lines.push(""); + + // 레이어별 그룹핑 + for (const layer of ["naming", "import", "co-change"] as const) { + const items = result.suggestions.filter((s) => s.layer === layer); + if (items.length === 0) continue; + + lines.push(pc.cyan(` [${layer}] ${items.length} suggestions`)); + for (const item of items.slice(0, 10)) { + const conf = `${(item.confidence * 100).toFixed(0)}%`; + lines.push(` ${pc.dim("├─")} ${item.source} ${pc.dim("↔")} ${item.target}`); + lines.push(` ${pc.dim("│")} ${pc.dim(item.detail)} ${pc.dim(`(${conf})`)}`); + } + if (items.length > 10) { + lines.push(` ${pc.dim(`└─ ... and ${items.length - 10} more`)}`); + } + lines.push(""); + } + } else { + lines.push(pc.green("No undeclared relationships found.")); + } + + if (result.staleWarnings.length > 0) { + lines.push(pc.bold("Stale relationships:")); + lines.push(""); + for (const warn of result.staleWarnings.slice(0, 10)) { + lines.push(` ${pc.yellow("⚠")} ${warn.source} ${pc.dim("→")} ${warn.target}`); + lines.push(` ${pc.dim(warn.reason)}`); + } + if (result.staleWarnings.length > 10) { + lines.push(` ${pc.dim(`... and ${result.staleWarnings.length - 10} more`)}`); + } + } + + return lines.join("\n"); +} diff --git a/ecosystem/kontext/cli/tsconfig.json b/ecosystem/kontext/cli/tsconfig.json new file mode 100644 index 000000000..7a9bfece1 --- /dev/null +++ b/ecosystem/kontext/cli/tsconfig.json @@ -0,0 +1,8 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "rootDir": "src" + } +} diff --git a/ecosystem/kontext/core/kontext.schema.json b/ecosystem/kontext/core/kontext.schema.json new file mode 100644 index 000000000..df282cd3b --- /dev/null +++ b/ecosystem/kontext/core/kontext.schema.json @@ -0,0 +1,97 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "https://kontext.dev/schema/v1.json", + "title": "Kontext Configuration", + "description": "Dependency graph declaration for monorepo packages", + "type": "object", + "required": ["apiVersion", "relations"], + "additionalProperties": false, + "properties": { + "apiVersion": { + "type": "string", + "const": "kontext/v1", + "description": "Schema version" + }, + "ignore": { + "type": "array", + "items": { "type": "string" }, + "description": "Glob patterns to ignore across this entire package (barrel files, deprecated, test files, etc.)" + }, + "relations": { + "type": "array", + "items": { + "type": "object", + "required": ["when", "affects"], + "additionalProperties": false, + "properties": { + "when": { + "type": "string", + "minLength": 1, + "description": "Glob pattern for files to watch within this package" + }, + "exclude": { + "type": "array", + "items": { "type": "string" }, + "description": "Glob patterns to exclude from the 'when' match" + }, + "affects": { + "type": "array", + "items": { "$ref": "#/$defs/affectedEntry" } + }, + "overrides": { + "type": "array", + "items": { + "type": "object", + "required": ["match", "affects"], + "additionalProperties": false, + "properties": { + "match": { + "type": "string", + "minLength": 1, + "description": "Glob pattern to match specific files for override (supports brace expansion)" + }, + "affects": { + "type": "array", + "items": { "$ref": "#/$defs/affectedEntry" } + } + } + }, + "description": "Override default affects for specific file patterns" + } + } + } + } + }, + "$defs": { + "affectedEntry": { + "type": "object", + "required": ["path"], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "minLength": 1, + "description": "Affected file path (repo root relative). Supports {id}=kebab-case, {Id}=PascalCase templates" + }, + "reason": { + "type": "string", + "description": "Why this file is affected" + }, + "generated": { + "type": "boolean", + "default": false, + "description": "Whether this is an auto-generated file" + }, + "command": { + "type": "string", + "description": "Command to run when generated=true" + }, + "optional": { + "type": "boolean", + "default": false, + "description": "If true, won't warn when file is missing in 'check'" + } + } + } + } +} diff --git a/ecosystem/kontext/core/package.json b/ecosystem/kontext/core/package.json new file mode 100644 index 000000000..154fa086a --- /dev/null +++ b/ecosystem/kontext/core/package.json @@ -0,0 +1,41 @@ +{ + "name": "@kontext/core", + "version": "0.0.0", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/daangn/seed-design.git", + "directory": "ecosystem/kontext/core" + }, + "sideEffects": false, + "type": "module", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js", + "require": "./lib/index.cjs" + } + }, + "main": "./lib/index.cjs", + "files": [ + "lib", + "src" + ], + "scripts": { + "clean": "rm -rf lib", + "build": "bunchee", + "test": "bun test", + "kontext:build": "bun run build", + "lint:publish": "bun publint" + }, + "dependencies": { + "yaml": "^2.7.1", + "minimatch": "^10.0.1" + }, + "devDependencies": { + "typescript": "^5.9.2" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/ecosystem/kontext/core/src/graph-builder.ts b/ecosystem/kontext/core/src/graph-builder.ts new file mode 100644 index 000000000..c3090e871 --- /dev/null +++ b/ecosystem/kontext/core/src/graph-builder.ts @@ -0,0 +1,245 @@ +import { existsSync, readdirSync } from "node:fs"; +import { dirname, join, resolve } from "node:path"; +import { minimatch } from "minimatch"; +import { parseKontextFile } from "./parser.js"; +import { expandTemplate, hasTemplate, toKebabCase } from "./resolver.js"; +import type { AffectedEntry, GraphEdge, GraphNode, KontextGraph } from "./types.js"; + +export interface BuildOptions { + rootDir: string; +} + +export function buildGraph(options: BuildOptions): KontextGraph { + const { rootDir } = options; + const kontextFiles = findKontextFiles(rootDir); + const nodes = new Map(); + const edges: GraphEdge[] = []; + const packages: string[] = []; + + for (const kontextPath of kontextFiles) { + const packageDir = dirname(kontextPath); + const relPackageDir = relativeFromRoot(rootDir, packageDir); + const relKontextPath = relativeFromRoot(rootDir, kontextPath); + packages.push(relPackageDir); + + const config = parseKontextFile(kontextPath); + const ignorePatterns = config.ignore ?? []; + + for (const relation of config.relations) { + let watchedFiles = resolveWatchPattern(packageDir, relation.when); + + // ignore 필터 + if (ignorePatterns.length > 0) { + watchedFiles = watchedFiles.filter((f) => { + const rel = f.slice(packageDir.length + 1); + return !ignorePatterns.some((p) => minimatch(rel, p)); + }); + } + + // exclude 필터 + if (relation.exclude?.length) { + const excludes = relation.exclude; + watchedFiles = watchedFiles.filter((f) => { + const rel = f.slice(packageDir.length + 1); + return !excludes.some((p) => minimatch(rel, p)); + }); + } + + for (const watchedFile of watchedFiles) { + const relWatched = relativeFromRoot(rootDir, watchedFile); + const relInPackage = watchedFile.slice(packageDir.length + 1); + ensureNode(nodes, relWatched, relPackageDir, rootDir); + + // override 매칭: 이 파일에 해당하는 override가 있으면 그걸 사용 + let affectsToUse = relation.affects; + if (relation.overrides?.length) { + for (const override of relation.overrides) { + if (minimatch(relInPackage, override.match)) { + affectsToUse = override.affects; + break; + } + } + } + + const id = extractIdFromWatched(watchedFile); + + for (const entry of affectsToUse) { + addEdgesForEntry(rootDir, nodes, edges, relWatched, entry, id, relKontextPath); + } + } + } + } + + return { + nodes: Array.from(nodes.values()), + edges, + packages, + builtAt: new Date().toISOString(), + }; +} + +function addEdgesForEntry( + rootDir: string, + nodes: Map, + edges: GraphEdge[], + source: string, + entry: AffectedEntry, + id: string | null, + definedBy: string, +) { + const pushEdge = (target: string) => { + ensureNode(nodes, target, resolvePackageDir(target), rootDir); + edges.push({ + source, + target, + reason: entry.reason, + generated: entry.generated ?? false, + command: entry.command, + optional: entry.optional ?? false, + definedBy, + }); + }; + + if (hasTemplate(entry.path) && id) { + const expanded = expandTemplate(entry.path, id); + const resolved = resolveGlobOrLiteral(rootDir, expanded); + + if (resolved.length > 0) { + for (const rp of resolved) pushEdge(rp); + } else { + pushEdge(expanded); + } + } else { + const resolved = resolveGlobOrLiteral(rootDir, entry.path); + + if (resolved.length > 0) { + for (const rp of resolved) pushEdge(rp); + } else { + pushEdge(entry.path); + } + } +} + +// --- 유틸리티 --- + +function findKontextFiles(rootDir: string): string[] { + const results: string[] = []; + const queue = [rootDir]; + + while (queue.length > 0) { + const dir = queue.pop()!; + const base = dir.split("/").pop() ?? ""; + if (["node_modules", ".git", "dist", "lib", "bin", ".kontext"].includes(base)) continue; + + try { + const entries = readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + queue.push(full); + } else if (entry.name === "kontext.yaml") { + results.push(full); + } + } + } catch { + // ignore + } + } + + return results; +} + +function resolveWatchPattern(packageDir: string, pattern: string): string[] { + const results: string[] = []; + + function walk(dir: string) { + try { + const entries = readdirSync(dir, { withFileTypes: true }); + for (const entry of entries) { + const full = join(dir, entry.name); + if (entry.isDirectory()) { + if (!["node_modules", ".git", "dist", "lib"].includes(entry.name)) { + walk(full); + } + } else { + const rel = full.slice(packageDir.length + 1); + if (minimatch(rel, pattern)) { + results.push(full); + } + } + } + } catch { + // ignore + } + } + + walk(packageDir); + return results; +} + +function resolveGlobOrLiteral(rootDir: string, path: string): string[] { + const absPath = resolve(rootDir, path); + + if (path.endsWith("/")) { + return existsSync(absPath) ? [path] : []; + } + + if (path.includes("*")) { + const dir = resolve(rootDir, dirname(path)); + if (!existsSync(dir)) return []; + + const results: string[] = []; + const pattern = path.split("/").pop() ?? ""; + try { + for (const entry of readdirSync(dir)) { + if (minimatch(entry, pattern)) { + results.push(relativeFromRoot(rootDir, join(dir, entry))); + } + } + } catch { + // ignore + } + return results; + } + + return existsSync(absPath) ? [path] : []; +} + +function extractIdFromWatched(filePath: string): string | null { + const name = filePath.split("/").pop() ?? ""; + const dotIdx = name.lastIndexOf("."); + const base = dotIdx >= 0 ? name.slice(0, dotIdx) : name; + if (!base || base === "index") return null; + return toKebabCase(base); +} + +function ensureNode( + nodes: Map, + path: string, + packageDir: string, + rootDir: string, +) { + if (!nodes.has(path)) { + nodes.set(path, { + id: path, + packageDir, + exists: existsSync(resolve(rootDir, path)), + }); + } +} + +function resolvePackageDir(relPath: string): string { + const parts = relPath.split("/"); + if (["packages", "ecosystem", "docs", "tools"].includes(parts[0] ?? "")) { + return parts.slice(0, 2).join("/"); + } + return parts[0] ?? ""; +} + +function relativeFromRoot(rootDir: string, absPath: string): string { + if (absPath.startsWith(rootDir)) { + const rel = absPath.slice(rootDir.length); + return rel.startsWith("/") ? rel.slice(1) : rel; + } + return absPath; +} diff --git a/ecosystem/kontext/core/src/index.ts b/ecosystem/kontext/core/src/index.ts new file mode 100644 index 000000000..4b5a34ed2 --- /dev/null +++ b/ecosystem/kontext/core/src/index.ts @@ -0,0 +1,21 @@ +export { buildGraph } from "./graph-builder.js"; +export type { BuildOptions } from "./graph-builder.js"; +export { lint } from "./lint.js"; +export { parseKontextFile } from "./parser.js"; +export { analyzeImpact, checkCompleteness, findAffectedBy, findDeps } from "./query.js"; +export { expandTemplate, extractId, hasTemplate, toKebabCase } from "./resolver.js"; +export { SchemaValidationError, validateConfig } from "./schema.js"; +export type { + AffectedEntry, + CheckResult, + DepResult, + GraphEdge, + GraphNode, + KontextConfig, + KontextGraph, + LintOptions, + LintResult, + LintStaleWarning, + LintSuggestion, + Relation, +} from "./types.js"; diff --git a/ecosystem/kontext/core/src/lint.ts b/ecosystem/kontext/core/src/lint.ts new file mode 100644 index 000000000..ca4db1689 --- /dev/null +++ b/ecosystem/kontext/core/src/lint.ts @@ -0,0 +1,332 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import { basename, resolve } from "node:path"; +import { minimatch } from "minimatch"; +import { buildGraph } from "./graph-builder.js"; +import type { + KontextGraph, + LintOptions, + LintResult, + LintStaleWarning, + LintSuggestion, +} from "./types.js"; + +const DEFAULT_COMMIT_COUNT = 200; +const DEFAULT_JACCARD_THRESHOLD = 0.7; +const DEFAULT_MIN_CO_OCCURRENCES = 3; +const DEFAULT_IGNORE_PATTERNS = [ + "node_modules/**", + "dist/**", + "lib/**", + "bin/**", + ".kontext/**", + "*.lock", + "bun.lock", + ".changeset/**", +]; + +export function lint(options: LintOptions): LintResult { + const { + rootDir, + commitCount = DEFAULT_COMMIT_COUNT, + jaccardThreshold = DEFAULT_JACCARD_THRESHOLD, + minCoOccurrences = DEFAULT_MIN_CO_OCCURRENCES, + ignorePatterns = DEFAULT_IGNORE_PATTERNS, + } = options; + + const graph = buildGraph({ rootDir }); + const declaredEdges = new Set(graph.edges.map((e) => edgeKey(e.source, e.target))); + + const suggestions: LintSuggestion[] = []; + + // Layer 1: 네이밍 매칭 + const namingSuggestions = analyzeNaming(rootDir, graph, declaredEdges); + suggestions.push(...namingSuggestions); + + // Layer 2: Import 분석 + const importSuggestions = analyzeImports(rootDir, graph, declaredEdges); + suggestions.push(...importSuggestions); + + // Layer 3: Git co-change + const { coChangeSuggestions, staleWarnings } = analyzeCoChange( + rootDir, + graph, + declaredEdges, + commitCount, + jaccardThreshold, + minCoOccurrences, + ignorePatterns, + ); + suggestions.push(...coChangeSuggestions); + + // 중복 제거 (같은 source-target 쌍이 여러 레이어에서 발견될 수 있음) + const seen = new Set(); + const dedupedSuggestions = suggestions.filter((s) => { + const key = edgeKey(s.source, s.target); + if (seen.has(key)) return false; + seen.add(key); + return true; + }); + + return { suggestions: dedupedSuggestions, staleWarnings }; +} + +// --- Layer 1: 네이밍 매칭 --- + +function analyzeNaming( + _rootDir: string, + graph: KontextGraph, + declaredEdges: Set, +): LintSuggestion[] { + const suggestions: LintSuggestion[] = []; + + // 컴포넌트 ID별로 파일을 그룹핑 + const idToFiles = new Map(); + + for (const node of graph.nodes) { + const id = extractBaseId(node.id); + if (!id) continue; + const list = idToFiles.get(id) ?? []; + list.push(node.id); + idToFiles.set(id, list); + } + + // 같은 ID를 가진 파일들 중 연결되지 않은 쌍 찾기 + for (const [id, files] of idToFiles) { + if (files.length < 2) continue; + + for (let i = 0; i < files.length; i++) { + for (let j = i + 1; j < files.length; j++) { + const a = files[i]!; + const b = files[j]!; + + // 같은 패키지 내 파일은 무시 + if (getPackageDir(a) === getPackageDir(b)) continue; + + if (!declaredEdges.has(edgeKey(a, b)) && !declaredEdges.has(edgeKey(b, a))) { + suggestions.push({ + source: a, + target: b, + layer: "naming", + confidence: 0.8, + detail: `Same component ID "${id}" across packages`, + }); + } + } + } + } + + return suggestions; +} + +// --- Layer 2: Import 분석 --- + +const IMPORT_REGEX = /(?:import|from)\s+['"](@seed-design\/[^'"]+|\.\.?\/[^'"]+)['"]/g; + +function analyzeImports( + rootDir: string, + graph: KontextGraph, + _declaredEdges: Set, +): LintSuggestion[] { + const suggestions: LintSuggestion[] = []; + + for (const node of graph.nodes) { + if (!node.id.endsWith(".ts") && !node.id.endsWith(".tsx")) continue; + + const absPath = resolve(rootDir, node.id); + let content: string; + try { + content = readFileSync(absPath, "utf-8"); + } catch { + continue; + } + + const sourcePackage = getPackageDir(node.id); + + for (const match of content.matchAll(IMPORT_REGEX)) { + const importPath = match[1]!; + + // @seed-design/ 패키지 import만 분석 + if (!importPath.startsWith("@seed-design/")) continue; + + // import 패키지를 실제 디렉토리로 매핑 + const targetPackage = resolveImportToPackage(importPath); + if (!targetPackage || targetPackage === sourcePackage) continue; + + // 이 패키지 간 관계가 선언되어 있는지 확인 + const hasRelation = graph.edges.some( + (e) => + (e.source.startsWith(sourcePackage) && e.target.startsWith(targetPackage)) || + (e.source.startsWith(targetPackage) && e.target.startsWith(sourcePackage)), + ); + + if (!hasRelation) { + suggestions.push({ + source: node.id, + target: targetPackage, + layer: "import", + confidence: 0.9, + detail: `Imports from ${importPath}`, + }); + } + } + } + + return suggestions; +} + +// --- Layer 3: Git co-change --- + +function analyzeCoChange( + rootDir: string, + graph: KontextGraph, + declaredEdges: Set, + commitCount: number, + jaccardThreshold: number, + minCoOccurrences: number, + ignorePatterns: string[], +): { coChangeSuggestions: LintSuggestion[]; staleWarnings: LintStaleWarning[] } { + const coChangeSuggestions: LintSuggestion[] = []; + const staleWarnings: LintStaleWarning[] = []; + + // git log 파싱 — execFileSync 배열 인자로 shell injection 완전 차단 + const safeCommitCount = Math.max(1, Math.min(Math.floor(Number(commitCount)), 10000)); + let gitOutput: string; + try { + gitOutput = execFileSync( + "git", + ["log", "--name-only", "--pretty=format:COMMIT:%H", "-n", String(safeCommitCount)], + { cwd: rootDir, encoding: "utf-8", maxBuffer: 50 * 1024 * 1024 }, + ); + } catch { + return { coChangeSuggestions, staleWarnings }; + } + + // 커밋별 파일 목록 구축 + const commits: string[][] = []; + let currentFiles: string[] = []; + + for (const line of gitOutput.split("\n")) { + if (line.startsWith("COMMIT:")) { + if (currentFiles.length > 0) commits.push(currentFiles); + currentFiles = []; + } else if (line.trim()) { + const file = line.trim(); + const shouldIgnore = ignorePatterns.some((p) => minimatch(file, p)); + if (!shouldIgnore) currentFiles.push(file); + } + } + if (currentFiles.length > 0) commits.push(currentFiles); + + // 파일별 커밋 인덱스 + 파일 쌍 co-occurrence 집계 + const fileCommits = new Map>(); + const pairCount = new Map(); + + for (let ci = 0; ci < commits.length; ci++) { + const files = commits[ci]!; + for (const f of files) { + const set = fileCommits.get(f) ?? new Set(); + set.add(ci); + fileCommits.set(f, set); + } + + // 커밋 내 파일 쌍 (최대 50파일까지만 — 큰 커밋은 노이즈) + if (files.length > 50) continue; + for (let i = 0; i < files.length; i++) { + for (let j = i + 1; j < files.length; j++) { + // 같은 패키지 내 파일은 무시 + if (getPackageDir(files[i]!) === getPackageDir(files[j]!)) continue; + + const key = edgeKey(files[i]!, files[j]!); + pairCount.set(key, (pairCount.get(key) ?? 0) + 1); + } + } + } + + // Jaccard 계산 + 미선언 관계 발견 + for (const [key, count] of pairCount) { + if (count < minCoOccurrences) continue; + + const [a, b] = key.split("|||") as [string, string]; + const commitsA = fileCommits.get(a); + const commitsB = fileCommits.get(b); + if (!commitsA || !commitsB) continue; + + const intersection = new Set([...commitsA].filter((x) => commitsB.has(x))); + const union = new Set([...commitsA, ...commitsB]); + const jaccard = intersection.size / union.size; + + if (jaccard < jaccardThreshold) continue; + + if (!declaredEdges.has(edgeKey(a, b)) && !declaredEdges.has(edgeKey(b, a))) { + coChangeSuggestions.push({ + source: a, + target: b, + layer: "co-change", + confidence: jaccard, + detail: `${count} co-changes, Jaccard: ${jaccard.toFixed(2)}`, + }); + } + } + + // Stale 관계 감지: 선언됐지만 co-change 0회 + for (const edge of graph.edges) { + const key1 = edgeKey(edge.source, edge.target); + const key2 = edgeKey(edge.target, edge.source); + const count = (pairCount.get(key1) ?? 0) + (pairCount.get(key2) ?? 0); + + if (count === 0 && !edge.generated) { + staleWarnings.push({ + source: edge.source, + target: edge.target, + reason: `No co-changes in last ${commitCount} commits`, + }); + } + } + + return { coChangeSuggestions, staleWarnings }; +} + +// --- 유틸리티 --- + +function edgeKey(a: string, b: string): string { + return a < b ? `${a}|||${b}` : `${b}|||${a}`; +} + +function extractBaseId(filePath: string): string | null { + const name = basename(filePath).replace(/\.[^.]+$/, ""); + if (!name || name === "index") return null; + + // PascalCase → kebab-case + return name + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") + .toLowerCase(); +} + +function getPackageDir(filePath: string): string { + const parts = filePath.split("/"); + if (parts[0] === "packages" || parts[0] === "ecosystem" || parts[0] === "tools") { + return parts.slice(0, 2).join("/"); + } + if (parts[0] === "docs") return "docs"; + return parts[0] ?? ""; +} + +function resolveImportToPackage(importPath: string): string | null { + // "@seed-design/css/recipes/button" → "packages/css" + // "@seed-design/react-checkbox" → "packages/react-headless/checkbox" + const withoutScope = importPath.replace("@seed-design/", ""); + const parts = withoutScope.split("/"); + const pkgName = parts[0]!; + + // 패키지 이름 → 디렉토리 매핑 (간단한 규칙) + if (pkgName === "css") return "packages/css"; + if (pkgName === "react") return "packages/react"; + if (pkgName.startsWith("react-")) + return `packages/react-headless/${pkgName.replace("react-", "")}`; + if (pkgName === "rootage") return "packages/rootage"; + if (pkgName === "qvism-preset") return "packages/qvism-preset"; + + return `packages/${pkgName}`; +} diff --git a/ecosystem/kontext/core/src/parser.ts b/ecosystem/kontext/core/src/parser.ts new file mode 100644 index 000000000..694d6d281 --- /dev/null +++ b/ecosystem/kontext/core/src/parser.ts @@ -0,0 +1,10 @@ +import { readFileSync } from "node:fs"; +import { parse as parseYaml } from "yaml"; +import { validateConfig } from "./schema.js"; +import type { KontextConfig } from "./types.js"; + +export function parseKontextFile(filePath: string): KontextConfig { + const raw = readFileSync(filePath, "utf-8"); + const data = parseYaml(raw); + return validateConfig(data, filePath); +} diff --git a/ecosystem/kontext/core/src/query.ts b/ecosystem/kontext/core/src/query.ts new file mode 100644 index 000000000..e309ccce8 --- /dev/null +++ b/ecosystem/kontext/core/src/query.ts @@ -0,0 +1,106 @@ +import type { CheckResult, DepResult, KontextGraph } from "./types.js"; + +/** + * 특정 파일과 관련된 모든 영향받는 파일을 반환 + */ +export function findDeps(graph: KontextGraph, filePath: string): DepResult[] { + const nodeMap = new Map(graph.nodes.map((n) => [n.id, n])); + const results: DepResult[] = []; + + for (const edge of graph.edges) { + if (edge.source === filePath) { + const node = nodeMap.get(edge.target); + results.push({ + path: edge.target, + reason: edge.reason, + generated: edge.generated, + command: edge.command, + exists: node?.exists ?? false, + }); + } + } + + return results; +} + +/** + * 특정 파일이 영향을 주는 쪽인지 찾기 (역방향 조회) + */ +export function findAffectedBy(graph: KontextGraph, filePath: string): DepResult[] { + const nodeMap = new Map(graph.nodes.map((n) => [n.id, n])); + const results: DepResult[] = []; + + for (const edge of graph.edges) { + if (edge.target === filePath) { + const node = nodeMap.get(edge.source); + results.push({ + path: edge.source, + reason: edge.reason, + generated: edge.generated, + command: edge.command, + exists: node?.exists ?? false, + }); + } + } + + return results; +} + +/** + * 모든 소스 파일에 대해 affects 경로의 존재 여부를 검증 + */ +export function checkCompleteness(graph: KontextGraph): CheckResult[] { + // source별로 그룹핑 + const nodeMap = new Map(graph.nodes.map((n) => [n.id, n])); + const groups = new Map< + string, + { definedBy: string; targets: Array<{ path: string; exists: boolean }> } + >(); + + for (const edge of graph.edges) { + if (edge.optional) continue; + + if (!groups.has(edge.source)) { + groups.set(edge.source, { definedBy: edge.definedBy, targets: [] }); + } + const node = nodeMap.get(edge.target); + groups.get(edge.source)!.targets.push({ + path: edge.target, + exists: node?.exists ?? false, + }); + } + + const results: CheckResult[] = []; + + for (const [source, { definedBy, targets }] of groups) { + const missing = targets.filter((t) => !t.exists).map((t) => t.path); + results.push({ + source, + definedBy, + total: targets.length, + existing: targets.length - missing.length, + missing, + }); + } + + return results; +} + +/** + * 변경된 파일 목록에서 영향받는 모든 파일을 반환 + */ +export function analyzeImpact( + graph: KontextGraph, + changedFiles: string[], +): Map { + const impact = new Map(); + + for (const file of changedFiles) { + const deps = findDeps(graph, file); + if (deps.length > 0) { + impact.set(file, deps); + } + } + + return impact; +} diff --git a/ecosystem/kontext/core/src/resolver.ts b/ecosystem/kontext/core/src/resolver.ts new file mode 100644 index 000000000..abf3b4800 --- /dev/null +++ b/ecosystem/kontext/core/src/resolver.ts @@ -0,0 +1,100 @@ +import { basename, relative } from "node:path"; + +/** + * {id} → kebab-case, {Id} → PascalCase 템플릿을 실제 파일명으로 확장. + * + * @example + * expandTemplate("packages/react/src/components/{Id}/", "action-button") + * // → "packages/react/src/components/ActionButton/" + * + * expandTemplate("packages/qvism-preset/src/recipes/{id}.ts", "action-button") + * // → "packages/qvism-preset/src/recipes/action-button.ts" + */ +export function expandTemplate(template: string, id: string): string { + return template.replace(/\{id\}/g, id).replace(/\{Id\}/g, toPascalCase(id)); +} + +/** + * 파일 경로에서 컴포넌트 ID(kebab-case)를 추출. + * + * @example + * extractId("packages/rootage/components/action-button.yaml", "components/*.yaml") + * // → "action-button" + * + * extractId("packages/react/src/components/ActionButton/ActionButton.tsx", "src/components/** /*") + * // → "action-button" + */ +export function extractId( + filePath: string, + watchPattern: string, + packageDir: string, +): string | null { + const relPath = relative(packageDir, filePath); + const patternParts = watchPattern.split("/"); + const pathParts = relPath.split("/"); + + // glob에서 * 위치를 찾아 해당 부분에서 이름 추출 + for (let i = 0; i < patternParts.length; i++) { + const pp = patternParts[i]; + if (pp === undefined || pathParts[i] === undefined) continue; + + if (pp.includes("*") && !pp.includes("**")) { + // "*.yaml" → 파일명에서 확장자 제거 + const name = basename(pathParts[i]!, getExtFromGlob(pp)); + return toKebabCase(name); + } + } + + // ** 패턴이면 첫 번째 디렉토리명을 ID로 사용 + if (watchPattern.includes("**")) { + const afterStatic = getStaticPrefixLength(watchPattern); + const segment = pathParts[afterStatic]; + if (segment) { + return toKebabCase(basename(segment, getExtFromPath(segment))); + } + } + + return null; +} + +/** + * 템플릿 경로에 {id} 또는 {Id} 플레이스홀더가 있는지 확인 + */ +export function hasTemplate(path: string): boolean { + return path.includes("{id}") || path.includes("{Id}"); +} + +function toPascalCase(kebab: string): string { + return kebab + .split("-") + .map((part) => part.charAt(0).toUpperCase() + part.slice(1)) + .join(""); +} + +export function toKebabCase(name: string): string { + // PascalCase → kebab-case + return name + .replace(/([a-z0-9])([A-Z])/g, "$1-$2") + .replace(/([A-Z])([A-Z][a-z])/g, "$1-$2") + .toLowerCase(); +} + +function getExtFromGlob(pattern: string): string { + const dotIdx = pattern.lastIndexOf("."); + return dotIdx >= 0 ? pattern.slice(dotIdx) : ""; +} + +function getExtFromPath(filename: string): string { + const dotIdx = filename.lastIndexOf("."); + return dotIdx >= 0 ? filename.slice(dotIdx) : ""; +} + +function getStaticPrefixLength(pattern: string): number { + const parts = pattern.split("/"); + let count = 0; + for (const p of parts) { + if (p.includes("*")) break; + count++; + } + return count; +} diff --git a/ecosystem/kontext/core/src/schema.ts b/ecosystem/kontext/core/src/schema.ts new file mode 100644 index 000000000..c27297a04 --- /dev/null +++ b/ecosystem/kontext/core/src/schema.ts @@ -0,0 +1,151 @@ +import type { AffectedEntry, KontextConfig, Override, Relation } from "./types.js"; + +const CURRENT_API_VERSION = "kontext/v1"; + +function parseAffects(entries: Array>): AffectedEntry[] { + return entries.map((entry) => ({ + path: entry.path as string, + reason: entry.reason as string | undefined, + generated: (entry.generated as boolean | undefined) ?? false, + command: entry.command as string | undefined, + optional: (entry.optional as boolean | undefined) ?? false, + })); +} + +export class SchemaValidationError extends Error { + constructor( + public readonly filePath: string, + public readonly issues: string[], + ) { + super(`Invalid kontext.yaml at ${filePath}:\n ${issues.join("\n ")}`); + this.name = "SchemaValidationError"; + } +} + +function validateStringArray(value: unknown, fieldName: string, issues: string[]): string[] { + if (value === undefined) return []; + if (!Array.isArray(value)) { + issues.push(`${fieldName} must be an array of strings`); + return []; + } + for (let i = 0; i < value.length; i++) { + if (typeof value[i] !== "string") { + issues.push(`${fieldName}[${i}] must be a string`); + } + } + return value as string[]; +} + +export function validateConfig(data: unknown, filePath: string): KontextConfig { + const issues: string[] = []; + + if (typeof data !== "object" || data === null || Array.isArray(data)) { + issues.push("Root must be an object with apiVersion and relations"); + throw new SchemaValidationError(filePath, issues); + } + + const obj = data as Record; + + if (obj.apiVersion !== CURRENT_API_VERSION) { + issues.push(`apiVersion must be "${CURRENT_API_VERSION}", got "${String(obj.apiVersion)}"`); + } + + // ignore (package-level) + const ignore = validateStringArray(obj.ignore, "ignore", issues); + + if (!Array.isArray(obj.relations)) { + issues.push('"relations" must be an array'); + throw new SchemaValidationError(filePath, issues); + } + + for (let i = 0; i < obj.relations.length; i++) { + const rel = obj.relations[i] as Record; + const prefix = `relations[${i}]`; + + if (typeof rel.when !== "string" || rel.when.length === 0) { + issues.push(`${prefix}.when must be a non-empty string`); + } + + // exclude (relation-level) + validateStringArray(rel.exclude, `${prefix}.exclude`, issues); + + if (!Array.isArray(rel.affects)) { + issues.push(`${prefix}.affects must be an array`); + continue; + } + + for (let j = 0; j < rel.affects.length; j++) { + const entry = rel.affects[j] as Record; + const entryPrefix = `${prefix}.affects[${j}]`; + + if (typeof entry.path !== "string" || entry.path.length === 0) { + issues.push(`${entryPrefix}.path must be a non-empty string`); + } + if (entry.reason !== undefined && typeof entry.reason !== "string") { + issues.push(`${entryPrefix}.reason must be a string`); + } + if (entry.generated !== undefined && typeof entry.generated !== "boolean") { + issues.push(`${entryPrefix}.generated must be a boolean`); + } + if (entry.command !== undefined && typeof entry.command !== "string") { + issues.push(`${entryPrefix}.command must be a string`); + } + if (entry.optional !== undefined && typeof entry.optional !== "boolean") { + issues.push(`${entryPrefix}.optional must be a boolean`); + } + } + + // overrides (relation-level) + if (rel.overrides !== undefined) { + if (!Array.isArray(rel.overrides)) { + issues.push(`${prefix}.overrides must be an array`); + } else { + for (let k = 0; k < rel.overrides.length; k++) { + const ov = rel.overrides[k] as Record; + const ovPrefix = `${prefix}.overrides[${k}]`; + + if (typeof ov.match !== "string" || ov.match.length === 0) { + issues.push(`${ovPrefix}.match must be a non-empty string`); + } + if (!Array.isArray(ov.affects)) { + issues.push(`${ovPrefix}.affects must be an array`); + } else { + for (let m = 0; m < ov.affects.length; m++) { + const ovEntry = ov.affects[m] as Record; + const ovEntryPrefix = `${ovPrefix}.affects[${m}]`; + if (typeof ovEntry.path !== "string" || ovEntry.path.length === 0) { + issues.push(`${ovEntryPrefix}.path must be a non-empty string`); + } + } + } + } + } + } + } + + if (issues.length > 0) { + throw new SchemaValidationError(filePath, issues); + } + + return { + apiVersion: CURRENT_API_VERSION, + ignore: ignore.length > 0 ? ignore : undefined, + relations: (obj.relations as Array>).map( + (rel): Relation => ({ + when: rel.when as string, + exclude: (rel.exclude as string[] | undefined)?.length + ? (rel.exclude as string[]) + : undefined, + affects: parseAffects(rel.affects as Array>), + overrides: Array.isArray(rel.overrides) + ? (rel.overrides as Array>).map( + (ov): Override => ({ + match: ov.match as string, + affects: parseAffects(ov.affects as Array>), + }), + ) + : undefined, + }), + ), + }; +} diff --git a/ecosystem/kontext/core/src/types.ts b/ecosystem/kontext/core/src/types.ts new file mode 100644 index 000000000..4bd2da2c6 --- /dev/null +++ b/ecosystem/kontext/core/src/types.ts @@ -0,0 +1,134 @@ +/** kontext.yaml 스키마 타입 */ +export interface KontextConfig { + apiVersion: "kontext/v1"; + /** 이 패키지 전체에서 무시할 glob 패턴 (barrel, deprecated 등) */ + ignore?: string[]; + relations: Relation[]; +} + +export interface Relation { + /** 이 패키지 내 감시할 파일의 glob 패턴 */ + when: string; + /** when 매칭에서 제외할 glob 패턴 */ + exclude?: string[]; + /** 영향받는 파일 목록 */ + affects: AffectedEntry[]; + /** 특정 파일에 대해 기본 affects를 덮어쓰는 규칙 */ + overrides?: Override[]; +} + +export interface Override { + /** 이 패턴에 매칭되는 파일에 대해 기본 affects 대신 이 affects를 적용 */ + match: string; + /** 덮어쓸 affects 목록 */ + affects: AffectedEntry[]; +} + +export interface AffectedEntry { + /** 영향받는 파일 경로 (레포 루트 기준). {id}=kebab, {Id}=Pascal 템플릿 지원 */ + path: string; + /** 왜 영향받는지 설명 */ + reason?: string; + /** 자동 생성 파일인지 */ + generated?: boolean; + /** generated=true일 때 실행할 생성 명령 */ + command?: string; + /** 없어도 check에서 경고하지 않음 */ + optional?: boolean; +} + +/** 빌드된 그래프 노드 */ +export interface GraphNode { + /** 고유 ID (e.g., "packages/rootage/components/button.yaml") */ + id: string; + /** 이 노드가 속한 패키지 디렉토리 (e.g., "packages/rootage") */ + packageDir: string; + /** 파일이 실제 존재하는지 */ + exists: boolean; +} + +/** 빌드된 그래프 엣지 */ +export interface GraphEdge { + /** 소스 파일 (변경 원인) */ + source: string; + /** 타겟 파일 (영향받는 쪽) */ + target: string; + /** 왜 연결됐는지 */ + reason?: string; + /** 자동 생성 파일인지 */ + generated: boolean; + /** generated일 때 실행할 명령 */ + command?: string; + /** 없어도 check에서 경고하지 않음 */ + optional: boolean; + /** 이 엣지를 정의한 kontext.yaml 경로 */ + definedBy: string; +} + +/** 빌드된 전체 그래프 */ +export interface KontextGraph { + nodes: GraphNode[]; + edges: GraphEdge[]; + /** 발견된 패키지 목록 (kontext.yaml이 있는 디렉토리) */ + packages: string[]; + /** 그래프 빌드 시각 */ + builtAt: string; +} + +/** kontext deps 명령의 결과 항목 */ +export interface DepResult { + path: string; + reason?: string; + generated: boolean; + command?: string; + exists: boolean; +} + +/** kontext check 명령의 결과 항목 */ +export interface CheckResult { + /** watch 패턴에 매칭된 소스 파일 */ + source: string; + /** 이 결과를 정의한 kontext.yaml 경로 */ + definedBy: string; + /** 전체 affects 수 */ + total: number; + /** 존재하는 파일 수 */ + existing: number; + /** 누락된 파일 목록 */ + missing: string[]; +} + +/** kontext lint — 발견된 미선언 관계 */ +export interface LintSuggestion { + source: string; + target: string; + layer: "naming" | "import" | "co-change"; + confidence: number; + detail: string; +} + +/** kontext lint — stale 관계 경고 */ +export interface LintStaleWarning { + source: string; + target: string; + reason: string; +} + +/** kontext lint — 전체 결과 */ +export interface LintResult { + suggestions: LintSuggestion[]; + staleWarnings: LintStaleWarning[]; +} + +/** kontext lint — 옵션 */ +export interface LintOptions { + rootDir: string; + /** git log에서 분석할 커밋 수 (기본 200) */ + commitCount?: number; + /** co-change Jaccard 임계값 (기본 0.7) */ + jaccardThreshold?: number; + /** 최소 co-occurrence 횟수 (기본 3) */ + minCoOccurrences?: number; + /** 무시할 파일 패턴 */ + ignorePatterns?: string[]; +} diff --git a/ecosystem/kontext/core/test/graph-builder.test.ts b/ecosystem/kontext/core/test/graph-builder.test.ts new file mode 100644 index 000000000..5fa6115f0 --- /dev/null +++ b/ecosystem/kontext/core/test/graph-builder.test.ts @@ -0,0 +1,182 @@ +import { afterEach, beforeEach, describe, expect, test } from "bun:test"; +import { mkdirSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { buildGraph } from "../src/graph-builder.js"; + +const TMP = join(import.meta.dirname, "__tmp_test__"); + +function setup(files: Record) { + mkdirSync(TMP, { recursive: true }); + for (const [path, content] of Object.entries(files)) { + const full = join(TMP, path); + mkdirSync(join(full, ".."), { recursive: true }); + writeFileSync(full, content); + } +} + +beforeEach(() => rmSync(TMP, { recursive: true, force: true })); +afterEach(() => rmSync(TMP, { recursive: true, force: true })); + +describe("buildGraph", () => { + test("builds graph from single kontext.yaml", () => { + setup({ + "pkg-a/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "src/*.ts" + affects: + - path: pkg-b/docs/ + reason: update docs +`, + "pkg-a/src/foo.ts": "export const foo = 1;", + "pkg-b/docs/placeholder": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + + expect(graph.packages).toContain("pkg-a"); + expect(graph.edges.length).toBeGreaterThanOrEqual(1); + + const edge = graph.edges.find((e) => e.source === "pkg-a/src/foo.ts"); + expect(edge).toBeDefined(); + expect(edge!.target).toBe("pkg-b/docs/"); + expect(edge!.reason).toBe("update docs"); + expect(edge!.definedBy).toBe("pkg-a/kontext.yaml"); + }); + + test("applies ignore patterns", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +ignore: + - "**/*.test.ts" +relations: + - when: "src/*.ts" + affects: + - path: pkg/docs/ +`, + "pkg/src/main.ts": "", + "pkg/src/main.test.ts": "", + "pkg/docs/placeholder": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + const sources = graph.edges.map((e) => e.source); + + expect(sources).toContain("pkg/src/main.ts"); + expect(sources).not.toContain("pkg/src/main.test.ts"); + }); + + test("applies exclude patterns", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "src/*.ts" + exclude: + - "src/internal.ts" + affects: + - path: pkg/docs/ +`, + "pkg/src/public.ts": "", + "pkg/src/internal.ts": "", + "pkg/docs/placeholder": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + const sources = graph.edges.map((e) => e.source); + + expect(sources).toContain("pkg/src/public.ts"); + expect(sources).not.toContain("pkg/src/internal.ts"); + }); + + test("expands {id} and {Id} templates", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "components/*.yaml" + affects: + - path: src/{Id}/{Id}.tsx +`, + "pkg/components/action-button.yaml": "", + "src/ActionButton/ActionButton.tsx": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + const edge = graph.edges.find((e) => e.source === "pkg/components/action-button.yaml"); + + expect(edge).toBeDefined(); + expect(edge!.target).toBe("src/ActionButton/ActionButton.tsx"); + }); + + test("applies overrides for matching files", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "components/*.yaml" + affects: + - path: docs/{id}.mdx + overrides: + - match: "components/{special-a,special-b}.yaml" + affects: + - path: docs/special/{id}.mdx +`, + "pkg/components/normal.yaml": "", + "pkg/components/special-a.yaml": "", + "docs/normal.mdx": "", + "docs/special/special-a.mdx": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + + const normalEdge = graph.edges.find((e) => e.source === "pkg/components/normal.yaml"); + expect(normalEdge!.target).toBe("docs/normal.mdx"); + + const overrideEdge = graph.edges.find((e) => e.source === "pkg/components/special-a.yaml"); + expect(overrideEdge!.target).toBe("docs/special/special-a.mdx"); + }); + + test("marks non-existent targets", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "src/*.ts" + affects: + - path: does-not-exist/file.ts +`, + "pkg/src/foo.ts": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + const targetNode = graph.nodes.find((n) => n.id === "does-not-exist/file.ts"); + + expect(targetNode).toBeDefined(); + expect(targetNode!.exists).toBe(false); + }); + + test("sets optional flag on edges", () => { + setup({ + "pkg/kontext.yaml": ` +apiVersion: kontext/v1 +relations: + - when: "src/*.ts" + affects: + - path: required/file.ts + - path: optional/file.ts + optional: true +`, + "pkg/src/foo.ts": "", + }); + + const graph = buildGraph({ rootDir: TMP }); + + const required = graph.edges.find((e) => e.target === "required/file.ts"); + const optional = graph.edges.find((e) => e.target === "optional/file.ts"); + + expect(required!.optional).toBe(false); + expect(optional!.optional).toBe(true); + }); +}); diff --git a/ecosystem/kontext/core/test/query.test.ts b/ecosystem/kontext/core/test/query.test.ts new file mode 100644 index 000000000..88efca2e6 --- /dev/null +++ b/ecosystem/kontext/core/test/query.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, test } from "bun:test"; +import { analyzeImpact, checkCompleteness, findAffectedBy, findDeps } from "../src/query.js"; +import type { KontextGraph } from "../src/types.js"; + +function makeGraph(overrides?: Partial): KontextGraph { + return { + nodes: [ + { id: "a.yaml", packageDir: "packages/rootage", exists: true }, + { id: "b.ts", packageDir: "packages/react", exists: true }, + { id: "c.mdx", packageDir: "docs", exists: false }, + { id: "d.tsx", packageDir: "docs", exists: true }, + ], + edges: [ + { + source: "a.yaml", + target: "b.ts", + reason: "update component", + generated: false, + command: undefined, + optional: false, + definedBy: "packages/rootage/kontext.yaml", + }, + { + source: "a.yaml", + target: "c.mdx", + reason: "update docs", + generated: false, + command: undefined, + optional: false, + definedBy: "packages/rootage/kontext.yaml", + }, + { + source: "a.yaml", + target: "d.tsx", + generated: true, + command: "bun generate", + optional: false, + definedBy: "packages/rootage/kontext.yaml", + }, + ], + packages: ["packages/rootage", "packages/react", "docs"], + builtAt: "2026-01-01T00:00:00.000Z", + ...overrides, + }; +} + +describe("findDeps", () => { + test("returns all targets for a source", () => { + const deps = findDeps(makeGraph(), "a.yaml"); + expect(deps).toHaveLength(3); + expect(deps[0]!.path).toBe("b.ts"); + expect(deps[0]!.reason).toBe("update component"); + expect(deps[0]!.exists).toBe(true); + }); + + test("returns generated info", () => { + const deps = findDeps(makeGraph(), "a.yaml"); + const gen = deps.find((d) => d.generated); + expect(gen).toBeDefined(); + expect(gen!.command).toBe("bun generate"); + }); + + test("returns empty for unknown source", () => { + expect(findDeps(makeGraph(), "unknown.ts")).toHaveLength(0); + }); + + test("shows exists=false for missing targets", () => { + const deps = findDeps(makeGraph(), "a.yaml"); + const missing = deps.find((d) => d.path === "c.mdx"); + expect(missing!.exists).toBe(false); + }); +}); + +describe("findAffectedBy", () => { + test("returns sources that affect a target", () => { + const affected = findAffectedBy(makeGraph(), "b.ts"); + expect(affected).toHaveLength(1); + expect(affected[0]!.path).toBe("a.yaml"); + }); + + test("returns empty for non-target", () => { + expect(findAffectedBy(makeGraph(), "a.yaml")).toHaveLength(0); + }); +}); + +describe("checkCompleteness", () => { + test("reports missing files", () => { + const results = checkCompleteness(makeGraph()); + expect(results).toHaveLength(1); // only a.yaml has edges as source + expect(results[0]!.source).toBe("a.yaml"); + expect(results[0]!.definedBy).toBe("packages/rootage/kontext.yaml"); + expect(results[0]!.total).toBe(3); + expect(results[0]!.existing).toBe(2); + expect(results[0]!.missing).toEqual(["c.mdx"]); + }); + + test("skips optional edges in completeness check", () => { + const graph = makeGraph({ + edges: [ + { + source: "a.yaml", + target: "c.mdx", + generated: false, + optional: true, + definedBy: "test.yaml", + }, + ], + }); + const results = checkCompleteness(graph); + expect(results).toHaveLength(0); // optional edge skipped + }); +}); + +describe("analyzeImpact", () => { + test("returns deps for changed files", () => { + const impact = analyzeImpact(makeGraph(), ["a.yaml"]); + expect(impact.size).toBe(1); + expect(impact.get("a.yaml")).toHaveLength(3); + }); + + test("ignores files with no deps", () => { + const impact = analyzeImpact(makeGraph(), ["b.ts", "unknown.ts"]); + expect(impact.size).toBe(0); + }); + + test("handles multiple changed files", () => { + const graph = makeGraph({ + edges: [ + ...makeGraph().edges, + { + source: "b.ts", + target: "d.tsx", + generated: false, + optional: false, + definedBy: "test.yaml", + }, + ], + }); + const impact = analyzeImpact(graph, ["a.yaml", "b.ts"]); + expect(impact.size).toBe(2); + }); +}); diff --git a/ecosystem/kontext/core/test/resolver.test.ts b/ecosystem/kontext/core/test/resolver.test.ts new file mode 100644 index 000000000..05745590f --- /dev/null +++ b/ecosystem/kontext/core/test/resolver.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, test } from "bun:test"; +import { expandTemplate, hasTemplate } from "../src/resolver.js"; + +describe("expandTemplate", () => { + test("replaces {id} with kebab-case", () => { + expect(expandTemplate("packages/css/recipes/{id}.ts", "action-button")).toBe( + "packages/css/recipes/action-button.ts", + ); + }); + + test("replaces {Id} with PascalCase", () => { + expect(expandTemplate("src/components/{Id}/", "action-button")).toBe( + "src/components/ActionButton/", + ); + }); + + test("replaces both {id} and {Id} in same string", () => { + expect(expandTemplate("docs/{Id}/{id}.mdx", "radio-group")).toBe( + "docs/RadioGroup/radio-group.mdx", + ); + }); + + test("replaces multiple {id} occurrences", () => { + expect(expandTemplate("{id}/{id}.ts", "foo")).toBe("foo/foo.ts"); + }); + + test("returns unchanged string without templates", () => { + expect(expandTemplate("packages/css/vars/", "button")).toBe("packages/css/vars/"); + }); + + test("handles single-word id", () => { + expect(expandTemplate("{Id}", "badge")).toBe("Badge"); + expect(expandTemplate("{id}", "badge")).toBe("badge"); + }); + + test("handles multi-segment kebab-case", () => { + expect(expandTemplate("{Id}", "inline-banner")).toBe("InlineBanner"); + expect(expandTemplate("{Id}", "bottom-sheet-close-button")).toBe("BottomSheetCloseButton"); + }); +}); + +describe("hasTemplate", () => { + test("detects {id}", () => { + expect(hasTemplate("packages/{id}.ts")).toBe(true); + }); + + test("detects {Id}", () => { + expect(hasTemplate("src/{Id}/")).toBe(true); + }); + + test("returns false without template", () => { + expect(hasTemplate("packages/css/vars/")).toBe(false); + expect(hasTemplate("plain-string")).toBe(false); + }); +}); diff --git a/ecosystem/kontext/core/test/schema.test.ts b/ecosystem/kontext/core/test/schema.test.ts new file mode 100644 index 000000000..54d465d53 --- /dev/null +++ b/ecosystem/kontext/core/test/schema.test.ts @@ -0,0 +1,133 @@ +import { describe, expect, test } from "bun:test"; +import { SchemaValidationError, validateConfig } from "../src/schema.js"; + +describe("validateConfig", () => { + test("accepts valid minimal config", () => { + const config = validateConfig( + { + apiVersion: "kontext/v1", + relations: [ + { + when: "components/*.yaml", + affects: [{ path: "packages/css/vars/" }], + }, + ], + }, + "test.yaml", + ); + + expect(config.apiVersion).toBe("kontext/v1"); + expect(config.relations).toHaveLength(1); + expect(config.relations[0]!.when).toBe("components/*.yaml"); + expect(config.relations[0]!.affects[0]!.path).toBe("packages/css/vars/"); + expect(config.relations[0]!.affects[0]!.generated).toBe(false); + expect(config.relations[0]!.affects[0]!.optional).toBe(false); + }); + + test("accepts config with all optional fields", () => { + const config = validateConfig( + { + apiVersion: "kontext/v1", + ignore: ["**/*.test.ts"], + relations: [ + { + when: "src/**", + exclude: ["*.test.ts"], + affects: [ + { + path: "docs/{id}.mdx", + reason: "docs update", + generated: true, + command: "bun generate", + optional: true, + }, + ], + overrides: [ + { + match: "src/special/*.ts", + affects: [{ path: "docs/special/{id}.mdx" }], + }, + ], + }, + ], + }, + "test.yaml", + ); + + expect(config.ignore).toEqual(["**/*.test.ts"]); + expect(config.relations[0]!.exclude).toEqual(["*.test.ts"]); + expect(config.relations[0]!.affects[0]!.reason).toBe("docs update"); + expect(config.relations[0]!.affects[0]!.generated).toBe(true); + expect(config.relations[0]!.affects[0]!.command).toBe("bun generate"); + expect(config.relations[0]!.affects[0]!.optional).toBe(true); + expect(config.relations[0]!.overrides).toHaveLength(1); + expect(config.relations[0]!.overrides![0]!.match).toBe("src/special/*.ts"); + }); + + test("rejects wrong apiVersion", () => { + expect(() => validateConfig({ apiVersion: "v2", relations: [] }, "test.yaml")).toThrow( + SchemaValidationError, + ); + }); + + test("rejects missing relations", () => { + expect(() => validateConfig({ apiVersion: "kontext/v1" }, "test.yaml")).toThrow( + SchemaValidationError, + ); + }); + + test("rejects non-object root", () => { + expect(() => validateConfig([], "test.yaml")).toThrow(SchemaValidationError); + expect(() => validateConfig(null, "test.yaml")).toThrow(SchemaValidationError); + expect(() => validateConfig("string", "test.yaml")).toThrow(SchemaValidationError); + }); + + test("rejects relation with empty when", () => { + expect(() => + validateConfig( + { + apiVersion: "kontext/v1", + relations: [{ when: "", affects: [{ path: "a" }] }], + }, + "test.yaml", + ), + ).toThrow(SchemaValidationError); + }); + + test("rejects affects entry with empty path", () => { + expect(() => + validateConfig( + { + apiVersion: "kontext/v1", + relations: [{ when: "*.ts", affects: [{ path: "" }] }], + }, + "test.yaml", + ), + ).toThrow(SchemaValidationError); + }); + + test("rejects invalid type for reason", () => { + expect(() => + validateConfig( + { + apiVersion: "kontext/v1", + relations: [{ when: "*.ts", affects: [{ path: "a", reason: 123 }] }], + }, + "test.yaml", + ), + ).toThrow(SchemaValidationError); + }); + + test("error includes file path and issues", () => { + try { + validateConfig({ apiVersion: "wrong", relations: [] }, "my/file.yaml"); + expect(true).toBe(false); // should not reach + } catch (e) { + expect(e).toBeInstanceOf(SchemaValidationError); + const err = e as SchemaValidationError; + expect(err.filePath).toBe("my/file.yaml"); + expect(err.issues.length).toBeGreaterThan(0); + expect(err.message).toContain("my/file.yaml"); + } + }); +}); diff --git a/ecosystem/kontext/core/tsconfig.json b/ecosystem/kontext/core/tsconfig.json new file mode 100644 index 000000000..bc012c938 --- /dev/null +++ b/ecosystem/kontext/core/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "composite": true, + "esModuleInterop": true, + "skipLibCheck": true, + "target": "es2022", + "allowJs": true, + "resolveJsonModule": true, + "moduleDetection": "force", + "isolatedModules": true, + "verbatimModuleSyntax": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "module": "preserve", + "lib": ["es2022"], + "rootDir": "src", + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["src/**/*.test.*"] +} diff --git a/ecosystem/kontext/dashboard/.gitignore b/ecosystem/kontext/dashboard/.gitignore new file mode 100644 index 000000000..849ddff3b --- /dev/null +++ b/ecosystem/kontext/dashboard/.gitignore @@ -0,0 +1 @@ +dist/ diff --git a/ecosystem/kontext/dashboard/components.json b/ecosystem/kontext/dashboard/components.json new file mode 100644 index 000000000..7e5e94ef1 --- /dev/null +++ b/ecosystem/kontext/dashboard/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "tailwind": { + "config": "", + "css": "src/index.css", + "baseColor": "zinc", + "cssVariables": true, + "prefix": "" + } +} diff --git a/ecosystem/kontext/dashboard/index.html b/ecosystem/kontext/dashboard/index.html new file mode 100644 index 000000000..019ab67fb --- /dev/null +++ b/ecosystem/kontext/dashboard/index.html @@ -0,0 +1,12 @@ + + + + + + Kontext + + +
+ + + diff --git a/ecosystem/kontext/dashboard/package.json b/ecosystem/kontext/dashboard/package.json new file mode 100644 index 000000000..cadc2c1dc --- /dev/null +++ b/ecosystem/kontext/dashboard/package.json @@ -0,0 +1,47 @@ +{ + "name": "@kontext/dashboard", + "version": "0.0.0", + "private": true, + "repository": { + "type": "git", + "url": "git+https://github.com/daangn/seed-design.git", + "directory": "ecosystem/kontext/dashboard" + }, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@radix-ui/react-collapsible": "^1.1.12", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-tooltip": "^1.2.8", + "@tailwindcss/vite": "^4.2.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "geist": "^1.7.0", + "lucide-react": "^1.8.0", + "next-themes": "^0.4.6", + "radix-ui": "^1.4.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "shiki": "^4.0.2", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "tailwindcss": "^4.2.2" + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "typescript": "^5.9.2", + "vite": "^6.3.4" + } +} diff --git a/ecosystem/kontext/dashboard/src/App.tsx b/ecosystem/kontext/dashboard/src/App.tsx new file mode 100644 index 000000000..2f8ab96e4 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/App.tsx @@ -0,0 +1,94 @@ +import { Search } from "lucide-react"; +import { Toaster } from "@/components/ui/sonner"; +import { Button } from "@/components/ui/button"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import { SearchDialog, useSearchShortcut } from "@/components/SearchDialog"; +import { KontextView } from "@/views/KontextView"; +import { useGraph } from "@/hooks/useGraph"; + +export function App() { + const { graph, error, loading, rebuild } = useGraph(); + const search = useSearchShortcut(); + + if (error) { + return ( +
+
+

CONNECTION FAILED

+

{error}

+

+ Run kontext serve to start the server. +

+
+
+ ); + } + + if (!graph || loading) { + return ( +
+
+
+ LOADING +
+
+
+ ); + } + + return ( + +
+ {/* Header */} +
+
+

KONTEXT

+ + {graph.nodes.length} nodes / {graph.edges.length} edges + + +
+ + +
+ + {/* Content — single unified view */} +
+ +
+ + {/* Search dialog — only mount when open to avoid cmdk stealing focus */} + {search.open && ( + {}} + /> + )} +
+ + + ); +} diff --git a/ecosystem/kontext/dashboard/src/components/CodePreview.tsx b/ecosystem/kontext/dashboard/src/components/CodePreview.tsx new file mode 100644 index 000000000..8ffedc68c --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/CodePreview.tsx @@ -0,0 +1,35 @@ +import { useMemo } from "react"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { useShiki } from "@/hooks/useShiki"; +import { cn } from "@/lib/utils"; + +interface CodePreviewProps { + code: string; + lang: string; + className?: string; +} + +export function CodePreview({ code, lang, className }: CodePreviewProps) { + const { highlight, isReady } = useShiki(); + + const html = useMemo( + () => (isReady ? highlight(code, lang) : null), + [code, lang, isReady, highlight], + ); + + return ( + + {html ? ( +
+ ) : ( +
+          {code}
+        
+ )} + + ); +} diff --git a/ecosystem/kontext/dashboard/src/components/FileTree.tsx b/ecosystem/kontext/dashboard/src/components/FileTree.tsx new file mode 100644 index 000000000..a402c33fa --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/FileTree.tsx @@ -0,0 +1,354 @@ +import { useEffect, useRef, useState } from "react"; +import { ChevronDown, ChevronRight, File, Folder, Sparkles } from "lucide-react"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/utils"; +import type { HighlightInfo, HighlightType } from "@/hooks/useHighlightedPaths"; +import type { FileEntry } from "@/types"; + +const HIGHLIGHT_COLORS: Record< + HighlightType, + { bg: string; dot: string; icon: string; label: string } +> = { + when: { + bg: "bg-[oklch(0.15_0.04_250)]", + dot: "bg-[oklch(0.60_0.18_250)]", + icon: "text-[oklch(0.60_0.18_250)]", + label: "Source", + }, + exists: { + bg: "bg-[oklch(0.14_0.04_155)]", + dot: "bg-[oklch(0.60_0.18_155)]", + icon: "text-[oklch(0.60_0.18_155)]", + label: "Affected", + }, + missing: { + bg: "bg-[oklch(0.14_0.04_25)]", + dot: "bg-[oklch(0.55_0.20_25)]", + icon: "text-[oklch(0.55_0.20_25)]", + label: "Missing", + }, + optional: { + bg: "bg-[oklch(0.14_0.04_85)]", + dot: "bg-[oklch(0.70_0.15_85)]", + icon: "text-[oklch(0.70_0.15_85)]", + label: "Optional", + }, + folder: { + bg: "bg-[oklch(0.13_0.02_250)]", + dot: "bg-[oklch(0.50_0.08_250)]", + icon: "text-[oklch(0.50_0.08_250)]", + label: "Contains affected files", + }, +}; + +interface FileTreeProps { + loadDir: (dir: string) => Promise; + getEntries: (dir: string) => FileEntry[] | undefined; + isLoading: (dir: string) => boolean; + onDragFile?: (path: string) => void; + onKontextClick?: (packageDir: string) => void; + /** Currently selected file path */ + selectedPath?: string | null; + /** Paths to highlight with status-based colors */ + highlightedPaths?: Map; + /** Callback when any file/folder is selected */ + onFileSelect?: (path: string) => void; + /** Path to scroll into view and flash */ + scrollToPath?: string | null; + /** Currently selected kontext.yaml package dir (shown as accent in Files tab) */ + activeKontextDir?: string | null; +} + +export function FileTree({ + loadDir, + getEntries, + isLoading, + onDragFile, + onKontextClick, + selectedPath, + highlightedPaths, + onFileSelect, + scrollToPath, + activeKontextDir, +}: FileTreeProps) { + useEffect(() => { + loadDir(""); + }, [loadDir]); + + const rootEntries = getEntries("") ?? []; + const rootLoading = isLoading(""); + + return ( +
+
+ {rootLoading ? ( +
Loading...
+ ) : ( + rootEntries.map((entry) => ( + + )) + )} +
+
+ ); +} + +function TreeNode({ + entry, + depth, + loadDir, + getEntries, + isLoading: isLoadingFn, + onDragFile, + onKontextClick, + selectedPath, + highlightedPaths, + onFileSelect, + scrollToPath, + activeKontextDir, +}: { + entry: FileEntry; + depth: number; + loadDir: (dir: string) => Promise; + getEntries: (dir: string) => FileEntry[] | undefined; + isLoading: (dir: string) => boolean; + onDragFile?: (path: string) => void; + onKontextClick?: (packageDir: string) => void; + selectedPath?: string | null; + highlightedPaths?: Map; + onFileSelect?: (path: string) => void; + scrollToPath?: string | null; + activeKontextDir?: string | null; +}) { + const [expanded, setExpanded] = useState(false); + const btnRef = useRef(null); + + const isDir = entry.type === "directory"; + const children = isDir ? getEntries(entry.path) : undefined; + const loading = isDir ? isLoadingFn(entry.path) : false; + const isKontext = entry.name === "kontext.yaml"; + const isActiveKontext = + isKontext && activeKontextDir && entry.path === `${activeKontextDir}/kontext.yaml`; + const isSelected = selectedPath === entry.path; + const highlightInfo = highlightedPaths?.get(entry.path); + const isHighlighted = !!highlightInfo; + const colors = highlightInfo ? HIGHLIGHT_COLORS[highlightInfo.type] : null; + + // Auto-expand folders that contain highlighted children or the active kontext + useEffect(() => { + if (!isDir || expanded) return; + const kontextPath = activeKontextDir ? `${activeKontextDir}/kontext.yaml` : null; + const shouldExpand = + (kontextPath && kontextPath.startsWith(entry.path + "/")) || + (highlightedPaths && + Array.from(highlightedPaths.keys()).some((p) => p.startsWith(entry.path + "/"))); + if (shouldExpand) { + loadDir(entry.path).then(() => setExpanded(true)); + } + }, [highlightedPaths, activeKontextDir, isDir, expanded, entry.path, loadDir]); + + // Scroll active kontext.yaml into view + useEffect(() => { + if (isActiveKontext && btnRef.current) { + btnRef.current.scrollIntoView({ behavior: "smooth", block: "center" }); + } + }, [isActiveKontext]); + + // Scroll into view when scrollToPath matches + useEffect(() => { + if (scrollToPath === entry.path && btnRef.current) { + btnRef.current.scrollIntoView({ behavior: "smooth", block: "center" }); + btnRef.current.classList.add("animate-flash"); + const timer = setTimeout(() => btnRef.current?.classList.remove("animate-flash"), 1000); + return () => clearTimeout(timer); + } + }, [scrollToPath, entry.path]); + + // Check if any child is kontext.yaml (after loaded) + const hasKontextChild = children?.some((c) => c.name === "kontext.yaml"); + + async function handleClick() { + // Notify file selection + onFileSelect?.(entry.path); + + if (isKontext && onKontextClick) { + const parts = entry.path.split("/"); + parts.pop(); + onKontextClick(parts.join("/")); + } + + if (isDir) { + if (!expanded && !children) { + await loadDir(entry.path); + } + setExpanded(!expanded); + } + } + + function handleDragStart(e: React.DragEvent) { + e.dataTransfer.setData("text/plain", entry.path); + e.dataTransfer.effectAllowed = "copy"; + onDragFile?.(entry.path); + } + + const btn = ( + + ); + + return ( +
+ {isHighlighted && highlightInfo ? ( + + {btn} + + {/* Status label */} +
+ + + {highlightInfo.type === "when" && "Source file"} + {highlightInfo.type === "exists" && "Affected file"} + {highlightInfo.type === "missing" && "File not found on disk"} + {highlightInfo.type === "optional" && "Optional dependency"} + {highlightInfo.type === "folder" && "Contains affected files"} + +
+ + {/* Explanation */} +
+ {highlightInfo.type === "when" && + "Changes to this file may require updates to related files."} + {highlightInfo.type === "exists" && + "This file should be updated when the source changes."} + {highlightInfo.type === "missing" && + "Referenced in kontext.yaml but doesn't exist on disk yet."} + {highlightInfo.type === "optional" && + "May need updating depending on the nature of the change."} + {highlightInfo.type === "folder" && + "Files inside this folder are part of a kontext relationship."} +
+ + {/* Sources — show all when patterns */} + {highlightInfo.sources.length > 0 && ( +
+ {highlightInfo.sources.map((src, i) => ( +
+
+ when:{" "} + {src.when} +
+ {src.reason && ( +
{src.reason}
+ )} +
+ ))} +
+ )} +
+
+ ) : ( + btn + )} + + {expanded && isDir && ( +
+ {loading ? ( +
+ ... +
+ ) : ( + children?.map((child) => ( + + )) + )} +
+ )} +
+ ); +} diff --git a/ecosystem/kontext/dashboard/src/components/ResizeHandle.tsx b/ecosystem/kontext/dashboard/src/components/ResizeHandle.tsx new file mode 100644 index 000000000..b8fab4e94 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ResizeHandle.tsx @@ -0,0 +1,64 @@ +import { useCallback, useRef } from "react"; +import { cn } from "@/lib/utils"; + +interface ResizeHandleProps { + onResize: (delta: number) => void; + direction?: "left" | "right"; + className?: string; +} + +export function ResizeHandle({ onResize, direction = "left", className }: ResizeHandleProps) { + const startX = useRef(0); + const isDragging = useRef(false); + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + startX.current = e.clientX; + isDragging.current = true; + + function onMouseMove(ev: MouseEvent) { + if (!isDragging.current) return; + const delta = ev.clientX - startX.current; + startX.current = ev.clientX; + // "left" means dragging left edge: moving left = grow, moving right = shrink + onResize(direction === "left" ? -delta : delta); + } + + function onMouseUp() { + isDragging.current = false; + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + } + + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }, + [onResize, direction], + ); + + return ( +
+ {/* Visible drag indicator on hover */} +
+
+
+
+
+
+
+ ); +} diff --git a/ecosystem/kontext/dashboard/src/components/SearchDialog.tsx b/ecosystem/kontext/dashboard/src/components/SearchDialog.tsx new file mode 100644 index 000000000..048cf5bc7 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/SearchDialog.tsx @@ -0,0 +1,95 @@ +import { useEffect, useMemo, useState } from "react"; +import { ArrowRight, ArrowLeft } from "lucide-react"; +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { searchFiles } from "@/lib/graph-utils"; +import { getPackageLabel } from "@/lib/packages"; +import type { KontextGraph, SearchResult } from "@/types"; + +interface SearchDialogProps { + graph: KontextGraph; + open: boolean; + onOpenChange: (open: boolean) => void; + onNavigate: (packageDir: string) => void; +} + +export function SearchDialog({ graph, open, onOpenChange, onNavigate }: SearchDialogProps) { + const [query, setQuery] = useState(""); + + const results = useMemo(() => searchFiles(graph, query), [graph, query]); + + const forwardResults = results.filter((r) => r.direction === "affects"); + const reverseResults = results.filter((r) => r.direction === "affected-by"); + + useEffect(() => { + if (!open) setQuery(""); + }, [open]); + + function handleSelect(result: SearchResult) { + onNavigate(result.packageDir); + onOpenChange(false); + } + + return ( + + + + + No files found. + + + {forwardResults.length > 0 && ( + + {forwardResults.slice(0, 15).map((r, i) => ( + handleSelect(r)} className="gap-2"> + + {r.relatedFile} + + {getPackageLabel(r.packageDir)} + + + ))} + + )} + + {reverseResults.length > 0 && ( + + {reverseResults.slice(0, 15).map((r, i) => ( + handleSelect(r)} className="gap-2"> + + {r.relatedFile} + + {getPackageLabel(r.packageDir)} + + + ))} + + )} + + + ); +} + +/** Hook for keyboard shortcut */ +export function useSearchShortcut() { + const [open, setOpen] = useState(false); + + useEffect(() => { + function onKeyDown(e: KeyboardEvent) { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((o) => !o); + } + } + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + }, []); + + return { open, setOpen }; +} diff --git a/ecosystem/kontext/dashboard/src/components/ui/badge.tsx b/ecosystem/kontext/dashboard/src/components/ui/badge.tsx new file mode 100644 index 000000000..6eb2a057a --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/badge.tsx @@ -0,0 +1,48 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90", + secondary: + "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90", + destructive: + "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90", + outline: + "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground", + link: "text-primary underline-offset-4 [a&]:hover:underline", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function Badge({ + className, + variant = "default", + asChild = false, + ...props +}: React.ComponentProps<"span"> & + VariantProps & { asChild?: boolean }) { + const Comp = asChild ? Slot.Root : "span" + + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/ecosystem/kontext/dashboard/src/components/ui/button.tsx b/ecosystem/kontext/dashboard/src/components/ui/button.tsx new file mode 100644 index 000000000..4d38506ce --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/button.tsx @@ -0,0 +1,64 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Slot } from "radix-ui" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40", + outline: + "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: + "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-9 px-4 py-2 has-[>svg]:px-3", + xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3", + sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5", + lg: "h-10 rounded-md px-6 has-[>svg]:px-4", + icon: "size-9", + "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3", + "icon-sm": "size-8", + "icon-lg": "size-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +function Button({ + className, + variant = "default", + size = "default", + asChild = false, + ...props +}: React.ComponentProps<"button"> & + VariantProps & { + asChild?: boolean + }) { + const Comp = asChild ? Slot.Root : "button" + + return ( + + ) +} + +export { Button, buttonVariants } diff --git a/ecosystem/kontext/dashboard/src/components/ui/collapsible.tsx b/ecosystem/kontext/dashboard/src/components/ui/collapsible.tsx new file mode 100644 index 000000000..63fc8eff3 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/collapsible.tsx @@ -0,0 +1,31 @@ +import { Collapsible as CollapsiblePrimitive } from "radix-ui" + +function Collapsible({ + ...props +}: React.ComponentProps) { + return +} + +function CollapsibleTrigger({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CollapsibleContent({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Collapsible, CollapsibleTrigger, CollapsibleContent } diff --git a/ecosystem/kontext/dashboard/src/components/ui/command.tsx b/ecosystem/kontext/dashboard/src/components/ui/command.tsx new file mode 100644 index 000000000..8fe3ccb40 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/command.tsx @@ -0,0 +1,184 @@ +"use client" + +import * as React from "react" +import { Command as CommandPrimitive } from "cmdk" +import { SearchIcon } from "lucide-react" + +import { cn } from "@/lib/utils" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" + +function Command({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandDialog({ + title = "Command Palette", + description = "Search for a command to run...", + children, + className, + showCloseButton = true, + ...props +}: React.ComponentProps & { + title?: string + description?: string + className?: string + showCloseButton?: boolean +}) { + return ( + + + {title} + {description} + + + + {children} + + + + ) +} + +function CommandInput({ + className, + ...props +}: React.ComponentProps) { + return ( +
+ + +
+ ) +} + +function CommandList({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandEmpty({ + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandGroup({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandItem({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function CommandShortcut({ + className, + ...props +}: React.ComponentProps<"span">) { + return ( + + ) +} + +export { + Command, + CommandDialog, + CommandInput, + CommandList, + CommandEmpty, + CommandGroup, + CommandItem, + CommandShortcut, + CommandSeparator, +} diff --git a/ecosystem/kontext/dashboard/src/components/ui/dialog.tsx b/ecosystem/kontext/dashboard/src/components/ui/dialog.tsx new file mode 100644 index 000000000..84bdef4bb --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/dialog.tsx @@ -0,0 +1,158 @@ +"use client" + +import * as React from "react" +import { XIcon } from "lucide-react" +import { Dialog as DialogPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" + +function Dialog({ + ...props +}: React.ComponentProps) { + return +} + +function DialogTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function DialogPortal({ + ...props +}: React.ComponentProps) { + return +} + +function DialogClose({ + ...props +}: React.ComponentProps) { + return +} + +function DialogOverlay({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogContent({ + className, + children, + showCloseButton = true, + ...props +}: React.ComponentProps & { + showCloseButton?: boolean +}) { + return ( + + + + {children} + {showCloseButton && ( + + + Close + + )} + + + ) +} + +function DialogHeader({ className, ...props }: React.ComponentProps<"div">) { + return ( +
+ ) +} + +function DialogFooter({ + className, + showCloseButton = false, + children, + ...props +}: React.ComponentProps<"div"> & { + showCloseButton?: boolean +}) { + return ( +
+ {children} + {showCloseButton && ( + + + + )} +
+ ) +} + +function DialogTitle({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function DialogDescription({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogOverlay, + DialogPortal, + DialogTitle, + DialogTrigger, +} diff --git a/ecosystem/kontext/dashboard/src/components/ui/input.tsx b/ecosystem/kontext/dashboard/src/components/ui/input.tsx new file mode 100644 index 000000000..f1124aea1 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/input.tsx @@ -0,0 +1,21 @@ +import * as React from "react" + +import { cn } from "@/lib/utils" + +function Input({ className, type, ...props }: React.ComponentProps<"input">) { + return ( + + ) +} + +export { Input } diff --git a/ecosystem/kontext/dashboard/src/components/ui/scroll-area.tsx b/ecosystem/kontext/dashboard/src/components/ui/scroll-area.tsx new file mode 100644 index 000000000..e38a492f2 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/scroll-area.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { ScrollArea as ScrollAreaPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function ScrollArea({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + + ) +} + +function ScrollBar({ + className, + orientation = "vertical", + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { ScrollArea, ScrollBar } diff --git a/ecosystem/kontext/dashboard/src/components/ui/separator.tsx b/ecosystem/kontext/dashboard/src/components/ui/separator.tsx new file mode 100644 index 000000000..cd873e363 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/separator.tsx @@ -0,0 +1,28 @@ +"use client" + +import * as React from "react" +import { Separator as SeparatorPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Separator({ + className, + orientation = "horizontal", + decorative = true, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Separator } diff --git a/ecosystem/kontext/dashboard/src/components/ui/sonner.tsx b/ecosystem/kontext/dashboard/src/components/ui/sonner.tsx new file mode 100644 index 000000000..9f46e06d5 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/sonner.tsx @@ -0,0 +1,38 @@ +import { + CircleCheckIcon, + InfoIcon, + Loader2Icon, + OctagonXIcon, + TriangleAlertIcon, +} from "lucide-react" +import { useTheme } from "next-themes" +import { Toaster as Sonner, type ToasterProps } from "sonner" + +const Toaster = ({ ...props }: ToasterProps) => { + const { theme = "system" } = useTheme() + + return ( + , + info: , + warning: , + error: , + loading: , + }} + style={ + { + "--normal-bg": "var(--popover)", + "--normal-text": "var(--popover-foreground)", + "--normal-border": "var(--border)", + "--border-radius": "var(--radius)", + } as React.CSSProperties + } + {...props} + /> + ) +} + +export { Toaster } diff --git a/ecosystem/kontext/dashboard/src/components/ui/tabs.tsx b/ecosystem/kontext/dashboard/src/components/ui/tabs.tsx new file mode 100644 index 000000000..7f2c4f20b --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/tabs.tsx @@ -0,0 +1,89 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" +import { Tabs as TabsPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function Tabs({ + className, + orientation = "horizontal", + ...props +}: React.ComponentProps) { + return ( + + ) +} + +const tabsListVariants = cva( + "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none", + { + variants: { + variant: { + default: "bg-muted", + line: "gap-1 bg-transparent", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +function TabsList({ + className, + variant = "default", + ...props +}: React.ComponentProps & + VariantProps) { + return ( + + ) +} + +function TabsTrigger({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function TabsContent({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants } diff --git a/ecosystem/kontext/dashboard/src/components/ui/tooltip.tsx b/ecosystem/kontext/dashboard/src/components/ui/tooltip.tsx new file mode 100644 index 000000000..ec65c1e42 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/components/ui/tooltip.tsx @@ -0,0 +1,57 @@ +"use client" + +import * as React from "react" +import { Tooltip as TooltipPrimitive } from "radix-ui" + +import { cn } from "@/lib/utils" + +function TooltipProvider({ + delayDuration = 0, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function Tooltip({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipTrigger({ + ...props +}: React.ComponentProps) { + return +} + +function TooltipContent({ + className, + sideOffset = 0, + children, + ...props +}: React.ComponentProps) { + return ( + + + {children} + + + + ) +} + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } diff --git a/ecosystem/kontext/dashboard/src/hooks/useConfig.ts b/ecosystem/kontext/dashboard/src/hooks/useConfig.ts new file mode 100644 index 000000000..b624f9524 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useConfig.ts @@ -0,0 +1,38 @@ +import { useCallback, useState } from "react"; +import type { KontextConfig } from "@/types"; + +export function useConfig() { + const [config, setConfig] = useState(null); + const [saving, setSaving] = useState(false); + + const loadConfig = useCallback(async (packageDir: string) => { + try { + const res = await fetch(`/api/config/${encodeURIComponent(packageDir)}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = (await res.json()) as KontextConfig; + setConfig(data); + } catch { + setConfig({ content: "", exists: false }); + } + }, []); + + const saveConfig = useCallback(async (packageDir: string, content: string): Promise => { + setSaving(true); + try { + const res = await fetch(`/api/config/${encodeURIComponent(packageDir)}`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ content }), + }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + setConfig({ content, exists: true }); + return true; + } catch { + return false; + } finally { + setSaving(false); + } + }, []); + + return { config, saving, loadConfig, saveConfig }; +} diff --git a/ecosystem/kontext/dashboard/src/hooks/useFileContent.ts b/ecosystem/kontext/dashboard/src/hooks/useFileContent.ts new file mode 100644 index 000000000..f79c4efaf --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useFileContent.ts @@ -0,0 +1,63 @@ +import { useCallback, useState } from "react"; + +export interface FileContent { + content: string; + path: string; + size: number; + extension: string; +} + +interface UseFileContentReturn { + fileContent: FileContent | null; + loading: boolean; + error: string | null; + isBinary: boolean; + loadFile: (path: string) => Promise; +} + +export function useFileContent(): UseFileContentReturn { + const [fileContent, setFileContent] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [isBinary, setIsBinary] = useState(false); + + const loadFile = useCallback(async (path: string) => { + setLoading(true); + setError(null); + setIsBinary(false); + + try { + const res = await fetch(`/api/file-content?path=${encodeURIComponent(path)}`); + + if (res.status === 413) { + setError("File too large (max 512KB)"); + setFileContent(null); + return; + } + + if (res.status === 415) { + setIsBinary(true); + setError("Binary file — cannot preview"); + setFileContent(null); + return; + } + + if (!res.ok) { + const data = await res.json().catch(() => ({ error: "Failed to load file" })); + setError(data.error ?? "Failed to load file"); + setFileContent(null); + return; + } + + const data: FileContent = await res.json(); + setFileContent(data); + } catch { + setError("Network error"); + setFileContent(null); + } finally { + setLoading(false); + } + }, []); + + return { fileContent, loading, error, isBinary, loadFile }; +} diff --git a/ecosystem/kontext/dashboard/src/hooks/useFileTree.ts b/ecosystem/kontext/dashboard/src/hooks/useFileTree.ts new file mode 100644 index 000000000..e9de8c5cf --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useFileTree.ts @@ -0,0 +1,39 @@ +import { useCallback, useRef, useState } from "react"; +import type { FileEntry } from "@/types"; + +export function useFileTree() { + const [entries, setEntries] = useState>(new Map()); + const [loading, setLoading] = useState>(new Set()); + const entriesRef = useRef(entries); + entriesRef.current = entries; + + const loadDir = useCallback(async (dir: string) => { + if (entriesRef.current.has(dir)) return; + + setLoading((prev) => new Set(prev).add(dir)); + try { + const params = dir ? `?dir=${encodeURIComponent(dir)}` : ""; + const res = await fetch(`/api/files${params}`); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = (await res.json()) as FileEntry[]; + setEntries((prev) => new Map(prev).set(dir, data)); + } catch { + setEntries((prev) => new Map(prev).set(dir, [])); + } finally { + setLoading((prev) => { + const next = new Set(prev); + next.delete(dir); + return next; + }); + } + }, []); + + const getEntries = useCallback( + (dir: string): FileEntry[] | undefined => entries.get(dir), + [entries], + ); + + const isLoading = useCallback((dir: string): boolean => loading.has(dir), [loading]); + + return { loadDir, getEntries, isLoading }; +} diff --git a/ecosystem/kontext/dashboard/src/hooks/useGraph.ts b/ecosystem/kontext/dashboard/src/hooks/useGraph.ts new file mode 100644 index 000000000..0de959c06 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useGraph.ts @@ -0,0 +1,44 @@ +import { useCallback, useEffect, useState } from "react"; +import type { KontextGraph } from "@/types"; + +export function useGraph() { + const [graph, setGraph] = useState(null); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(true); + + const fetchGraph = useCallback(() => { + setLoading(true); + fetch("/api/graph") + .then((res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`); + return res.json(); + }) + .then((data) => { + setGraph(data as KontextGraph); + setError(null); + }) + .catch((err) => setError(err.message)) + .finally(() => setLoading(false)); + }, []); + + useEffect(() => { + fetchGraph(); + }, [fetchGraph]); + + const rebuild = useCallback(async () => { + setLoading(true); + try { + const res = await fetch("/api/rebuild", { method: "POST" }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const data = await res.json(); + setGraph(data as KontextGraph); + setError(null); + } catch (err) { + setError(err instanceof Error ? err.message : "Rebuild failed"); + } finally { + setLoading(false); + } + }, []); + + return { graph, error, loading, rebuild, refetch: fetchGraph }; +} diff --git a/ecosystem/kontext/dashboard/src/hooks/useHighlightedPaths.ts b/ecosystem/kontext/dashboard/src/hooks/useHighlightedPaths.ts new file mode 100644 index 000000000..905bb130b --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useHighlightedPaths.ts @@ -0,0 +1,97 @@ +import { useMemo } from "react"; +import type { KontextGraph } from "@/types"; + +export type HighlightType = "when" | "exists" | "missing" | "optional" | "folder"; + +/** A single source that causes this file to be highlighted */ +export interface HighlightSource { + when: string; + reason?: string; +} + +export interface HighlightInfo { + type: HighlightType; + /** All when patterns that reference this file (supports multiple) */ + sources: HighlightSource[]; + /** Which kontext.yaml defines this relationship */ + definedBy?: string; +} + +interface EditableRelation { + when: string; + affects: { path: string; reason?: string; optional: boolean }[]; +} + +/** + * Builds a Map of file paths → highlight info from the current relations. + * Supports multiple when patterns referencing the same file. + */ +export function useHighlightedPaths( + relations: EditableRelation[], + graph: KontextGraph, + selectedPackage?: string | null, +): Map { + return useMemo(() => { + const map = new Map(); + if (relations.length === 0) return map; + + const nodeExistsSet = new Set(graph.nodes.filter((n) => n.exists).map((n) => n.id)); + const definedBy = selectedPackage ? `${selectedPackage}/kontext.yaml` : undefined; + + for (const rel of relations) { + if (!rel.when) continue; + + // when paths — source files + addOrMerge(map, rel.when, { + type: "when", + sources: [{ when: rel.when, reason: "Source pattern" }], + definedBy, + }); + + // affects paths + for (const aff of rel.affects) { + if (!aff.path) continue; + const type: HighlightType = aff.optional + ? "optional" + : nodeExistsSet.has(aff.path) + ? "exists" + : "missing"; + addOrMerge(map, aff.path, { + type, + sources: [{ when: rel.when, reason: aff.reason }], + definedBy, + }); + } + } + + // Add parent folders + const folderPaths = new Set(); + for (const path of map.keys()) { + const parts = path.split("/"); + for (let i = 1; i < parts.length; i++) { + folderPaths.add(parts.slice(0, i).join("/")); + } + } + for (const folder of folderPaths) { + if (!map.has(folder)) { + map.set(folder, { type: "folder", sources: [], definedBy }); + } + } + + return map; + }, [relations, graph, selectedPackage]); +} + +function addOrMerge(map: Map, path: string, info: HighlightInfo) { + const existing = map.get(path); + if (existing) { + // Merge sources — add new when patterns that aren't already tracked + for (const src of info.sources) { + if (!existing.sources.some((s) => s.when === src.when)) { + existing.sources.push(src); + } + } + } else { + map.set(path, { ...info, sources: [...info.sources] }); + } +} diff --git a/ecosystem/kontext/dashboard/src/hooks/useShiki.ts b/ecosystem/kontext/dashboard/src/hooks/useShiki.ts new file mode 100644 index 000000000..3ed00f3bd --- /dev/null +++ b/ecosystem/kontext/dashboard/src/hooks/useShiki.ts @@ -0,0 +1,98 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Highlighter = any; + +let highlighterPromise: Promise | null = null; + +async function getHighlighter(): Promise { + if (!highlighterPromise) { + highlighterPromise = import("shiki/bundle/web").then((mod) => + mod.createHighlighter({ + themes: ["github-dark-dimmed"], + langs: [ + "typescript", + "tsx", + "javascript", + "jsx", + "css", + "json", + "yaml", + "html", + "markdown", + "mdx", + "bash", + ], + }), + ); + } + return highlighterPromise; +} + +export function useShiki() { + const [isReady, setIsReady] = useState(false); + const hlRef = useRef(null); + + useEffect(() => { + getHighlighter().then((hl) => { + hlRef.current = hl; + setIsReady(true); + }); + }, []); + + const highlight = useCallback( + (code: string, lang: string): string | null => { + if (!hlRef.current) return null; + try { + const loadedLangs = hlRef.current.getLoadedLanguages(); + const safeLang = loadedLangs.includes(lang) ? lang : "text"; + return hlRef.current.codeToHtml(code, { + lang: safeLang, + theme: "github-dark-dimmed", + transformers: [ + { + pre(node: { properties?: { style?: string } }) { + // Remove background color to match dashboard theme + if (node.properties?.style) { + node.properties.style = (node.properties.style as string).replace( + /background-color:[^;]+;?/, + "", + ); + } + }, + }, + ], + }); + } catch { + return null; + } + }, + [isReady], + ); + + return { highlight, isReady }; +} + +const EXT_TO_LANG: Record = { + ".ts": "typescript", + ".tsx": "tsx", + ".js": "javascript", + ".jsx": "jsx", + ".mjs": "javascript", + ".cjs": "javascript", + ".css": "css", + ".json": "json", + ".yaml": "yaml", + ".yml": "yaml", + ".html": "html", + ".md": "markdown", + ".mdx": "mdx", + ".sh": "bash", + ".bash": "bash", + ".toml": "text", + ".txt": "text", +}; + +export function extToLang(ext: string): string { + return EXT_TO_LANG[ext] ?? "text"; +} diff --git a/ecosystem/kontext/dashboard/src/index.css b/ecosystem/kontext/dashboard/src/index.css new file mode 100644 index 000000000..03440835e --- /dev/null +++ b/ecosystem/kontext/dashboard/src/index.css @@ -0,0 +1,256 @@ +@import "tailwindcss"; + +/* Variable fonts loaded from node_modules via Vite's URL resolution */ +@font-face { + font-family: "Geist"; + src: url("/node_modules/geist/dist/fonts/geist-sans/Geist-Variable.woff2") format("woff2"); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "Geist Mono"; + src: url("/node_modules/geist/dist/fonts/geist-mono/GeistMono-Variable.woff2") format("woff2"); + font-weight: 100 900; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: "GeistPixelSquare"; + src: url("/node_modules/geist/dist/fonts/geist-pixel/GeistPixel-Square.woff2") format("woff2"); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@custom-variant dark (&:is(.dark *)); + +@theme { + --font-sans: "Geist", ui-sans-serif, system-ui, sans-serif; + --font-mono: "Geist Mono", ui-monospace, monospace; + + --color-background: oklch(0.07 0.005 260); + --color-foreground: oklch(0.93 0.005 260); + --color-card: oklch(0.10 0.005 260); + --color-card-foreground: oklch(0.93 0.005 260); + --color-popover: oklch(0.10 0.005 260); + --color-popover-foreground: oklch(0.93 0.005 260); + --color-primary: oklch(0.65 0.15 250); + --color-primary-foreground: oklch(0.98 0.005 260); + --color-secondary: oklch(0.18 0.005 260); + --color-secondary-foreground: oklch(0.85 0.005 260); + --color-muted: oklch(0.18 0.005 260); + --color-muted-foreground: oklch(0.55 0.005 260); + --color-accent: oklch(0.18 0.01 260); + --color-accent-foreground: oklch(0.93 0.005 260); + --color-destructive: oklch(0.55 0.2 25); + --color-destructive-foreground: oklch(0.98 0.005 260); + --color-border: oklch(0.20 0.005 260); + --color-input: oklch(0.20 0.005 260); + --color-ring: oklch(0.65 0.15 250); + + --color-sidebar-background: oklch(0.09 0.005 260); + --color-sidebar-foreground: oklch(0.93 0.005 260); + --color-sidebar-primary: oklch(0.65 0.15 250); + --color-sidebar-primary-foreground: oklch(0.98 0.005 260); + --color-sidebar-accent: oklch(0.15 0.01 260); + --color-sidebar-accent-foreground: oklch(0.93 0.005 260); + --color-sidebar-border: oklch(0.18 0.005 260); + --color-sidebar-ring: oklch(0.65 0.15 250); + + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + + --color-pkg-rootage: oklch(0.65 0.15 250); + --color-pkg-qvism: oklch(0.65 0.18 155); + --color-pkg-css: oklch(0.65 0.15 290); + --color-pkg-react: oklch(0.65 0.18 340); + --color-pkg-headless: oklch(0.65 0.15 55); + --color-pkg-cli: oklch(0.75 0.15 90); + --color-pkg-docs: oklch(0.65 0.12 220); + --color-pkg-ecosystem: oklch(0.50 0.02 260); +} + +@layer base { + *, + ::after, + ::before { + border-color: var(--color-border); + } + + body { + background-color: var(--color-background); + color: var(--color-foreground); + font-family: var(--font-sans); + } + + button, + [role="tab"], + [role="button"], + summary, + a[href] { + cursor: pointer; + } +} + +/* Bayer 4x4 dithering pattern */ +.dither-bg { + position: relative; +} + +.dither-bg::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + opacity: 0.06; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Crect x='0' y='0' width='2' height='2' fill='%23fff' opacity='0.0625'/%3E%3Crect x='4' y='0' width='2' height='2' fill='%23fff' opacity='0.5625'/%3E%3Crect x='2' y='2' width='2' height='2' fill='%23fff' opacity='0.8125'/%3E%3Crect x='6' y='2' width='2' height='2' fill='%23fff' opacity='0.3125'/%3E%3Crect x='0' y='4' width='2' height='2' fill='%23fff' opacity='0.75'/%3E%3Crect x='4' y='4' width='2' height='2' fill='%23fff' opacity='0.1875'/%3E%3Crect x='2' y='6' width='2' height='2' fill='%23fff' opacity='0.4375'/%3E%3Crect x='6' y='6' width='2' height='2' fill='%23fff' opacity='0.9375'/%3E%3C/svg%3E"); + background-repeat: repeat; + background-size: 8px 8px; + z-index: 0; +} + +.dither-bg > * { + position: relative; + z-index: 1; +} + +.dither-divider { + height: 1px; + background: repeating-linear-gradient( + 90deg, + var(--color-border) 0px, + var(--color-border) 2px, + transparent 2px, + transparent 4px + ); +} + +.font-pixel { + font-family: "GeistPixelSquare", "Geist Mono", monospace; + font-feature-settings: normal; + letter-spacing: 0.05em; +} + +/* Dithered header accent bar */ +.dither-accent-bar { + height: 2px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='2'%3E%3Crect x='0' y='0' width='2' height='2' fill='%234a9eff' opacity='0.6'/%3E%3Crect x='2' y='0' width='2' height='2' fill='%234a9eff' opacity='0.2'/%3E%3C/svg%3E"); + background-repeat: repeat-x; + background-size: 4px 2px; +} + +/* Stagger animation for relation cards */ +@keyframes fadeSlideIn { + from { + opacity: 0; + transform: translateY(6px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.animate-card-in { + animation: fadeSlideIn 0.2s ease-out both; +} + +/* SVG connection line draw-on animation */ +@keyframes drawLine { + to { + stroke-dashoffset: 0; + } +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +/* Flash animation for scrollToPath */ +@keyframes flash { + 0%, + 100% { + background-color: transparent; + } + 25%, + 75% { + background-color: oklch(0.25 0.06 250); + } +} + +.animate-flash { + animation: flash 0.8s ease-out; +} + +/* Subtle glow pulse for active kontext.yaml in Files tab */ +@keyframes kontextGlow { + 0%, + 100% { + box-shadow: inset 0 0 0 1px oklch(0.55 0.15 250 / 0.3); + } + 50% { + box-shadow: inset 0 0 0 1px oklch(0.55 0.15 250 / 0.6); + } +} + +.animate-kontext-glow { + animation: kontextGlow 2s ease-in-out infinite; +} + +/* Shiki code highlight overrides */ +.shiki-container pre, +.shiki-container code { + background: transparent !important; + font-family: "Geist Mono Variable", "Geist Mono", monospace !important; + font-size: 11px !important; + line-height: 1.625 !important; +} + +/* Line numbers via CSS counter */ +.shiki-container code { + counter-reset: line; +} + +.shiki-container .line { + display: inline-block; + width: 100%; + padding-left: 3.5rem; + position: relative; +} + +.shiki-container .line::before { + counter-increment: line; + content: counter(line); + position: absolute; + left: 0; + width: 2.5rem; + text-align: right; + padding-right: 0.75rem; + color: oklch(0.35 0.005 260); + user-select: none; +} + +::-webkit-scrollbar { + width: 6px; + height: 6px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: oklch(0.25 0.005 260); + border-radius: 3px; +} +::-webkit-scrollbar-thumb:hover { + background: oklch(0.35 0.005 260); +} diff --git a/ecosystem/kontext/dashboard/src/main.tsx b/ecosystem/kontext/dashboard/src/main.tsx new file mode 100644 index 000000000..2f44a6535 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import { App } from "./App.js"; +import "./index.css"; + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/ecosystem/kontext/dashboard/src/types.ts b/ecosystem/kontext/dashboard/src/types.ts new file mode 100644 index 000000000..fa0547fe6 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/types.ts @@ -0,0 +1,42 @@ +export interface GraphNode { + id: string; + packageDir: string; + exists: boolean; +} + +export interface GraphEdge { + source: string; + target: string; + reason?: string; + generated: boolean; + command?: string; + optional: boolean; + definedBy: string; +} + +export interface KontextGraph { + nodes: GraphNode[]; + edges: GraphEdge[]; + packages: string[]; + builtAt: string; +} + +export interface FileEntry { + name: string; + type: "file" | "directory"; + path: string; +} + +export interface KontextConfig { + content: string; + exists: boolean; +} + +/** Search result combining forward and reverse lookups */ +export interface SearchResult { + file: string; + direction: "affects" | "affected-by"; + relatedFile: string; + reason?: string; + packageDir: string; +} diff --git a/ecosystem/kontext/dashboard/src/views/KontextView.tsx b/ecosystem/kontext/dashboard/src/views/KontextView.tsx new file mode 100644 index 000000000..6b41f2eb5 --- /dev/null +++ b/ecosystem/kontext/dashboard/src/views/KontextView.tsx @@ -0,0 +1,973 @@ +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { + Plus, + Save, + Trash2, + GripVertical, + Undo2, + Redo2, + Sparkles, + FilePlus, + Check, + X, + Minus, +} from "lucide-react"; +import { toast } from "sonner"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Separator } from "@/components/ui/separator"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; +import { FileTree } from "@/components/FileTree"; +import { CodePreview } from "@/components/CodePreview"; +import { ResizeHandle } from "@/components/ResizeHandle"; +import { useFileTree } from "@/hooks/useFileTree"; +import { useConfig } from "@/hooks/useConfig"; +import { useHighlightedPaths } from "@/hooks/useHighlightedPaths"; +import { useFileContent } from "@/hooks/useFileContent"; +import { extToLang } from "@/hooks/useShiki"; +import { getPackageDotColor, getPackageLabel } from "@/lib/packages"; +import { cn } from "@/lib/utils"; +import type { KontextGraph } from "@/types"; + +interface KontextViewProps { + graph: KontextGraph; + onSaved: () => void; +} + +interface EditableRelation { + id: string; + when: string; + affects: EditableAffect[]; +} + +interface EditableAffect { + id: string; + path: string; + reason: string; + optional: boolean; + generated: boolean; + command: string; +} + +interface EditorSnapshot { + relations: EditableRelation[]; + ignorePatterns: string; +} + +// --- Undo/Redo history hook --- +function useUndoRedo(maxSize = 50) { + const undoStack = useRef([]); + const redoStack = useRef([]); + const [canUndo, setCanUndo] = useState(false); + const [canRedo, setCanRedo] = useState(false); + + const syncState = useCallback(() => { + setCanUndo(undoStack.current.length > 0); + setCanRedo(redoStack.current.length > 0); + }, []); + + const push = useCallback( + (snapshot: EditorSnapshot) => { + undoStack.current.push(JSON.parse(JSON.stringify(snapshot))); + if (undoStack.current.length > maxSize) undoStack.current.shift(); + redoStack.current = []; // new edit clears redo + syncState(); + }, + [maxSize, syncState], + ); + + const undo = useCallback( + (current: EditorSnapshot): EditorSnapshot | null => { + const prev = undoStack.current.pop() ?? null; + if (prev) { + redoStack.current.push(JSON.parse(JSON.stringify(current))); + } + syncState(); + return prev ? (JSON.parse(JSON.stringify(prev)) as EditorSnapshot) : null; + }, + [syncState], + ); + + const redo = useCallback( + (current: EditorSnapshot): EditorSnapshot | null => { + const next = redoStack.current.pop() ?? null; + if (next) { + undoStack.current.push(JSON.parse(JSON.stringify(current))); + } + syncState(); + return next ? (JSON.parse(JSON.stringify(next)) as EditorSnapshot) : null; + }, + [syncState], + ); + + const clear = useCallback(() => { + undoStack.current = []; + redoStack.current = []; + syncState(); + }, [syncState]); + + return { push, undo, redo, clear, canUndo, canRedo }; +} + +export function KontextView({ graph, onSaved }: KontextViewProps) { + const fileTree = useFileTree(); + const { config, saving, loadConfig, saveConfig } = useConfig(); + const history = useUndoRedo(); + + const [selectedPackage, setSelectedPackage] = useState(null); + const [relations, setRelations] = useState([]); + const [ignorePatterns, setIgnorePatterns] = useState(""); + const [scrollToPath, setScrollToPath] = useState(null); + const [previewTab, setPreviewTab] = useState<"yaml" | "file">("yaml"); + const [selectedFilePath, setSelectedFilePath] = useState(null); + + // Dependency highlight map for the file tree + const highlightedPaths = useHighlightedPaths(relations, graph, selectedPackage); + + // File content for preview + const fileContent = useFileContent(); + const [yamlPanelWidth, setYamlPanelWidth] = useState(660); + const [filePanelWidth, setFilePanelWidth] = useState(280); + const [showNewPkgInput, setShowNewPkgInput] = useState(false); + const [newPkgPath, setNewPkgPath] = useState(""); + + // Packages with kontext.yaml (from graph edges) + const kontextFiles = useMemo(() => { + const files = new Set(); + for (const edge of graph.edges) { + files.add(edge.definedBy); + } + return Array.from(files).sort(); + }, [graph]); + + const packages = useMemo(() => { + const pkgs = new Set(graph.packages); + return Array.from(pkgs).sort(); + }, [graph]); + + // Load config when package is selected + useEffect(() => { + if (selectedPackage) { + loadConfig(selectedPackage); + history.clear(); + } + // eslint-disable-next-line -- history.clear is stable but the object ref changes with canUndo/canRedo + }, [selectedPackage, loadConfig]); + + // Parse YAML into editable state + useEffect(() => { + if (!config) return; + if (!config.exists || !config.content) { + setRelations([]); + setIgnorePatterns(""); + return; + } + try { + const parsed = parseYamlToRelations(config.content); + setRelations(parsed.relations); + setIgnorePatterns(parsed.ignore); + } catch { + setRelations([]); + setIgnorePatterns(""); + } + }, [config]); + + // Keep refs to current state for undo/redo keyboard handler + const relationsRef = useRef(relations); + const ignorePatternsRef = useRef(ignorePatterns); + relationsRef.current = relations; + ignorePatternsRef.current = ignorePatterns; + + // Cmd+Z undo / Cmd+Shift+Z redo — stable listener, no re-registration on state change + useEffect(() => { + function onKeyDown(e: KeyboardEvent) { + if ((e.metaKey || e.ctrlKey) && e.key === "z") { + e.preventDefault(); + const current = { + relations: relationsRef.current, + ignorePatterns: ignorePatternsRef.current, + }; + if (e.shiftKey) { + const next = history.redo(current); + if (next) { + setRelations(next.relations); + setIgnorePatterns(next.ignorePatterns); + toast.info("Redo"); + } + } else { + const prev = history.undo(current); + if (prev) { + setRelations(prev.relations); + setIgnorePatterns(prev.ignorePatterns); + toast.info("Undo"); + } + } + } + } + document.addEventListener("keydown", onKeyDown); + return () => document.removeEventListener("keydown", onKeyDown); + // eslint-disable-next-line -- refs are used for current values; history methods are stable + }, []); + + // Snapshot current state for undo — call before structural changes + function pushUndo() { + history.push({ relations, ignorePatterns }); + } + + // Save undo snapshot on blur (for text edits) — avoids per-keystroke deep copies + function onFieldBlur() { + // We push AFTER blur so the current text is captured as the "before" for the next edit + history.push({ relations, ignorePatterns }); + } + + // Generate YAML preview + const yamlPreview = useMemo( + () => generateYaml(relations, ignorePatterns), + [relations, ignorePatterns], + ); + + function addRelation() { + pushUndo(); + setRelations((prev) => [...prev, { id: crypto.randomUUID(), when: "", affects: [] }]); + } + + function removeRelation(id: string) { + pushUndo(); + setRelations((prev) => prev.filter((r) => r.id !== id)); + } + + // Text input updates — no pushUndo per keystroke + function updateRelationWhen(id: string, when: string) { + setRelations((prev) => prev.map((r) => (r.id === id ? { ...r, when } : r))); + } + + function addAffect(relationId: string, path: string) { + pushUndo(); + setRelations((prev) => + prev.map((r) => + r.id === relationId + ? { + ...r, + affects: [ + ...r.affects, + { + id: crypto.randomUUID(), + path, + reason: "", + optional: false, + generated: false, + command: "", + }, + ], + } + : r, + ), + ); + } + + function removeAffect(relationId: string, affectId: string) { + pushUndo(); + setRelations((prev) => + prev.map((r) => + r.id === relationId ? { ...r, affects: r.affects.filter((a) => a.id !== affectId) } : r, + ), + ); + } + + // Text input updates — no pushUndo per keystroke + function updateAffect(relationId: string, affectId: string, updates: Partial) { + setRelations((prev) => + prev.map((r) => + r.id === relationId + ? { + ...r, + affects: r.affects.map((a) => (a.id === affectId ? { ...a, ...updates } : a)), + } + : r, + ), + ); + } + + // Toggle-type updates do get undo + function toggleAffectOptional(relationId: string, affectId: string) { + pushUndo(); + setRelations((prev) => + prev.map((r) => + r.id === relationId + ? { + ...r, + affects: r.affects.map((a) => + a.id === affectId ? { ...a, optional: !a.optional } : a, + ), + } + : r, + ), + ); + } + + function updateIgnorePatterns(value: string) { + setIgnorePatterns(value); + } + + const handleDrop = useCallback( + (relationId: string) => (e: React.DragEvent) => { + e.preventDefault(); + const path = e.dataTransfer.getData("text/plain"); + if (path) addAffect(relationId, path); + }, + [], + ); + + async function handleSave() { + if (!selectedPackage) return; + const success = await saveConfig(selectedPackage, yamlPreview); + if (success) { + toast.success("Saved", { + description: `${selectedPackage}/kontext.yaml`, + }); + onSaved(); + } else { + toast.error("Save failed"); + } + } + + async function handleCreateNew() { + if (!newPkgPath.trim()) return; + const defaultYaml = 'apiVersion: "kontext/v1"\n\nrelations: []\n'; + const success = await saveConfig(newPkgPath.trim(), defaultYaml); + if (success) { + toast.success("Created", { + description: `${newPkgPath.trim()}/kontext.yaml`, + }); + setSelectedPackage(newPkgPath.trim()); + setShowNewPkgInput(false); + setNewPkgPath(""); + onSaved(); + } else { + toast.error("Failed to create kontext.yaml"); + } + } + + return ( +
+ {/* Left: KONTEXTS / FILES tabs (resizable) */} +
+ +
+ + + Kontexts + + + Files + + + + + + + Create new kontext.yaml + +
+ + {/* New kontext.yaml input */} + {showNewPkgInput && ( +
+ setNewPkgPath(e.target.value)} + placeholder="packages/my-pkg" + className="font-mono text-[11px] h-6 flex-1" + onKeyDown={(e) => { + if (e.key === "Enter") handleCreateNew(); + if (e.key === "Escape") setShowNewPkgInput(false); + }} + autoFocus + /> + +
+ )} + + {/* KONTEXTS tab */} + +
+ {kontextFiles.length === 0 ? ( +
+ No kontext.yaml found +
+ ) : ( + kontextFiles.map((file) => { + const pkgDir = file.replace("/kontext.yaml", ""); + return ( + + ); + }) + )} +
+
+ + {/* FILES tab */} + + setSelectedPackage(pkgDir)} + highlightedPaths={highlightedPaths} + scrollToPath={scrollToPath} + activeKontextDir={selectedPackage} + onFileSelect={(path) => { + if (path.includes(".")) { + setSelectedFilePath(path); + setPreviewTab("file"); + fileContent.loadFile(path); + } + }} + /> + +
+
+ + {/* Left resize handle */} + setFilePanelWidth((w) => Math.max(160, Math.min(400, w + delta)))} + /> + + {/* Center: Relation editor */} +
+ +
+ {/* Compact package selector + undo/save toolbar */} +
+ {packages.map((pkg) => ( + + ))} +
+ {(history.canUndo || history.canRedo) && ( +
+ + + + + Undo (Cmd+Z) + + + + + + Redo (Cmd+Shift+Z) + +
+ )} +
+ + {selectedPackage && ( + <> + {/* Selected package path */} +
+ {selectedPackage}/kontext.yaml +
+ + {/* Ignore patterns */} +
+ + updateIgnorePatterns(e.target.value)} + onBlur={onFieldBlur} + placeholder="*.test.tsx, *.namespace.tsx" + className="font-mono text-xs h-7" + /> +
+ + + + {/* Relations */} +
+
+

+ Relations +

+ +
+ + {relations.map((rel) => ( +
+ {/* When pattern */} +
+ + when + + updateRelationWhen(rel.id, e.target.value)} + onBlur={onFieldBlur} + placeholder="src/commands/**" + className="font-mono text-xs h-7 flex-1" + /> + +
+ + {/* Affects (drop zone) */} +
{ + e.preventDefault(); + e.currentTarget.classList.add("ring-1", "ring-primary/50"); + }} + onDragLeave={(e) => { + e.currentTarget.classList.remove("ring-1", "ring-primary/50"); + }} + onDrop={(e) => { + e.currentTarget.classList.remove("ring-1", "ring-primary/50"); + handleDrop(rel.id)(e); + }} + className="rounded-md border border-dashed border-border/50 p-2.5 min-h-[48px] transition-all" + > +
+ affects +
+ {rel.affects.length === 0 ? ( +

+ Drop files here or add manually +

+ ) : ( +
+ {rel.affects.map((affect) => { + const affectExists = affect.path + ? graph.nodes.some((n) => n.id === affect.path && n.exists) + : false; + return ( +
+
+ {/* Status badge with tooltip */} + + + + + + {affectExists + ? "File exists - click to find in tree" + : affect.optional + ? "Optional - may not need updating" + : affect.path + ? "File not found on disk" + : "Enter a file path"} + + + + updateAffect(rel.id, affect.id, { path: e.target.value }) + } + onBlur={onFieldBlur} + placeholder="path/to/file" + className="font-mono text-[11px] h-6 flex-1" + /> + + + + + Toggle optional + + +
+
+ + updateAffect(rel.id, affect.id, { reason: e.target.value }) + } + onBlur={onFieldBlur} + placeholder="reason..." + className="text-[10px] h-5 text-muted-foreground/70 border-0 bg-transparent px-0 shadow-none focus-visible:ring-0 placeholder:text-muted-foreground/25" + /> +
+
+ ); + })} +
+ )} + +
+
+ ))} +
+ + )} + + {!selectedPackage && ( +
+ Select a kontext file or package to edit +
+ )} +
+ +
+ + {/* Right resize handle */} + setYamlPanelWidth((w) => Math.max(280, Math.min(720, w + delta)))} + /> + + {/* Right: Preview panel (YAML / File tabs) */} +
+ setPreviewTab(v as "yaml" | "file")} + className="flex flex-col h-full" + > +
+ + + YAML + + + {selectedFilePath ? selectedFilePath.split("/").pop() : "File"} + + + + {previewTab === "yaml" && ( + + )} + {previewTab === "file" && selectedFilePath && ( + + {selectedFilePath} + + )} +
+ + + {selectedPackage ? ( + + ) : ( +
+ Select a package to edit +
+ )} +
+ + + {fileContent.loading ? ( +
+ Loading... +
+ ) : fileContent.error ? ( +
{fileContent.error}
+ ) : fileContent.fileContent ? ( + + ) : ( +
+ Click a file in the tree to preview +
+ )} +
+
+
+
+ ); +} + +// --- YAML parsing/generation helpers --- + +function parseYamlToRelations(content: string): { + relations: EditableRelation[]; + ignore: string; +} { + const lines = content.split("\n"); + const relations: EditableRelation[] = []; + let ignore = ""; + let currentRelation: EditableRelation | null = null; + let inRelations = false; + let inIgnore = false; + let inAffects = false; + + for (const line of lines) { + const trimmed = line.trim(); + + if (trimmed.startsWith("ignore:")) { + inIgnore = true; + inRelations = false; + continue; + } + if (trimmed.startsWith("relations:")) { + inRelations = true; + inIgnore = false; + continue; + } + + if (inIgnore && trimmed.startsWith("- ")) { + const pattern = trimmed.slice(2).replace(/^["']|["']$/g, ""); + ignore += (ignore ? ", " : "") + pattern; + continue; + } + + if (inRelations) { + if (trimmed.startsWith("- when:")) { + if (currentRelation) relations.push(currentRelation); + currentRelation = { + id: crypto.randomUUID(), + when: trimmed + .slice(7) + .trim() + .replace(/^["']|["']$/g, ""), + affects: [], + }; + inAffects = false; + continue; + } + if (trimmed === "affects:" && currentRelation) { + inAffects = true; + continue; + } + if (inAffects && trimmed.startsWith("- path:") && currentRelation) { + const path = trimmed + .slice(7) + .trim() + .replace(/^["']|["']$/g, ""); + currentRelation.affects.push({ + id: crypto.randomUUID(), + path, + reason: "", + optional: false, + generated: false, + command: "", + }); + continue; + } + if (inAffects && trimmed.startsWith("reason:") && currentRelation) { + const lastAffect = currentRelation.affects.at(-1); + if (lastAffect) { + lastAffect.reason = trimmed + .slice(7) + .trim() + .replace(/^["']|["']$/g, ""); + } + continue; + } + if (inAffects && trimmed.startsWith("optional:") && currentRelation) { + const lastAffect = currentRelation.affects.at(-1); + if (lastAffect) { + lastAffect.optional = trimmed.includes("true"); + } + continue; + } + } + } + if (currentRelation) relations.push(currentRelation); + + return { relations, ignore }; +} + +function generateYaml(relations: EditableRelation[], ignorePatterns: string): string { + const lines: string[] = ['apiVersion: "kontext/v1"', ""]; + + if (ignorePatterns.trim()) { + lines.push("ignore:"); + for (const pattern of ignorePatterns + .split(",") + .map((p) => p.trim()) + .filter(Boolean)) { + lines.push(` - "${pattern}"`); + } + lines.push(""); + } + + if (relations.length > 0) { + lines.push("relations:"); + for (const rel of relations) { + lines.push(` - when: "${rel.when}"`); + if (rel.affects.length > 0) { + lines.push(" affects:"); + for (const affect of rel.affects) { + lines.push(` - path: "${affect.path}"`); + if (affect.reason) { + lines.push(` reason: "${affect.reason}"`); + } + if (affect.optional) { + lines.push(" optional: true"); + } + if (affect.generated) { + lines.push(" generated: true"); + if (affect.command) { + lines.push(` command: "${affect.command}"`); + } + } + } + } + } + } + + return lines.join("\n") + "\n"; +} diff --git a/ecosystem/kontext/dashboard/tsconfig.json b/ecosystem/kontext/dashboard/tsconfig.json new file mode 100644 index 000000000..8ac6223f7 --- /dev/null +++ b/ecosystem/kontext/dashboard/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "jsx": "react-jsx", + "strict": true, + "skipLibCheck": true, + "rootDir": "src", + "outDir": "dist", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"] +} diff --git a/ecosystem/kontext/dashboard/vite.config.ts b/ecosystem/kontext/dashboard/vite.config.ts new file mode 100644 index 000000000..e710c940f --- /dev/null +++ b/ecosystem/kontext/dashboard/vite.config.ts @@ -0,0 +1,16 @@ +import { fileURLToPath, URL } from "node:url"; +import tailwindcss from "@tailwindcss/vite"; +import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; + +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + "@": fileURLToPath(new URL("./src", import.meta.url)), + }, + }, + build: { + outDir: "dist", + }, +}); diff --git a/package.json b/package.json index 455de5e16..74f77d397 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "ecosystem/*", "ecosystem/rootage/*", "ecosystem/qvism/*", + "ecosystem/kontext/*", "examples/*", "docs" ], diff --git a/packages/cli/kontext.yaml b/packages/cli/kontext.yaml new file mode 100644 index 000000000..607e63ffb --- /dev/null +++ b/packages/cli/kontext.yaml @@ -0,0 +1,42 @@ +# yaml-language-server: $schema=../../ecosystem/kontext/core/kontext.schema.json +apiVersion: kontext/v1 + +relations: + - when: "src/commands/**" + affects: + - path: docs/content/react/getting-started/cli/commands.mdx + reason: CLI 명령어 추가/변경 시 문서 업데이트 + - path: docs/content/react/getting-started/cli/configuration.mdx + reason: 명령어의 config 옵션 변경 시 설정 문서 업데이트 + optional: true + - path: skills/seed-design-cli/references/usage.md + reason: CLI 사용법 변경 시 스킬 문서 업데이트 + overrides: + - match: "src/commands/upgrade.ts" + affects: + - path: docs/content/react/getting-started/cli/commands.mdx + reason: upgrade 명령 변경 시 문서 업데이트 + - path: skills/seed-design-cli/references/upgrade.md + reason: upgrade 스킬 문서 업데이트 + - match: "src/commands/compat.ts" + affects: + - path: docs/content/react/getting-started/cli/commands.mdx + reason: compat 명령 변경 시 문서 업데이트 + - path: skills/seed-design-cli/references/migration.md + reason: 호환성/마이그레이션 스킬 문서 업데이트 + + - when: "src/utils/**" + affects: + - path: docs/content/react/getting-started/cli/commands.mdx + reason: CLI 내부 유틸 변경이 명령 동작에 영향줄 수 있음 + optional: true + - path: skills/seed-design-cli/references/usage.md + reason: 유틸 변경이 사용법에 영향줄 수 있음 + optional: true + + - when: "src/schema.ts" + affects: + - path: docs/registry/schema.ts + reason: CLI schema와 registry schema는 동기화 필요 + - path: docs/content/react/getting-started/cli/configuration.mdx + reason: config 스키마 변경 시 설정 문서 업데이트 diff --git a/skills/kontext/SKILL.md b/skills/kontext/SKILL.md new file mode 100644 index 000000000..f92ddf67b --- /dev/null +++ b/skills/kontext/SKILL.md @@ -0,0 +1,42 @@ +--- +name: Kontext +description: 모노레포 의존성 그래프를 탐색하고 변경 영향 범위를 파악하는 도구 사용 가이드 +--- + +# Kontext + +모노레포 의존성 그래프를 탐색하고, 변경 영향 범위를 파악한다. + +## 사용 시점 + +- 파일을 수정하기 전: 어떤 파일이 영향받는지 미리 확인 +- 작업 완료 후: 빠뜨린 파일이 없는지 검증 +- 주기적으로: `kontext lint`로 미선언 관계 발견 + +## 명령어 + +```bash +# 특정 파일의 영향 범위 조회 +bun ecosystem/kontext/cli/bin/kontext.mjs deps <파일경로> +bun ecosystem/kontext/cli/bin/kontext.mjs deps <파일경로> --json + +# 전체 완전성 검증 +bun ecosystem/kontext/cli/bin/kontext.mjs check + +# git 이력에서 미선언 관계 발견 +bun ecosystem/kontext/cli/bin/kontext.mjs lint + +# 그래프 재구축 +bun ecosystem/kontext/cli/bin/kontext.mjs build + +# 대시보드 +bun ecosystem/kontext/cli/bin/kontext.mjs serve +``` + +## kontext.yaml 수정 + +각 패키지의 `kontext.yaml`에 관계를 추가/수정할 수 있다. 스키마는 `references/yaml-schema.md` 참고. + +## 참조 파일 + +- `references/yaml-schema.md` diff --git a/skills/kontext/references/yaml-schema.md b/skills/kontext/references/yaml-schema.md new file mode 100644 index 000000000..a6c1ef676 --- /dev/null +++ b/skills/kontext/references/yaml-schema.md @@ -0,0 +1,37 @@ +# kontext.yaml Schema + +## 구조 + +```yaml +apiVersion: kontext/v1 + +relations: + - when: "glob 패턴" # 이 패키지 내 감시 대상 + affects: + - path: "레포 루트 기준" # 영향받는 파일 + reason: "왜" # 선택 + generated: true # 선택. 자동 생성 파일 + command: "명령어" # 선택. generated일 때 실행할 명령 +``` + +## 템플릿 변수 + +- `{id}` → kebab-case (e.g., `action-button`) +- `{Id}` → PascalCase (e.g., `ActionButton`) + +`when` 패턴에서 매칭된 파일명에서 자동 추출됨. + +## 예시 + +```yaml +apiVersion: kontext/v1 + +relations: + - when: "components/*.yaml" + affects: + - path: packages/css/vars/component/ + generated: true + command: bun rootage:generate + - path: packages/react/src/components/{Id}/ + reason: API 변경 시 컴포넌트 업데이트 +```