diff --git a/apps/desktop/README.md b/apps/desktop/README.md index 8b1b8b5..41e188a 100644 --- a/apps/desktop/README.md +++ b/apps/desktop/README.md @@ -2,11 +2,34 @@ DeepCode Mac 客户端(Electron + React + Tailwind + xterm + monaco)。 -## 当前状态 +## 当前状态 — M6 skeleton -M0 骨架 — 仅占位结构。实际实现: +骨架已落地(type-check 通过): -- **M6**:onboarding / chat / sessions / settings / MCPManager / plugins 管理 / skills 管理 / xterm + monaco 嵌入 / **自动更新(electron-updater + GitHub Releases)** -- **M7**:右侧文件面板 / 完整 composer / approval UI / 收起态 inspector / rewind UX +- `electron/main.ts` — BrowserWindow + IPC 处理(version / creds / settings)+ + electron-updater 钩子(懒加载,没装也不崩) +- `electron/preload.ts` — `contextBridge` 暴露 `window.deepcode` 给 renderer +- `src/main.tsx` + `src/App.tsx` — React 入口 + Onboarding gate + 更新 banner +- `src/screens/Onboarding.tsx` — 首次运行的 API key 收集表单 +- `src/screens/Repl.tsx` — 对话占位 UI +- `src/components/UpdateBanner.tsx` — "Relaunch to update vX.Y.Z" 提示 +- `tsconfig.electron.json` — 等装了 `electron` 之后用这个编译 main/preload + +## 还没做(M6-rest,多个 PR) + +- 装 `electron` / `electron-builder` / `vite` / `tailwindcss` 实际依赖(约 250 MB node_modules) +- Vite dev server + HMR +- Tailwind PostCSS 流水线 +- xterm.js 终端嵌入 +- Monaco 编辑器嵌入 +- electron-builder universal dmg 打包 +- Apple Developer ID + codesign + notarize +- 11 个屏幕剩余 9 个(Chat / Sessions / Settings / MCPManager / FilePanel 等) +- Renderer ↔ main process 的 agent loop 流式桥 + +## 为什么 skeleton 故意留小 + +Electron 二进制装下来 ~250 MB,CI 装包会变慢。把这个负担留给真正开始 +做 M6-rest 的 PR,这样到 M6-skeleton 为止的 monorepo 仍然轻。 详见 `docs/DEVELOPMENT_PLAN.md` §4 + §4a + §4b。 diff --git a/apps/desktop/electron/main.ts b/apps/desktop/electron/main.ts index 349ca0b..39f1292 100644 --- a/apps/desktop/electron/main.ts +++ b/apps/desktop/electron/main.ts @@ -1,6 +1,124 @@ // Electron main process entry. -// Milestone: M6 // Spec: docs/DEVELOPMENT_PLAN.md §4 + §4b (auto-update) -// Status: placeholder +// Milestone: M6 — skeleton: window creation, IPC bridge, electron-updater stub +// +// This file is intentionally minimal for the skeleton PR. It wires: +// · Single BrowserWindow with the renderer's HTML +// · IPC channels for credentials / settings / agent control +// · electron-updater hook (lazy-loaded; gracefully no-ops if pkg not present) +// +// Full feature wiring (terminal, file panel, 11 screens) lands in subsequent +// M6-rest PRs. -export {}; +import { app, BrowserWindow, ipcMain } from 'electron'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; +import { homedir } from 'node:os'; +import { + CredentialsStore, + loadSettings, + resolveCredentials, + VERSION, +} from '@deepcode/core'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const isDev = !app.isPackaged; +let mainWindow: BrowserWindow | null = null; + +async function createWindow(): Promise { + mainWindow = new BrowserWindow({ + width: 1280, + height: 800, + minWidth: 900, + minHeight: 600, + backgroundColor: '#0e0e10', + titleBarStyle: 'hiddenInset', + webPreferences: { + preload: join(__dirname, 'preload.cjs'), + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + }, + }); + + if (isDev) { + // Vite dev server (configured to run on 5173 — see scripts/run-dev.sh) + await mainWindow.loadURL('http://localhost:5173'); + mainWindow.webContents.openDevTools({ mode: 'detach' }); + } else { + await mainWindow.loadFile(join(__dirname, '..', 'dist', 'index.html')); + } + + mainWindow.on('closed', () => { + mainWindow = null; + }); +} + +// ────────────────────────────────────────────────────────────────────────── +// IPC handlers — renderer asks main for things it can't do (fs, creds, etc.) +// ────────────────────────────────────────────────────────────────────────── + +ipcMain.handle('app:version', () => VERSION); + +ipcMain.handle('creds:load', async () => { + const store = new CredentialsStore(); + const creds = await resolveCredentials({ store }); + return { + hasKey: !!(creds.apiKey || creds.authToken), + baseURL: creds.baseURL, + }; +}); + +ipcMain.handle('creds:save', async (_event, args: { apiKey: string; baseURL?: string }) => { + const store = new CredentialsStore(); + await store.save({ apiKey: args.apiKey, baseURL: args.baseURL }); + return true; +}); + +ipcMain.handle('settings:load', async () => { + const { merged } = await loadSettings({ cwd: process.cwd(), home: homedir() }); + return merged; +}); + +// ────────────────────────────────────────────────────────────────────────── +// electron-updater — lazy import so the skeleton works without the dep +// ────────────────────────────────────────────────────────────────────────── + +async function setupAutoUpdater(): Promise { + if (isDev) return; + try { + const mod = await import('electron-updater').catch(() => null); + if (!mod) return; + const { autoUpdater } = mod; + autoUpdater.checkForUpdatesAndNotify().catch(() => { + /* silent: no releases yet / offline */ + }); + autoUpdater.on('update-downloaded', (info) => { + mainWindow?.webContents.send('updater:update-downloaded', { + version: info.version, + releaseNotes: info.releaseNotes, + }); + }); + } catch { + /* electron-updater not installed yet — fine for skeleton */ + } +} + +// ────────────────────────────────────────────────────────────────────────── +// App lifecycle +// ────────────────────────────────────────────────────────────────────────── + +app.whenReady().then(async () => { + await createWindow(); + await setupAutoUpdater(); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) void createWindow(); + }); +}); + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit(); +}); diff --git a/apps/desktop/electron/preload.ts b/apps/desktop/electron/preload.ts index f9595c0..c985ccd 100644 --- a/apps/desktop/electron/preload.ts +++ b/apps/desktop/electron/preload.ts @@ -1,6 +1,31 @@ -// Electron preload script — bridges renderer to @deepcode/core via contextBridge. -// Milestone: M6 +// Electron preload — bridges renderer to the trusted main process via +// contextBridge. The renderer can ONLY call these exposed APIs; raw `require` +// and Node globals are disabled. // Spec: docs/DEVELOPMENT_PLAN.md §4 -// Status: placeholder +// Milestone: M6 + +import { contextBridge, ipcRenderer } from 'electron'; + +const api = { + version: (): Promise => ipcRenderer.invoke('app:version'), + creds: { + load: (): Promise<{ hasKey: boolean; baseURL?: string }> => + ipcRenderer.invoke('creds:load'), + save: (args: { apiKey: string; baseURL?: string }): Promise => + ipcRenderer.invoke('creds:save', args), + }, + settings: { + load: (): Promise> => ipcRenderer.invoke('settings:load'), + }, + /** Subscribe to "update downloaded" events from the auto-updater. */ + onUpdateDownloaded: (cb: (info: { version: string; releaseNotes?: string }) => void): (() => void) => { + const listener = (_e: unknown, info: { version: string; releaseNotes?: string }) => cb(info); + ipcRenderer.on('updater:update-downloaded', listener); + return () => ipcRenderer.removeListener('updater:update-downloaded', listener); + }, +}; + +contextBridge.exposeInMainWorld('deepcode', api); -export {}; +// Type declaration for the renderer (mirrored manually in src/types/global.d.ts) +export type DeepCodeRendererAPI = typeof api; diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5bebc4b..448e1e2 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -5,12 +5,14 @@ "description": "DeepCode Mac desktop client (Electron + React)", "license": "MIT", "type": "module", + "main": "dist-electron/main.cjs", "scripts": { "build": "tsc -p tsconfig.json", "typecheck": "tsc -p tsconfig.json --noEmit", - "test": "echo 'desktop tests in M6' && exit 0", + "test": "vitest run --passWithNoTests", "lint": "echo 'lint: configured in M1' && exit 0", - "clean": "rm -rf dist dist-electron release *.tsbuildinfo" + "clean": "rm -rf dist dist-electron release *.tsbuildinfo", + "// notes": "M6-rest will add: dev (vite + concurrent electron), pack (electron-builder), dist (dmg)" }, "dependencies": { "@deepcode/core": "workspace:*", @@ -18,7 +20,12 @@ }, "devDependencies": { "@types/node": "^22.10.0", - "typescript": "^5.7.0" + "@types/react": "^18.3.0", + "@types/react-dom": "^18.3.0", + "react": "^18.3.0", + "react-dom": "^18.3.0", + "typescript": "^5.7.0", + "vitest": "^2.1.9" }, "engines": { "node": ">=22" diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index ae7b162..5250e15 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,5 +1,47 @@ // Top-level React component for desktop client. -// Milestone: M6 -// Status: placeholder +// Spec: docs/VISUAL_DESIGN.html +// Milestone: M6 skeleton — onboarding + REPL placeholder + update banner -export {}; +import { useEffect, useState } from 'react'; +import { OnboardingScreen } from './screens/Onboarding.js'; +import { ReplScreen } from './screens/Repl.js'; +import { UpdateBanner } from './components/UpdateBanner.js'; +import type { UpdateInfo } from './types/global.js'; + +export function App(): JSX.Element { + const [version, setVersion] = useState(''); + const [hasKey, setHasKey] = useState(null); + const [update, setUpdate] = useState(null); + + useEffect(() => { + void window.deepcode.version().then(setVersion); + void window.deepcode.creds.load().then((c) => setHasKey(c.hasKey)); + const off = window.deepcode.onUpdateDownloaded((info) => setUpdate(info)); + return () => off(); + }, []); + + if (hasKey === null) { + return ( +
+ Loading… +
+ ); + } + + return ( +
+ {update && } +
+ DeepCode + v{version} +
+
+ {!hasKey ? ( + setHasKey(true)} /> + ) : ( + + )} +
+
+ ); +} diff --git a/apps/desktop/src/components/UpdateBanner.tsx b/apps/desktop/src/components/UpdateBanner.tsx new file mode 100644 index 0000000..bf6612c --- /dev/null +++ b/apps/desktop/src/components/UpdateBanner.tsx @@ -0,0 +1,41 @@ +// "Relaunch to update vX.Y.Z" banner — fires when electron-updater has +// downloaded a new release. Click → app.relaunch() (host wires this in M6-rest). +// Spec: docs/VISUAL_DESIGN.html screen #11 +// Milestone: M6 skeleton + +import { useState } from 'react'; +import type { UpdateInfo } from '../types/global.js'; + +interface BannerProps { + info: UpdateInfo; +} + +export function UpdateBanner({ info }: BannerProps): JSX.Element | null { + const [dismissed, setDismissed] = useState(false); + if (dismissed) return null; + return ( +
+ + DeepCode v{info.version} is ready to install. Relaunch to update. + +
+ + +
+
+ ); +} diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index f68ad5a..eeffd3a 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -1 +1,97 @@ -/* Tailwind / global styles entry — M6 */ +/* Tailwind / global styles entry — M6 skeleton. + * Real Tailwind setup (postcss + vite-plugin) lands in M6-rest. The skeleton + * relies on CSS variables + plain styles so the React tree renders without + * a build pipeline beyond `tsc`. + */ + +:root { + color-scheme: dark; + --bg: #0e0e10; + --bg-elevated: #18181b; + --fg: #f4f4f5; + --muted: #71717a; + --accent: #a3e635; + --error: #f87171; + --border: #27272a; +} + +* { + box-sizing: border-box; +} + +body, html, #root { + margin: 0; + padding: 0; + height: 100%; + background: var(--bg); + color: var(--fg); + font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, system-ui, sans-serif; + font-size: 14px; +} + +/* Minimum class-equivalents so the skeleton's Tailwind-flavored markup is + * legible without postcss. Real Tailwind replaces this block in M6-rest. */ +.bg-bg { background: var(--bg); } +.bg-bg-elevated { background: var(--bg-elevated); } +.text-fg { color: var(--fg); } +.text-muted { color: var(--muted); } +.bg-accent { background: var(--accent); } +.text-accent { color: var(--accent); } +.bg-error\/10 { background: rgba(248, 113, 113, 0.1); } +.text-error { color: var(--error); } +.border-border { border-color: var(--border); } +.bg-accent\/10 { background: rgba(163, 230, 53, 0.1); } +.bg-accent\/20 { background: rgba(163, 230, 53, 0.2); } + +.flex { display: flex; } +.flex-col { flex-direction: column; } +.flex-1 { flex: 1; } +.gap-2 { gap: 0.5rem; } +.h-full { height: 100%; } +.h-screen { height: 100vh; } +.items-center { align-items: center; } +.justify-center { justify-content: center; } +.justify-between { justify-content: space-between; } +.overflow-hidden { overflow: hidden; } +.overflow-y-auto { overflow-y: auto; } +.whitespace-pre-wrap { white-space: pre-wrap; } +.space-y-3 > * + * { margin-top: 0.75rem; } +.space-y-4 > * + * { margin-top: 1rem; } +.p-3 { padding: 0.75rem; } +.p-4 { padding: 1rem; } +.p-6 { padding: 1.5rem; } +.p-8 { padding: 2rem; } +.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; } +.px-4 { padding-left: 1rem; padding-right: 1rem; } +.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; } +.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } +.mt-1 { margin-top: 0.25rem; } +.ml-12 { margin-left: 3rem; } +.mr-12 { margin-right: 3rem; } +.mx-12 { margin-left: 3rem; margin-right: 3rem; } +.mb-1 { margin-bottom: 0.25rem; } +.text-xs { font-size: 0.75rem; } +.text-sm { font-size: 0.875rem; } +.text-xl { font-size: 1.25rem; } +.font-medium { font-weight: 500; } +.font-semibold { font-weight: 600; } +.rounded { border-radius: 0.25rem; } +.rounded-lg { border-radius: 0.5rem; } +.border { border-width: 1px; border-style: solid; } +.border-t { border-top-width: 1px; border-top-style: solid; } +.border-b { border-bottom-width: 1px; border-bottom-style: solid; } +.w-full { width: 100%; } +.max-w-md { max-width: 28rem; } +.outline-none { outline: none; } + +input, button { + font: inherit; + color: inherit; +} +input:focus { border-color: var(--accent); } +button:disabled { opacity: 0.5; cursor: not-allowed; } + +code { + font-family: ui-monospace, SFMono-Regular, monospace; + font-size: 0.95em; +} diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx index 88e9170..2eb5721 100644 --- a/apps/desktop/src/main.tsx +++ b/apps/desktop/src/main.tsx @@ -1,6 +1,16 @@ -// React renderer entry. -// Milestone: M6 +// React renderer entry — mounts into #root. // Spec: docs/VISUAL_DESIGN.html screens #1-#11 -// Status: placeholder +// Milestone: M6 skeleton -export {}; +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App.js'; +import './index.css'; + +const rootEl = document.getElementById('root'); +if (!rootEl) throw new Error('No #root element found'); +createRoot(rootEl).render( + + + , +); diff --git a/apps/desktop/src/screens/Onboarding.tsx b/apps/desktop/src/screens/Onboarding.tsx index 76a154f..1c01a91 100644 --- a/apps/desktop/src/screens/Onboarding.tsx +++ b/apps/desktop/src/screens/Onboarding.tsx @@ -1,6 +1,87 @@ -// Screen: Onboarding -// Milestone: M6 -// Spec: docs/VISUAL_DESIGN.html -// Status: placeholder +// Onboarding screen — first-run flow capturing the DeepSeek API key. +// Spec: docs/VISUAL_DESIGN.html screen #1 +// Milestone: M6 skeleton -export {}; +import { useState } from 'react'; + +interface OnboardingProps { + onComplete: () => void; +} + +export function OnboardingScreen({ onComplete }: OnboardingProps): JSX.Element { + const [apiKey, setApiKey] = useState(''); + const [baseURL, setBaseURL] = useState(''); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(null); + + async function handleSubmit(e: React.FormEvent): Promise { + e.preventDefault(); + if (!apiKey.trim()) { + setError('API key is required.'); + return; + } + setSubmitting(true); + setError(null); + try { + await window.deepcode.creds.save({ + apiKey: apiKey.trim(), + baseURL: baseURL.trim() || undefined, + }); + onComplete(); + } catch (err) { + setError((err as Error).message ?? 'Failed to save credentials.'); + } finally { + setSubmitting(false); + } + } + + return ( +
+
+
+

Welcome to DeepCode

+

+ DeepCode talks to the DeepSeek API. Paste your key to get started. +

+
+ + + {error && ( +
{error}
+ )} + +

+ Your key is stored at ~/.deepcode/credentials.json (chmod 600). +

+
+
+ ); +} diff --git a/apps/desktop/src/screens/Repl.tsx b/apps/desktop/src/screens/Repl.tsx new file mode 100644 index 0000000..60a505e --- /dev/null +++ b/apps/desktop/src/screens/Repl.tsx @@ -0,0 +1,88 @@ +// REPL screen — minimal chat surface for the skeleton. +// Spec: docs/VISUAL_DESIGN.html screen #2 +// Milestone: M6 skeleton — wires onSubmit; the full agent loop integration +// (streaming + tools + permissions) lives in subsequent M6-rest PRs. + +import { useEffect, useRef, useState } from 'react'; + +interface Message { + role: 'user' | 'assistant' | 'system'; + text: string; +} + +export function ReplScreen(): JSX.Element { + const [messages, setMessages] = useState([ + { + role: 'system', + text: + "DeepCode is ready. The Mac client's agent loop is wired in M6-rest — this " + + "skeleton renders chat history and the input box. For real conversations " + + "today, use the CLI: `deepcode`.", + }, + ]); + const [input, setInput] = useState(''); + const listRef = useRef(null); + + useEffect(() => { + listRef.current?.scrollTo({ top: listRef.current.scrollHeight, behavior: 'smooth' }); + }, [messages]); + + function handleSubmit(e: React.FormEvent): void { + e.preventDefault(); + const text = input.trim(); + if (!text) return; + setMessages((m) => [ + ...m, + { role: 'user', text }, + { + role: 'assistant', + text: '(M6 skeleton — agent loop not yet wired. See repl.ts in apps/cli for live convos.)', + }, + ]); + setInput(''); + } + + return ( +
+
+ {messages.map((m, i) => ( +
+
{m.role}
+
{m.text}
+
+ ))} +
+
+
+ setInput(e.target.value)} + placeholder="Ask DeepCode..." + className="flex-1 rounded border border-border bg-bg px-3 py-2 text-fg outline-none focus:border-accent" + /> + +
+
+
+ ); +} diff --git a/apps/desktop/src/types/global.d.ts b/apps/desktop/src/types/global.d.ts new file mode 100644 index 0000000..74d8eca --- /dev/null +++ b/apps/desktop/src/types/global.d.ts @@ -0,0 +1,25 @@ +// Window-attached API exposed by electron/preload.ts via contextBridge. + +export interface UpdateInfo { + version: string; + releaseNotes?: string; +} + +export interface DeepCodeAPI { + version: () => Promise; + creds: { + load: () => Promise<{ hasKey: boolean; baseURL?: string }>; + save: (args: { apiKey: string; baseURL?: string }) => Promise; + }; + settings: { + load: () => Promise>; + }; + onUpdateDownloaded: (cb: (info: UpdateInfo) => void) => () => void; +} + +declare global { + interface Window { + deepcode: DeepCodeAPI; + } +} +export {}; diff --git a/apps/desktop/tsconfig.electron.json b/apps/desktop/tsconfig.electron.json new file mode 100644 index 0000000..66dc788 --- /dev/null +++ b/apps/desktop/tsconfig.electron.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist-electron", + "rootDir": "./electron", + "composite": false, + "lib": ["ES2022"], + "module": "CommonJS", + "moduleResolution": "node", + "types": ["node"], + "skipLibCheck": true, + "isolatedModules": false, + "noEmit": false + }, + "include": ["electron/**/*"], + "exclude": ["node_modules", "dist", "dist-electron", "release", "src/**/*"] +} diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json index 78c1d2d..7818152 100644 --- a/apps/desktop/tsconfig.json +++ b/apps/desktop/tsconfig.json @@ -2,16 +2,17 @@ "extends": "../../tsconfig.base.json", "compilerOptions": { "outDir": "./dist", - "rootDir": "./", + "rootDir": "./src", "composite": true, "tsBuildInfoFile": "./dist/.tsbuildinfo", "lib": ["ES2022", "DOM"], "types": ["node"], - "jsx": "preserve", + "jsx": "react-jsx", "allowJs": false, - "isolatedModules": true + "isolatedModules": true, + "noEmit": true }, - "include": ["electron/**/*", "src/**/*"], - "exclude": ["node_modules", "dist", "dist-electron", "release"], + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "dist-electron", "release", "electron/**/*"], "references": [{ "path": "../../packages/core" }, { "path": "../../packages/shared-ui" }] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7009795..876a566 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -52,9 +52,24 @@ importers: '@types/node': specifier: ^22.10.0 version: 22.19.19 + '@types/react': + specifier: ^18.3.0 + version: 18.3.29 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.7(@types/react@18.3.29) + react: + specifier: ^18.3.0 + version: 18.3.1 + react-dom: + specifier: ^18.3.0 + version: 18.3.1(react@18.3.1) typescript: specifier: ^5.7.0 version: 5.9.3 + vitest: + specifier: ^2.1.9 + version: 2.1.9(@types/node@22.19.19) packages/core: dependencies: @@ -374,6 +389,17 @@ packages: '@types/node@22.19.19': resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.29': + resolution: {integrity: sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==} + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -478,6 +504,9 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -636,12 +665,19 @@ packages: jose@6.2.3: resolution: {integrity: sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==} + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@8.0.2: resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -757,6 +793,15 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -773,6 +818,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + send@1.2.1: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} @@ -1131,6 +1179,17 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.29)': + dependencies: + '@types/react': 18.3.29 + + '@types/react@18.3.29': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -1248,6 +1307,8 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + csstype@3.2.3: {} + debug@4.4.3: dependencies: ms: 2.1.3 @@ -1432,10 +1493,16 @@ snapshots: jose@6.2.3: {} + js-tokens@4.0.0: {} + json-schema-traverse@1.0.0: {} json-schema-typed@8.0.2: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.2.1: {} magic-string@0.30.21: @@ -1516,6 +1583,16 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + require-from-string@2.0.2: {} rollup@4.60.4: @@ -1561,6 +1638,10 @@ snapshots: safer-buffer@2.1.2: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + send@1.2.1: dependencies: debug: 4.4.3