From b8bd5b26ec0e1d9c79d2184d58494d924cd90cc6 Mon Sep 17 00:00:00 2001 From: Brad Groux <3053586+BradGroux@users.noreply.github.com> Date: Sun, 31 May 2026 15:04:12 -0500 Subject: [PATCH] Scaffold v5 desktop shell --- .gitignore | 3 + desktop/README.md | 44 + desktop/electron.vite.config.ts | 35 + desktop/package.json | 40 + desktop/resources/icon.svg | 5 + desktop/src/main/__tests__/lifecycle.test.ts | 63 + desktop/src/main/__tests__/paths.test.ts | 44 + desktop/src/main/__tests__/ports.test.ts | 24 + desktop/src/main/app-metadata.ts | 7 + desktop/src/main/bridge.ts | 40 + desktop/src/main/index.ts | 158 ++ desktop/src/main/lifecycle.ts | 108 ++ desktop/src/main/paths.ts | 49 + desktop/src/main/ports.ts | 42 + desktop/src/main/process-supervisor.ts | 127 ++ desktop/src/main/runtime.ts | 138 ++ desktop/src/main/status-page.ts | 79 + desktop/src/main/types.ts | 55 + desktop/src/preload/index.ts | 29 + desktop/src/renderer/index.html | 43 + desktop/src/types/preload.d.ts | 9 + desktop/tsconfig.json | 17 + desktop/vitest.config.ts | 9 + eslint.config.js | 14 +- package.json | 6 +- pnpm-lock.yaml | 1375 +++++++++++++++++ pnpm-workspace.yaml | 11 +- server/src/config/env.ts | 3 + server/src/index.ts | 8 +- .../__tests__/mantine-ui-primitives.test.tsx | 22 + web/src/components/ui/input.tsx | 11 +- web/src/components/ui/textarea.tsx | 9 +- web/vite.config.ts | 6 +- 33 files changed, 2612 insertions(+), 21 deletions(-) create mode 100644 desktop/README.md create mode 100644 desktop/electron.vite.config.ts create mode 100644 desktop/package.json create mode 100644 desktop/resources/icon.svg create mode 100644 desktop/src/main/__tests__/lifecycle.test.ts create mode 100644 desktop/src/main/__tests__/paths.test.ts create mode 100644 desktop/src/main/__tests__/ports.test.ts create mode 100644 desktop/src/main/app-metadata.ts create mode 100644 desktop/src/main/bridge.ts create mode 100644 desktop/src/main/index.ts create mode 100644 desktop/src/main/lifecycle.ts create mode 100644 desktop/src/main/paths.ts create mode 100644 desktop/src/main/ports.ts create mode 100644 desktop/src/main/process-supervisor.ts create mode 100644 desktop/src/main/runtime.ts create mode 100644 desktop/src/main/status-page.ts create mode 100644 desktop/src/main/types.ts create mode 100644 desktop/src/preload/index.ts create mode 100644 desktop/src/renderer/index.html create mode 100644 desktop/src/types/preload.d.ts create mode 100644 desktop/tsconfig.json create mode 100644 desktop/vitest.config.ts diff --git a/.gitignore b/.gitignore index 30bfc73d..3e4c0ea0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ # Dependencies node_modules/ +.pnpm-store/ # Build outputs dist/ build/ +out/ .next/ # IDE @@ -44,6 +46,7 @@ tasks/attachments/ tasks/archive-attachments/ .veritas-kanban/* !.veritas-kanban/.gitkeep +.veritas-desktop-dev/ # Historical broken config data (should never have been tracked) .veritas-kanban.broken/ diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 00000000..f536bc7e --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,44 @@ +# Veritas Kanban Desktop + +This package is the v5 native desktop scaffold. It uses Electron with +electron-vite, starts the existing Veritas server as the local backend, and +loads the existing web UI. + +## Development + +```bash +pnpm desktop:dev +pnpm desktop:dev:fresh +``` + +`desktop:dev` launches a loopback-only local server and a Vite web renderer +without requiring a separate terminal. The desktop runtime chooses available +ports, writes logs under `.veritas-desktop-dev//logs`, and uses SQLite +data under `.veritas-desktop-dev//data`. + +`desktop:dev:fresh` uses the `fresh` profile so onboarding and startup behavior +can be tested without reusing the default development home. + +## Runtime Boundaries + +- Electron main owns window lifecycle, process supervision, app paths, native + URL opening, status pages, and future native capabilities. +- Closing the last desktop window quits the app and stops supervised local + processes. Native menu/background behavior belongs in the dedicated menus + work. +- The renderer uses the existing Veritas web app and has no Node, filesystem, + process, or secret access. +- The preload bridge exposes only typed desktop operations: + `getAppInfo`, `getConnectionStatus`, `restartLocalServer`, `openExternal`, + and `onServerStatus`. +- Local development mode disables app auth only for the supervised loopback + runtime. The packaged app path keeps auth enabled and is expected to move to + keychain-backed bootstrap credentials in the dedicated keychain issue. + +## Production Scaffold + +`pnpm desktop:build` compiles the Electron main, preload, and fallback renderer. +Packaging, signing, notarization, updater metadata, and bundled server/web asset +layout are handled by later v5 desktop issues. Packaged mode expects a built +server entry at `server/dist/index.js` unless `VERITAS_DESKTOP_SERVER_ENTRY` is +provided. diff --git a/desktop/electron.vite.config.ts b/desktop/electron.vite.config.ts new file mode 100644 index 00000000..8f2e234e --- /dev/null +++ b/desktop/electron.vite.config.ts @@ -0,0 +1,35 @@ +import { resolve } from 'node:path'; +import { defineConfig, externalizeDepsPlugin } from 'electron-vite'; + +export default defineConfig({ + main: { + plugins: [externalizeDepsPlugin()], + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'src/main/index.ts'), + }, + }, + }, + }, + preload: { + plugins: [externalizeDepsPlugin()], + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'src/preload/index.ts'), + }, + }, + }, + }, + renderer: { + root: resolve(__dirname, 'src/renderer'), + build: { + rollupOptions: { + input: { + index: resolve(__dirname, 'src/renderer/index.html'), + }, + }, + }, + }, +}); diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 00000000..7a383c7c --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,40 @@ +{ + "name": "@veritas-kanban/desktop", + "version": "4.3.2", + "private": true, + "description": "Veritas Kanban native desktop shell", + "author": "Brad Groux ", + "license": "MIT", + "type": "module", + "main": "./out/main/index.js", + "scripts": { + "dev": "electron-vite dev", + "dev:fresh": "VERITAS_DESKTOP_PROFILE=fresh electron-vite dev", + "build": "electron-vite build", + "typecheck": "tsc --noEmit", + "lint": "eslint src electron.vite.config.ts --ext .ts", + "test": "vitest run --config vitest.config.ts", + "clean": "rm -rf dist out .veritas-desktop-dev" + }, + "dependencies": { + "@veritas-kanban/shared": "workspace:*" + }, + "devDependencies": { + "@types/node": "^25.7.0", + "electron": "^39.2.6", + "electron-vite": "^5.0.0", + "typescript": "^6.0.3", + "vite": "^7.2.7", + "vitest": "^4.1.6" + }, + "build": { + "appId": "io.digitalmeld.veritas-kanban", + "productName": "Veritas Kanban", + "directories": { + "buildResources": "resources" + }, + "mac": { + "category": "public.app-category.productivity" + } + } +} diff --git a/desktop/resources/icon.svg b/desktop/resources/icon.svg new file mode 100644 index 00000000..91ed9721 --- /dev/null +++ b/desktop/resources/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/desktop/src/main/__tests__/lifecycle.test.ts b/desktop/src/main/__tests__/lifecycle.test.ts new file mode 100644 index 00000000..42a04f00 --- /dev/null +++ b/desktop/src/main/__tests__/lifecycle.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildServerEnvironment, + buildWebEnvironment, + createDesktopAdminKey, + createManagedProcessConfigs, +} from '../lifecycle.js'; +import type { DesktopLifecycleOptions } from '../lifecycle.js'; + +function options(): DesktopLifecycleOptions { + return { + repoRoot: '/repo/veritas-kanban', + paths: { + appHome: '/tmp/veritas-desktop', + configDir: '/tmp/veritas-desktop/config', + dataDir: '/tmp/veritas-desktop/data', + logsDir: '/tmp/veritas-desktop/logs', + runtimeDir: '/tmp/veritas-desktop/runtime', + exportsDir: '/tmp/veritas-desktop/exports', + backupsDir: '/tmp/veritas-desktop/backups', + debugBundlesDir: '/tmp/veritas-desktop/debug-bundles', + }, + serverPort: 39123, + webPort: 39124, + isPackaged: false, + }; +} + +describe('desktop lifecycle config', () => { + it('creates a dev-only admin key shape without fixed secrets', () => { + expect(createDesktopAdminKey('fresh profile')).toMatch(/^desktop-dev-admin-key-fresh-profile-/); + }); + + it('builds loopback server environment for local desktop dev mode', () => { + const env = buildServerEnvironment(options(), 'desktop-dev-admin-key-test-000000000000'); + + expect(env.HOST).toBe('127.0.0.1'); + expect(env.PORT).toBe('39123'); + expect(env.VERITAS_STORAGE).toBe('sqlite'); + expect(env.VERITAS_DATA_DIR).toBe('/tmp/veritas-desktop/data'); + expect(env.VERITAS_AUTH_ENABLED).toBe('false'); + expect(env.CORS_ORIGINS).toContain('http://127.0.0.1:39124'); + }); + + it('points web dev proxies at the selected server port', () => { + const env = buildWebEnvironment(options()); + + expect(env.VITE_API_PROXY_TARGET).toBe('http://127.0.0.1:39123'); + expect(env.VITE_WS_PROXY_TARGET).toBe('ws://127.0.0.1:39123'); + }); + + it('creates server and web process configs in dev mode', () => { + const configs = createManagedProcessConfigs( + options(), + 'desktop-dev-admin-key-test-000000000000' + ); + + expect(configs.map((config) => config.name)).toEqual(['server', 'web']); + expect(configs[0]?.readyUrl).toBe('http://127.0.0.1:39123/api/health'); + expect(configs[1]?.readyUrl).toBe('http://127.0.0.1:39124'); + }); +}); diff --git a/desktop/src/main/__tests__/paths.test.ts b/desktop/src/main/__tests__/paths.test.ts new file mode 100644 index 00000000..a8819015 --- /dev/null +++ b/desktop/src/main/__tests__/paths.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import path from 'node:path'; + +import { createDesktopPaths, resolveRepoRoot } from '../paths.js'; + +describe('desktop paths', () => { + it('uses a profile-isolated dev home in the repo', () => { + const paths = createDesktopPaths({ + userDataPath: '/Users/example/Library/Application Support/Veritas Kanban', + repoRoot: '/repo/veritas-kanban', + isPackaged: false, + profile: 'fresh profile', + }); + + expect(paths.appHome).toBe( + path.join('/repo/veritas-kanban', '.veritas-desktop-dev', 'fresh-profile') + ); + expect(paths.dataDir).toBe(path.join(paths.appHome, 'data')); + expect(paths.logsDir).toBe(path.join(paths.appHome, 'logs')); + expect(paths.runtimeDir).toBe(path.join(paths.appHome, 'runtime')); + }); + + it('uses app userData in packaged mode', () => { + const paths = createDesktopPaths({ + userDataPath: '/Users/example/Library/Application Support/Veritas Kanban', + repoRoot: '/repo/veritas-kanban', + isPackaged: true, + }); + + expect(paths.appHome).toBe('/Users/example/Library/Application Support/Veritas Kanban'); + }); + + it('resolves repo root from package cwd', () => { + expect( + resolveRepoRoot('/repo/veritas-kanban/desktop/out/main', '/repo/veritas-kanban/desktop') + ).toBe('/repo/veritas-kanban'); + }); + + it('resolves repo root from electron-vite app output path', () => { + expect(resolveRepoRoot('/repo/veritas-kanban/desktop/out/main', '/repo/veritas-kanban')).toBe( + '/repo/veritas-kanban' + ); + }); +}); diff --git a/desktop/src/main/__tests__/ports.test.ts b/desktop/src/main/__tests__/ports.test.ts new file mode 100644 index 00000000..dd41808e --- /dev/null +++ b/desktop/src/main/__tests__/ports.test.ts @@ -0,0 +1,24 @@ +import { describe, expect, it } from 'vitest'; +import net from 'node:net'; + +import { findAvailablePort, isPortAvailable } from '../ports.js'; + +describe('port selection', () => { + it('returns the preferred port when it is available', async () => { + const port = await findAvailablePort(47631, '127.0.0.1', 1); + expect(port).toBe(47631); + }); + + it('falls forward when the preferred port is busy', async () => { + const server = net.createServer(); + await new Promise((resolve) => server.listen(47632, '127.0.0.1', resolve)); + + try { + expect(await isPortAvailable(47632)).toBe(false); + const port = await findAvailablePort(47632, '127.0.0.1', 3); + expect(port).toBeGreaterThan(47632); + } finally { + await new Promise((resolve) => server.close(() => resolve())); + } + }); +}); diff --git a/desktop/src/main/app-metadata.ts b/desktop/src/main/app-metadata.ts new file mode 100644 index 00000000..8d3af1ed --- /dev/null +++ b/desktop/src/main/app-metadata.ts @@ -0,0 +1,7 @@ +export const DESKTOP_APP_NAME = 'Veritas Kanban'; +export const DESKTOP_APP_ID = 'io.digitalmeld.veritas-kanban'; +export const DESKTOP_PROTOCOL = 'veritas-kanban'; +export const DESKTOP_MIN_WINDOW = { + width: 1180, + height: 760, +}; diff --git a/desktop/src/main/bridge.ts b/desktop/src/main/bridge.ts new file mode 100644 index 00000000..d80dc2a3 --- /dev/null +++ b/desktop/src/main/bridge.ts @@ -0,0 +1,40 @@ +import type { IpcMain, Shell } from 'electron'; + +import { DESKTOP_APP_ID, DESKTOP_APP_NAME } from './app-metadata.js'; +import type { DesktopAppInfo } from './types.js'; +import type { DesktopRuntime } from './runtime.js'; + +const SAFE_EXTERNAL_PROTOCOLS = new Set(['https:', 'http:', 'mailto:']); + +export function registerDesktopBridge( + ipcMain: IpcMain, + runtime: DesktopRuntime, + shell: Shell, + packaged: boolean +): void { + ipcMain.handle( + 'desktop:get-app-info', + (): DesktopAppInfo => ({ + name: DESKTOP_APP_NAME, + appId: DESKTOP_APP_ID, + version: process.env.npm_package_version || '0.0.0', + platform: process.platform, + packaged, + }) + ); + + ipcMain.handle('desktop:get-connection-status', () => runtime.snapshot()); + ipcMain.handle('desktop:restart-local-server', () => runtime.restartLocalServer()); + ipcMain.handle('desktop:open-external', async (_event, url: unknown) => { + if (typeof url !== 'string') { + throw new Error('URL must be a string'); + } + + const parsed = new URL(url); + if (!SAFE_EXTERNAL_PROTOCOLS.has(parsed.protocol)) { + throw new Error(`External URL protocol is not allowed: ${parsed.protocol}`); + } + + await shell.openExternal(parsed.toString()); + }); +} diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts new file mode 100644 index 00000000..e99cee4a --- /dev/null +++ b/desktop/src/main/index.ts @@ -0,0 +1,158 @@ +import { app, BrowserWindow, ipcMain, shell } from 'electron'; +import path from 'node:path'; +import { mkdirSync } from 'node:fs'; + +import { DESKTOP_APP_ID, DESKTOP_APP_NAME, DESKTOP_MIN_WINDOW } from './app-metadata.js'; +import { registerDesktopBridge } from './bridge.js'; +import { createDesktopPaths, resolveRepoRoot } from './paths.js'; +import { findAvailablePort } from './ports.js'; +import { DesktopRuntime } from './runtime.js'; +import { statusPageUrl } from './status-page.js'; + +let mainWindow: BrowserWindow | null = null; +let runtime: DesktopRuntime | null = null; +let quitting = false; +let shutdownStarted = false; + +function isPackagedRuntime(): boolean { + return app.isPackaged || process.env.VERITAS_DESKTOP_PRODUCTION === 'true'; +} + +const launchPackaged = isPackagedRuntime(); +const launchRepoRoot = resolveRepoRoot(app.getAppPath()); +const launchProfile = process.env.VERITAS_DESKTOP_PROFILE || 'default'; + +if (!launchPackaged) { + const devUserDataPath = path.join( + launchRepoRoot, + '.veritas-desktop-dev', + launchProfile, + 'app-home' + ); + mkdirSync(devUserDataPath, { recursive: true }); + app.setPath('userData', devUserDataPath); +} + +function createMainWindow(): BrowserWindow { + const preloadPath = path.join(__dirname, '../preload/index.mjs'); + + const window = new BrowserWindow({ + title: DESKTOP_APP_NAME, + minWidth: DESKTOP_MIN_WINDOW.width, + minHeight: DESKTOP_MIN_WINDOW.height, + width: 1360, + height: 900, + titleBarStyle: process.platform === 'darwin' ? 'hiddenInset' : 'default', + backgroundColor: '#111318', + show: false, + webPreferences: { + preload: preloadPath, + nodeIntegration: false, + contextIsolation: true, + sandbox: true, + }, + }); + + window.once('ready-to-show', () => window.show()); + window.webContents.setWindowOpenHandler(({ url }) => { + void shell.openExternal(url); + return { action: 'deny' }; + }); + window.webContents.on('will-navigate', (event, url) => { + const current = runtime?.getRendererOrigin(); + if (current && !url.startsWith(current)) { + event.preventDefault(); + void shell.openExternal(url); + } + }); + window.webContents.on('did-fail-load', (_event, _code, description) => { + if (!quitting) { + void window.loadURL( + statusPageUrl('Veritas Kanban could not load', description, runtime?.snapshot()) + ); + } + }); + + return window; +} + +async function boot(): Promise { + app.setName(DESKTOP_APP_NAME); + app.setAppUserModelId(DESKTOP_APP_ID); + + const packaged = launchPackaged; + const repoRoot = launchRepoRoot; + const profile = launchProfile; + const paths = createDesktopPaths({ + userDataPath: app.getPath('userData'), + repoRoot, + isPackaged: packaged, + profile, + }); + + const serverPort = await findAvailablePort( + Number(process.env.VERITAS_DESKTOP_SERVER_PORT || 3001) + ); + const webPort = await findAvailablePort(Number(process.env.VERITAS_DESKTOP_WEB_PORT || 3000)); + + mainWindow = createMainWindow(); + await mainWindow.loadURL(statusPageUrl('Starting Veritas Kanban', 'Preparing the local app.')); + + runtime = new DesktopRuntime({ + repoRoot, + paths, + serverPort, + webPort, + isPackaged: packaged, + profile, + }); + + registerDesktopBridge(ipcMain, runtime, shell, packaged); + runtime.on('status', (status) => { + mainWindow?.webContents.send('desktop:server-status', status); + }); + + try { + await runtime.start(); + await mainWindow.loadURL(runtime.getRendererOrigin()); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + await mainWindow.loadURL( + statusPageUrl('Veritas Kanban startup failed', message, runtime.snapshot()) + ); + } +} + +app.on('ready', () => { + void boot(); +}); + +app.on('before-quit', (event) => { + quitting = true; + if (runtime && !shutdownStarted) { + event.preventDefault(); + shutdownStarted = true; + void runtime.stop().finally(() => app.quit()); + } +}); + +app.on('window-all-closed', () => { + app.quit(); +}); + +app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + void boot(); + } +}); + +process.on('uncaughtException', (error) => { + mainWindow?.loadURL( + statusPageUrl('Veritas Kanban desktop error', error.message, runtime?.snapshot()) + ); +}); + +process.on('unhandledRejection', (reason) => { + const message = reason instanceof Error ? reason.message : String(reason); + mainWindow?.loadURL(statusPageUrl('Veritas Kanban desktop error', message, runtime?.snapshot())); +}); diff --git a/desktop/src/main/lifecycle.ts b/desktop/src/main/lifecycle.ts new file mode 100644 index 00000000..6d4eb6fb --- /dev/null +++ b/desktop/src/main/lifecycle.ts @@ -0,0 +1,108 @@ +import path from 'node:path'; +import { randomUUID } from 'node:crypto'; + +import type { DesktopPaths, ManagedProcessConfig } from './types.js'; + +const ADMIN_KEY_PREFIX = 'desktop-dev-admin-key'; + +export interface DesktopLifecycleOptions { + repoRoot: string; + paths: DesktopPaths; + serverPort: number; + webPort: number; + isPackaged: boolean; +} + +function pnpmCommand(): string { + return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm'; +} + +export function createDesktopAdminKey(profile = 'default'): string { + const safeProfile = profile.replace(/[^a-zA-Z0-9._-]/g, '-'); + return `${ADMIN_KEY_PREFIX}-${safeProfile}-${randomUUID()}`; +} + +export function buildServerEnvironment( + options: DesktopLifecycleOptions, + adminKey: string +): NodeJS.ProcessEnv { + const serverOrigin = `http://127.0.0.1:${options.serverPort}`; + const webOrigin = `http://127.0.0.1:${options.webPort}`; + + return { + ...process.env, + NODE_ENV: options.isPackaged ? 'production' : 'development', + HOST: '127.0.0.1', + PORT: String(options.serverPort), + VERITAS_ADMIN_KEY: adminKey, + VERITAS_AUTH_ENABLED: options.isPackaged ? 'true' : 'false', + VERITAS_AUTH_LOCALHOST_BYPASS: 'false', + VERITAS_STORAGE: 'sqlite', + VERITAS_DATA_DIR: options.paths.dataDir, + VERITAS_DISABLE_WATCHERS: '1', + CORS_ORIGINS: `${serverOrigin},${webOrigin},http://localhost:${options.webPort}`, + }; +} + +export function buildWebEnvironment(options: DesktopLifecycleOptions): NodeJS.ProcessEnv { + const serverOrigin = `http://127.0.0.1:${options.serverPort}`; + + return { + ...process.env, + VITE_HOST: '127.0.0.1', + VITE_API_PROXY_TARGET: serverOrigin, + VITE_WS_PROXY_TARGET: serverOrigin.replace(/^http/, 'ws'), + }; +} + +export function createManagedProcessConfigs( + options: DesktopLifecycleOptions, + adminKey: string +): ManagedProcessConfig[] { + const serverConfig: ManagedProcessConfig = options.isPackaged + ? { + name: 'server', + command: process.execPath, + args: [ + process.env.VERITAS_DESKTOP_SERVER_ENTRY || + path.join(options.repoRoot, 'server/dist/index.js'), + ], + cwd: options.repoRoot, + env: buildServerEnvironment(options, adminKey), + logFile: path.join(options.paths.logsDir, 'server.log'), + readyUrl: `http://127.0.0.1:${options.serverPort}/api/health`, + } + : { + name: 'server', + command: pnpmCommand(), + args: ['--filter', '@veritas-kanban/server', 'dev'], + cwd: options.repoRoot, + env: buildServerEnvironment(options, adminKey), + logFile: path.join(options.paths.logsDir, 'server.log'), + readyUrl: `http://127.0.0.1:${options.serverPort}/api/health`, + }; + + if (options.isPackaged) { + return [serverConfig]; + } + + const webConfig: ManagedProcessConfig = { + name: 'web', + command: pnpmCommand(), + args: [ + '--filter', + '@veritas-kanban/web', + 'dev', + '--host', + '127.0.0.1', + '--port', + String(options.webPort), + ], + cwd: options.repoRoot, + env: buildWebEnvironment(options), + logFile: path.join(options.paths.logsDir, 'web.log'), + readyUrl: `http://127.0.0.1:${options.webPort}`, + }; + + return [serverConfig, webConfig]; +} diff --git a/desktop/src/main/paths.ts b/desktop/src/main/paths.ts new file mode 100644 index 00000000..26d928ef --- /dev/null +++ b/desktop/src/main/paths.ts @@ -0,0 +1,49 @@ +import path from 'node:path'; + +import type { DesktopPaths } from './types.js'; + +export interface CreateDesktopPathsOptions { + userDataPath: string; + repoRoot: string; + isPackaged: boolean; + profile?: string; +} + +function profileSegment(profile: string | undefined): string { + return (profile || 'default').replace(/[^a-zA-Z0-9._-]/g, '-'); +} + +export function createDesktopPaths(options: CreateDesktopPathsOptions): DesktopPaths { + const appHome = options.isPackaged + ? options.userDataPath + : path.join(options.repoRoot, '.veritas-desktop-dev', profileSegment(options.profile)); + + return { + appHome, + configDir: path.join(appHome, 'config'), + dataDir: path.join(appHome, 'data'), + logsDir: path.join(appHome, 'logs'), + runtimeDir: path.join(appHome, 'runtime'), + exportsDir: path.join(appHome, 'exports'), + backupsDir: path.join(appHome, 'backups'), + debugBundlesDir: path.join(appHome, 'debug-bundles'), + }; +} + +export function resolveRepoRoot(appPath: string, cwd = process.cwd()): string { + if (process.env.VERITAS_REPO_ROOT) { + return process.env.VERITAS_REPO_ROOT; + } + + if (path.basename(cwd) === 'desktop') { + return path.resolve(cwd, '..'); + } + + const segments = appPath.split(path.sep); + const desktopIndex = segments.lastIndexOf('desktop'); + if (desktopIndex > 0) { + return segments.slice(0, desktopIndex).join(path.sep) || path.sep; + } + + return path.resolve(appPath, '..'); +} diff --git a/desktop/src/main/ports.ts b/desktop/src/main/ports.ts new file mode 100644 index 00000000..e911c3a5 --- /dev/null +++ b/desktop/src/main/ports.ts @@ -0,0 +1,42 @@ +import net from 'node:net'; + +export async function isPortAvailable(port: number, host = '127.0.0.1'): Promise { + return new Promise((resolve) => { + const server = net.createServer(); + + server.once('error', () => resolve(false)); + server.once('listening', () => { + server.close(() => resolve(true)); + }); + server.listen(port, host); + }); +} + +export async function findAvailablePort( + preferredPort: number, + host = '127.0.0.1', + maxAttempts = 50 +): Promise { + for (let offset = 0; offset < maxAttempts; offset += 1) { + const candidate = preferredPort + offset; + if (await isPortAvailable(candidate, host)) { + return candidate; + } + } + + return new Promise((resolve, reject) => { + const server = net.createServer(); + server.once('error', reject); + server.once('listening', () => { + const address = server.address(); + server.close(() => { + if (typeof address === 'object' && address) { + resolve(address.port); + return; + } + reject(new Error('Unable to allocate an ephemeral port')); + }); + }); + server.listen(0, host); + }); +} diff --git a/desktop/src/main/process-supervisor.ts b/desktop/src/main/process-supervisor.ts new file mode 100644 index 00000000..2a995c3b --- /dev/null +++ b/desktop/src/main/process-supervisor.ts @@ -0,0 +1,127 @@ +import { EventEmitter } from 'node:events'; +import { createWriteStream, type WriteStream } from 'node:fs'; +import { mkdir } from 'node:fs/promises'; +import path from 'node:path'; +import { spawn, type ChildProcess } from 'node:child_process'; + +import type { ManagedProcessConfig, ManagedProcessSnapshot, DesktopProcessState } from './types.js'; + +export class ProcessSupervisor extends EventEmitter { + private child: ChildProcess | null = null; + private state: DesktopProcessState = 'idle'; + private lastError: string | null = null; + private startedAt: string | null = null; + private exitedAt: string | null = null; + private logStream: WriteStream | null = null; + private stopping = false; + + constructor(private readonly config: ManagedProcessConfig) { + super(); + } + + snapshot(): ManagedProcessSnapshot { + return { + name: this.config.name, + state: this.state, + pid: this.child?.pid ?? null, + port: this.config.readyUrl ? Number(new URL(this.config.readyUrl).port) : null, + lastError: this.lastError, + startedAt: this.startedAt, + exitedAt: this.exitedAt, + }; + } + + async start(): Promise { + if (this.child) return; + + await mkdir(path.dirname(this.config.logFile), { recursive: true }); + this.logStream = createWriteStream(this.config.logFile, { flags: 'a' }); + this.setState('starting'); + this.stopping = false; + this.startedAt = new Date().toISOString(); + this.exitedAt = null; + this.lastError = null; + + const child = spawn(this.config.command, this.config.args, { + cwd: this.config.cwd, + env: this.config.env, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + this.child = child; + this.log(`[desktop] started ${this.config.command} ${this.config.args.join(' ')}\n`); + child.stdout?.on('data', (chunk: Buffer) => this.log(chunk)); + child.stderr?.on('data', (chunk: Buffer) => this.log(chunk)); + + child.once('error', (error) => { + this.lastError = error.message; + this.setState('failed'); + this.emit('error', error); + }); + + child.once('exit', (code, signal) => { + this.exitedAt = new Date().toISOString(); + this.log(`[desktop] exited code=${code ?? 'null'} signal=${signal ?? 'null'}\n`); + this.child = null; + this.closeLogStream(); + if (this.stopping || code === 0) { + this.setState('stopped'); + return; + } + this.lastError = `${this.config.name} exited unexpectedly with code ${code ?? 'null'} signal ${signal ?? 'null'}`; + this.setState('failed'); + }); + } + + markReady(): void { + if (this.child) { + this.setState('ready'); + } + } + + async stop(): Promise { + if (!this.child) { + this.setState('stopped'); + return; + } + + this.stopping = true; + this.setState('stopping'); + const child = this.child; + const timeoutMs = this.config.shutdownTimeoutMs ?? 5000; + + await new Promise((resolve) => { + const timeout = setTimeout(() => { + if (!child.killed) { + child.kill('SIGKILL'); + } + }, timeoutMs); + + child.once('exit', () => { + clearTimeout(timeout); + resolve(); + }); + + child.kill('SIGTERM'); + }); + } + + async restart(): Promise { + await this.stop(); + await this.start(); + } + + private setState(state: DesktopProcessState): void { + this.state = state; + this.emit('state', this.snapshot()); + } + + private log(chunk: Buffer | string): void { + this.logStream?.write(chunk); + } + + private closeLogStream(): void { + this.logStream?.end(); + this.logStream = null; + } +} diff --git a/desktop/src/main/runtime.ts b/desktop/src/main/runtime.ts new file mode 100644 index 00000000..549b25ff --- /dev/null +++ b/desktop/src/main/runtime.ts @@ -0,0 +1,138 @@ +import { EventEmitter } from 'node:events'; +import { mkdir, writeFile, rm } from 'node:fs/promises'; +import path from 'node:path'; + +import { createDesktopAdminKey, createManagedProcessConfigs } from './lifecycle.js'; +import { ProcessSupervisor } from './process-supervisor.js'; +import type { DesktopPaths, DesktopStatusSnapshot } from './types.js'; + +export interface DesktopRuntimeOptions { + repoRoot: string; + paths: DesktopPaths; + serverPort: number; + webPort: number; + isPackaged: boolean; + profile: string; +} + +export class DesktopRuntime extends EventEmitter { + private readonly server: ProcessSupervisor; + private readonly web: ProcessSupervisor | null; + private lastError: string | null = null; + private readonly serverOrigin: string; + private readonly rendererOrigin: string; + + constructor(private readonly options: DesktopRuntimeOptions) { + super(); + const adminKey = createDesktopAdminKey(options.profile); + const [serverConfig, webConfig] = createManagedProcessConfigs(options, adminKey); + this.server = new ProcessSupervisor(serverConfig); + this.web = webConfig ? new ProcessSupervisor(webConfig) : null; + this.serverOrigin = `http://127.0.0.1:${options.serverPort}`; + this.rendererOrigin = options.isPackaged + ? this.serverOrigin + : `http://127.0.0.1:${options.webPort}`; + + for (const supervisor of [this.server, this.web].filter(Boolean) as ProcessSupervisor[]) { + supervisor.on('state', () => this.emitStatus()); + supervisor.on('error', (error) => { + this.lastError = error instanceof Error ? error.message : String(error); + this.emitStatus(); + }); + } + } + + getRendererOrigin(): string { + return this.rendererOrigin; + } + + snapshot(): DesktopStatusSnapshot { + return { + mode: this.options.isPackaged ? 'local-production' : 'local-dev', + server: this.server.snapshot(), + web: this.web?.snapshot(), + serverOrigin: this.serverOrigin, + rendererOrigin: this.rendererOrigin, + appHome: this.options.paths.appHome, + lastError: this.lastError, + }; + } + + async start(): Promise { + await this.ensureDirectories(); + await this.writeRuntimeState(); + await this.server.start(); + await this.waitForReady(this.serverOrigin + '/api/health', 'server'); + this.server.markReady(); + + if (this.web) { + await this.web.start(); + await this.waitForReady(this.rendererOrigin, 'web'); + this.web.markReady(); + } + + this.emitStatus(); + } + + async restartLocalServer(): Promise { + await this.server.restart(); + await this.waitForReady(this.serverOrigin + '/api/health', 'server'); + this.server.markReady(); + this.emitStatus(); + return this.snapshot(); + } + + async stop(): Promise { + await Promise.all([this.web?.stop(), this.server.stop()].filter(Boolean) as Promise[]); + await rm(path.join(this.options.paths.runtimeDir, 'server-state.json'), { force: true }); + } + + private async ensureDirectories(): Promise { + await Promise.all( + Object.values(this.options.paths).map((targetPath) => mkdir(targetPath, { recursive: true })) + ); + } + + private async writeRuntimeState(): Promise { + await mkdir(this.options.paths.runtimeDir, { recursive: true }); + await writeFile( + path.join(this.options.paths.runtimeDir, 'server-state.json'), + JSON.stringify( + { + mode: this.options.isPackaged ? 'local-production' : 'local-dev', + serverOrigin: this.serverOrigin, + rendererOrigin: this.rendererOrigin, + updatedAt: new Date().toISOString(), + }, + null, + 2 + ) + ); + } + + private async waitForReady(url: string, label: string): Promise { + const deadline = Date.now() + 45_000; + let lastError: string | null = null; + + while (Date.now() < deadline) { + try { + const response = await fetch(url); + if (response.ok) { + return; + } + lastError = `${label} returned ${response.status}`; + } catch (error) { + lastError = error instanceof Error ? error.message : String(error); + } + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + this.lastError = `${label} did not become ready: ${lastError ?? 'timeout'}`; + this.emitStatus(); + throw new Error(this.lastError); + } + + private emitStatus(): void { + this.emit('status', this.snapshot()); + } +} diff --git a/desktop/src/main/status-page.ts b/desktop/src/main/status-page.ts new file mode 100644 index 00000000..384ab4e9 --- /dev/null +++ b/desktop/src/main/status-page.ts @@ -0,0 +1,79 @@ +import type { DesktopStatusSnapshot } from './types.js'; + +function escapeHtml(value: string): string { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} + +export function statusPage(title: string, message: string, status?: DesktopStatusSnapshot): string { + const statusJson = status ? escapeHtml(JSON.stringify(status, null, 2)) : ''; + + return ` + + + + + + ${escapeHtml(title)} + + + +
+

${escapeHtml(title)}

+

${escapeHtml(message)}

+ ${statusJson ? `
${statusJson}
` : ''} +
+ +`; +} + +export function statusPageUrl( + title: string, + message: string, + status?: DesktopStatusSnapshot +): string { + return `data:text/html;charset=utf-8,${encodeURIComponent(statusPage(title, message, status))}`; +} diff --git a/desktop/src/main/types.ts b/desktop/src/main/types.ts new file mode 100644 index 00000000..a71d40ac --- /dev/null +++ b/desktop/src/main/types.ts @@ -0,0 +1,55 @@ +export type DesktopProcessName = 'server' | 'web'; + +export type DesktopProcessState = 'idle' | 'starting' | 'ready' | 'stopping' | 'stopped' | 'failed'; + +export type DesktopConnectionMode = 'local-dev' | 'local-production'; + +export interface DesktopPaths { + appHome: string; + configDir: string; + dataDir: string; + logsDir: string; + runtimeDir: string; + exportsDir: string; + backupsDir: string; + debugBundlesDir: string; +} + +export interface ManagedProcessConfig { + name: DesktopProcessName; + command: string; + args: string[]; + cwd: string; + env: NodeJS.ProcessEnv; + logFile: string; + readyUrl?: string; + shutdownTimeoutMs?: number; +} + +export interface ManagedProcessSnapshot { + name: DesktopProcessName; + state: DesktopProcessState; + pid: number | null; + port: number | null; + lastError: string | null; + startedAt: string | null; + exitedAt: string | null; +} + +export interface DesktopStatusSnapshot { + mode: DesktopConnectionMode; + server: ManagedProcessSnapshot; + web?: ManagedProcessSnapshot; + serverOrigin: string | null; + rendererOrigin: string | null; + appHome: string; + lastError: string | null; +} + +export interface DesktopAppInfo { + name: string; + appId: string; + version: string; + platform: NodeJS.Platform; + packaged: boolean; +} diff --git a/desktop/src/preload/index.ts b/desktop/src/preload/index.ts new file mode 100644 index 00000000..c5ca391b --- /dev/null +++ b/desktop/src/preload/index.ts @@ -0,0 +1,29 @@ +import { contextBridge, ipcRenderer } from 'electron'; + +import type { DesktopAppInfo, DesktopStatusSnapshot } from '../main/types.js'; + +export interface VeritasDesktopApi { + getAppInfo(): Promise; + getConnectionStatus(): Promise; + restartLocalServer(): Promise; + openExternal(url: string): Promise; + onServerStatus(listener: (status: DesktopStatusSnapshot) => void): () => void; +} + +const api: VeritasDesktopApi = { + getAppInfo: () => ipcRenderer.invoke('desktop:get-app-info') as Promise, + getConnectionStatus: () => + ipcRenderer.invoke('desktop:get-connection-status') as Promise, + restartLocalServer: () => + ipcRenderer.invoke('desktop:restart-local-server') as Promise, + openExternal: (url: string) => ipcRenderer.invoke('desktop:open-external', url) as Promise, + onServerStatus: (listener) => { + const handler = (_event: Electron.IpcRendererEvent, status: DesktopStatusSnapshot): void => { + listener(status); + }; + ipcRenderer.on('desktop:server-status', handler); + return () => ipcRenderer.off('desktop:server-status', handler); + }, +}; + +contextBridge.exposeInMainWorld('veritasDesktop', api); diff --git a/desktop/src/renderer/index.html b/desktop/src/renderer/index.html new file mode 100644 index 00000000..03f8cdfa --- /dev/null +++ b/desktop/src/renderer/index.html @@ -0,0 +1,43 @@ + + + + + + + Veritas Kanban + + + +
+

Starting Veritas Kanban

+

Preparing the local desktop runtime.

+
+ + diff --git a/desktop/src/types/preload.d.ts b/desktop/src/types/preload.d.ts new file mode 100644 index 00000000..31e29495 --- /dev/null +++ b/desktop/src/types/preload.d.ts @@ -0,0 +1,9 @@ +import type { VeritasDesktopApi } from '../preload/index.js'; + +declare global { + interface Window { + veritasDesktop?: VeritasDesktopApi; + } +} + +export {}; diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json new file mode 100644 index 00000000..44e48ff3 --- /dev/null +++ b/desktop/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "types": ["node", "vitest"], + "noEmit": true + }, + "include": ["src/**/*.ts", "electron.vite.config.ts"], + "exclude": ["dist", "out", "node_modules"] +} diff --git a/desktop/vitest.config.ts b/desktop/vitest.config.ts new file mode 100644 index 00000000..9f2a9028 --- /dev/null +++ b/desktop/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'node', + include: ['src/**/*.test.ts'], + exclude: ['dist/**', 'out/**', 'node_modules/**'], + }, +}); diff --git a/eslint.config.js b/eslint.config.js index b93cd5c2..9f083d14 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,9 +9,11 @@ export default [ { ignores: [ '**/dist/**', + '**/out/**', '**/node_modules/**', '**/*.d.ts', '.veritas-kanban/**', + '.veritas-desktop-dev/**', // Config files (CommonJS tooling) 'web/tailwind.config.js', 'web/vite.config.js', @@ -22,9 +24,17 @@ export default [ // Base JS config js.configs.recommended, - // TypeScript files (server, shared, cli, mcp) + // TypeScript files (server, shared, cli, mcp, desktop) { - files: ['server/src/**/*.ts', 'shared/src/**/*.ts', 'cli/src/**/*.ts', 'mcp/src/**/*.ts'], + files: [ + 'server/src/**/*.ts', + 'shared/src/**/*.ts', + 'cli/src/**/*.ts', + 'mcp/src/**/*.ts', + 'desktop/src/**/*.ts', + 'desktop/electron.vite.config.ts', + 'desktop/vitest.config.ts', + ], languageOptions: { parser: tsparser, parserOptions: { diff --git a/package.json b/package.json index d9195bfb..dcdd09db 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,13 @@ "packageManager": "pnpm@9.15.4", "scripts": { "dev": "concurrently -n server,web -c blue,green \"pnpm --filter server dev\" \"pnpm --filter web dev\"", + "desktop:dev": "pnpm --filter @veritas-kanban/desktop dev", + "desktop:dev:fresh": "pnpm --filter @veritas-kanban/desktop dev:fresh", + "desktop:build": "pnpm --filter @veritas-kanban/desktop build", + "desktop:test": "pnpm --filter @veritas-kanban/desktop test", "dev:clean": "bash scripts/dev-clean.sh", "dev:watchdog": "bash scripts/dev-watchdog.sh", - "build": "pnpm --filter @veritas-kanban/shared build && pnpm --filter @veritas-kanban/server build && pnpm --filter @veritas-kanban/web build && pnpm --filter @veritas-kanban/cli build && pnpm --filter @veritas-kanban/mcp build", + "build": "pnpm --filter @veritas-kanban/shared build && pnpm --filter @veritas-kanban/server build && pnpm --filter @veritas-kanban/web build && pnpm --filter @veritas-kanban/cli build && pnpm --filter @veritas-kanban/mcp build && pnpm --filter @veritas-kanban/desktop build", "lint": "eslint .", "lint:budget": "eslint . --max-warnings=714", "lint:fix": "eslint . --fix", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a073a3e..76fd53cc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,31 @@ importers: specifier: ^6.0.3 version: 6.0.3 + desktop: + dependencies: + '@veritas-kanban/shared': + specifier: workspace:* + version: link:../shared + devDependencies: + '@types/node': + specifier: ^25.7.0 + version: 25.7.0 + electron: + specifier: ^39.2.6 + version: 39.8.10 + electron-vite: + specifier: ^5.0.0 + version: 5.0.0(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)) + typescript: + specifier: ^6.0.3 + version: 6.0.3 + vite: + specifier: ^7.2.7 + version: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0) + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@29.1.1(@noble/hashes@1.8.0))(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)) + mcp: dependencies: '@modelcontextprotocol/sdk': @@ -569,6 +594,13 @@ packages: } engines: { node: '>=6.9.0' } + '@babel/helper-plugin-utils@7.29.7': + resolution: + { + integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==, + } + engines: { node: '>=6.9.0' } + '@babel/helper-replace-supers@7.28.6': resolution: { @@ -639,6 +671,15 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: + { + integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==, + } + engines: { node: '>=6.9.0' } + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-modules-commonjs@7.28.6': resolution: { @@ -836,6 +877,13 @@ packages: peerDependencies: '@noble/ciphers': ^1.0.0 + '@electron/get@2.0.3': + resolution: + { + integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==, + } + engines: { node: '>=12' } + '@emnapi/core@1.10.0': resolution: { @@ -854,6 +902,15 @@ packages: integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==, } + '@esbuild/aix-ppc64@0.25.12': + resolution: + { + integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.27.7': resolution: { @@ -863,6 +920,15 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.25.12': + resolution: + { + integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.27.7': resolution: { @@ -872,6 +938,15 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm@0.25.12': + resolution: + { + integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.27.7': resolution: { @@ -881,6 +956,15 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-x64@0.25.12': + resolution: + { + integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.27.7': resolution: { @@ -890,6 +974,15 @@ packages: cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.25.12': + resolution: + { + integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.27.7': resolution: { @@ -899,6 +992,15 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.25.12': + resolution: + { + integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.27.7': resolution: { @@ -908,6 +1010,15 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': + resolution: + { + integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.27.7': resolution: { @@ -917,6 +1028,15 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': + resolution: + { + integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.27.7': resolution: { @@ -926,6 +1046,15 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.25.12': + resolution: + { + integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.27.7': resolution: { @@ -935,6 +1064,15 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.25.12': + resolution: + { + integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==, + } + engines: { node: '>=18' } + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.27.7': resolution: { @@ -944,6 +1082,15 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.25.12': + resolution: + { + integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.27.7': resolution: { @@ -953,6 +1100,15 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.25.12': + resolution: + { + integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==, + } + engines: { node: '>=18' } + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.27.7': resolution: { @@ -962,6 +1118,15 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.25.12': + resolution: + { + integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==, + } + engines: { node: '>=18' } + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.27.7': resolution: { @@ -971,6 +1136,15 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.25.12': + resolution: + { + integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==, + } + engines: { node: '>=18' } + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.27.7': resolution: { @@ -980,6 +1154,15 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.25.12': + resolution: + { + integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==, + } + engines: { node: '>=18' } + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.27.7': resolution: { @@ -989,6 +1172,15 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.25.12': + resolution: + { + integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==, + } + engines: { node: '>=18' } + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.27.7': resolution: { @@ -998,6 +1190,15 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.25.12': + resolution: + { + integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.27.7': resolution: { @@ -1007,6 +1208,15 @@ packages: cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.25.12': + resolution: + { + integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.27.7': resolution: { @@ -1016,6 +1226,15 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': + resolution: + { + integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.27.7': resolution: { @@ -1025,6 +1244,15 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': + resolution: + { + integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.27.7': resolution: { @@ -1034,6 +1262,15 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': + resolution: + { + integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.27.7': resolution: { @@ -1043,6 +1280,15 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': + resolution: + { + integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [openharmony] + '@esbuild/openharmony-arm64@0.27.7': resolution: { @@ -1052,6 +1298,15 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.25.12': + resolution: + { + integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.27.7': resolution: { @@ -1061,6 +1316,15 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.25.12': + resolution: + { + integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==, + } + engines: { node: '>=18' } + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.27.7': resolution: { @@ -1070,6 +1334,15 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.25.12': + resolution: + { + integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==, + } + engines: { node: '>=18' } + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.27.7': resolution: { @@ -1079,6 +1352,15 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.25.12': + resolution: + { + integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==, + } + engines: { node: '>=18' } + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.27.7': resolution: { @@ -1786,6 +2068,206 @@ packages: integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==, } + '@rollup/rollup-android-arm-eabi@4.60.4': + resolution: + { + integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==, + } + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.4': + resolution: + { + integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==, + } + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.4': + resolution: + { + integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==, + } + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.4': + resolution: + { + integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==, + } + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.4': + resolution: + { + integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==, + } + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.4': + resolution: + { + integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==, + } + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + resolution: + { + integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + resolution: + { + integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==, + } + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + resolution: + { + integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.4': + resolution: + { + integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==, + } + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + resolution: + { + integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.4': + resolution: + { + integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==, + } + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + resolution: + { + integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + resolution: + { + integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==, + } + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + resolution: + { + integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + resolution: + { + integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==, + } + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + resolution: + { + integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==, + } + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.4': + resolution: + { + integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.4': + resolution: + { + integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==, + } + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.4': + resolution: + { + integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==, + } + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.4': + resolution: + { + integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==, + } + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + resolution: + { + integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==, + } + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + resolution: + { + integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==, + } + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.4': + resolution: + { + integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==, + } + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.4': + resolution: + { + integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==, + } + cpu: [x64] + os: [win32] + '@scarf/scarf@1.4.0': resolution: { @@ -1810,6 +2292,13 @@ packages: integrity: sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==, } + '@sindresorhus/is@4.6.0': + resolution: + { + integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==, + } + engines: { node: '>=10' } + '@sindresorhus/merge-streams@4.0.0': resolution: { @@ -1829,6 +2318,13 @@ packages: integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==, } + '@szmarczak/http-timer@4.0.6': + resolution: + { + integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==, + } + engines: { node: '>=10' } + '@tailwindcss/node@4.3.0': resolution: { @@ -2086,6 +2582,12 @@ packages: integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==, } + '@types/cacheable-request@6.0.3': + resolution: + { + integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==, + } + '@types/chai@5.2.3': resolution: { @@ -2240,6 +2742,12 @@ packages: integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==, } + '@types/http-cache-semantics@4.2.0': + resolution: + { + integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==, + } + '@types/http-errors@2.0.5': resolution: { @@ -2258,6 +2766,12 @@ packages: integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==, } + '@types/keyv@3.1.4': + resolution: + { + integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==, + } + '@types/mdast@4.0.4': resolution: { @@ -2294,6 +2808,12 @@ packages: integrity: sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==, } + '@types/node@22.19.19': + resolution: + { + integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==, + } + '@types/node@25.7.0': resolution: { @@ -2333,6 +2853,12 @@ packages: integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==, } + '@types/responselike@1.0.3': + resolution: + { + integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==, + } + '@types/sanitize-html@2.16.1': resolution: { @@ -2423,6 +2949,12 @@ packages: integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==, } + '@types/yauzl@2.10.3': + resolution: + { + integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==, + } + '@typescript-eslint/eslint-plugin@8.59.3': resolution: { @@ -2940,6 +3472,13 @@ packages: } engines: { node: '>=18' } + boolean@3.2.0: + resolution: + { + integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==, + } + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + brace-expansion@5.0.5: resolution: { @@ -3021,6 +3560,27 @@ packages: } engines: { node: '>= 0.8' } + cac@6.7.14: + resolution: + { + integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==, + } + engines: { node: '>=8' } + + cacheable-lookup@5.0.4: + resolution: + { + integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==, + } + engines: { node: '>=10.6.0' } + + cacheable-request@7.0.4: + resolution: + { + integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==, + } + engines: { node: '>=8' } + call-bind-apply-helpers@1.0.2: resolution: { @@ -3172,6 +3732,12 @@ packages: } engines: { node: '>=12' } + clone-response@1.0.3: + resolution: + { + integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==, + } + clsx@2.1.1: resolution: { @@ -3593,6 +4159,13 @@ packages: integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==, } + decompress-response@6.0.0: + resolution: + { + integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==, + } + engines: { node: '>=10' } + dedent@1.7.2: resolution: { @@ -3631,6 +4204,13 @@ packages: } engines: { node: '>=18' } + defer-to-connect@2.0.1: + resolution: + { + integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==, + } + engines: { node: '>=10' } + define-data-property@1.1.4: resolution: { @@ -3686,6 +4266,12 @@ packages: integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==, } + detect-node@2.1.0: + resolution: + { + integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==, + } + devlop@1.1.0: resolution: { @@ -3825,6 +4411,28 @@ packages: integrity: sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==, } + electron-vite@5.0.0: + resolution: + { + integrity: sha512-OHp/vjdlubNlhNkPkL/+3JD34ii5ov7M0GpuXEVdQeqdQ3ulvVR7Dg/rNBLfS5XPIFwgoBLDf9sjjrL+CuDyRQ==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + peerDependencies: + '@swc/core': ^1.0.0 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 + peerDependenciesMeta: + '@swc/core': + optional: true + + electron@39.8.10: + resolution: + { + integrity: sha512-zbYtGPYUI7PzqLAzkk21Rk6j67WN0hxn0Mq/njErZo1d0HSf33is4f8ICI5fMLy5vYe0JtCtM5sYunNOaochSQ==, + } + engines: { node: '>= 12.20.55' } + hasBin: true + emoji-regex@10.6.0: resolution: { @@ -3966,6 +4574,20 @@ packages: integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==, } + es6-error@4.1.1: + resolution: + { + integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==, + } + + esbuild@0.25.12: + resolution: + { + integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==, + } + engines: { node: '>=18' } + hasBin: true + esbuild@0.27.7: resolution: { @@ -4208,6 +4830,14 @@ packages: integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==, } + extract-zip@2.0.1: + resolution: + { + integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==, + } + engines: { node: '>= 10.17.0' } + hasBin: true + fast-copy@4.0.3: resolution: { @@ -4288,6 +4918,12 @@ packages: integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==, } + fd-slicer@1.1.0: + resolution: + { + integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==, + } + fdir@6.5.0: resolution: { @@ -4423,6 +5059,13 @@ packages: } engines: { node: '>=14.14' } + fs-extra@8.1.0: + resolution: + { + integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==, + } + engines: { node: '>=6 <7 || >=8' } + fs.realpath@1.0.0: resolution: { @@ -4541,6 +5184,13 @@ packages: } engines: { node: '>= 0.4' } + get-stream@5.2.0: + resolution: + { + integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==, + } + engines: { node: '>=8' } + get-stream@6.0.1: resolution: { @@ -4596,6 +5246,13 @@ packages: } deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + global-agent@3.0.0: + resolution: + { + integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==, + } + engines: { node: '>=10.0' } + globals@14.0.0: resolution: { @@ -4617,6 +5274,13 @@ packages: } engines: { node: '>= 0.4' } + got@11.8.6: + resolution: + { + integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==, + } + engines: { node: '>=10.19.0' } + graceful-fs@4.2.11: resolution: { @@ -4779,6 +5443,12 @@ packages: integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==, } + http-cache-semantics@4.2.0: + resolution: + { + integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==, + } + http-errors@2.0.1: resolution: { @@ -4786,6 +5456,13 @@ packages: } engines: { node: '>= 0.8' } + http2-wrapper@1.0.3: + resolution: + { + integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==, + } + engines: { node: '>=10.19.0' } + https-proxy-agent@7.0.6: resolution: { @@ -5420,6 +6097,12 @@ packages: integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==, } + json-stringify-safe@5.0.1: + resolution: + { + integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==, + } + json5@2.2.3: resolution: { @@ -5428,6 +6111,12 @@ packages: engines: { node: '>=6' } hasBin: true + jsonfile@4.0.0: + resolution: + { + integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==, + } + jsonfile@6.2.1: resolution: { @@ -5827,6 +6516,13 @@ packages: integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==, } + lowercase-keys@2.0.0: + resolution: + { + integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==, + } + engines: { node: '>=8' } + lowlight@3.3.0: resolution: { @@ -5894,6 +6590,13 @@ packages: integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==, } + matcher@3.0.0: + resolution: + { + integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==, + } + engines: { node: '>=10' } + math-intrinsics@1.1.0: resolution: { @@ -6263,6 +6966,20 @@ packages: } engines: { node: '>=18' } + mimic-response@1.0.1: + resolution: + { + integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==, + } + engines: { node: '>=4' } + + mimic-response@3.1.0: + resolution: + { + integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==, + } + engines: { node: '>=10' } + min-indent@1.0.1: resolution: { @@ -6414,6 +7131,13 @@ packages: } engines: { node: '>=0.10.0' } + normalize-url@6.1.0: + resolution: + { + integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==, + } + engines: { node: '>=10' } + npm-run-path@4.0.1: resolution: { @@ -6577,6 +7301,13 @@ packages: } engines: { node: '>= 0.4' } + p-cancelable@2.1.1: + resolution: + { + integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==, + } + engines: { node: '>=8' } + p-limit@3.1.0: resolution: { @@ -6695,6 +7426,12 @@ packages: integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==, } + pend@1.2.0: + resolution: + { + integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==, + } + picocolors@1.1.1: resolution: { @@ -6883,6 +7620,13 @@ packages: integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==, } + progress@2.0.3: + resolution: + { + integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==, + } + engines: { node: '>=0.4.0' } + prompts@2.4.2: resolution: { @@ -6941,6 +7685,13 @@ packages: integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==, } + quick-lru@5.1.1: + resolution: + { + integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==, + } + engines: { node: '>=10' } + range-parser@1.2.1: resolution: { @@ -7232,6 +7983,12 @@ packages: integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==, } + resolve-alpn@1.2.1: + resolution: + { + integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==, + } + resolve-from@4.0.0: resolution: { @@ -7253,6 +8010,12 @@ packages: engines: { node: '>= 0.4' } hasBin: true + responselike@2.0.1: + resolution: + { + integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==, + } + restore-cursor@5.1.0: resolution: { @@ -7287,6 +8050,13 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + roarr@2.15.4: + resolution: + { + integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==, + } + engines: { node: '>=8.0' } + rolldown@1.0.0: resolution: { @@ -7295,6 +8065,14 @@ packages: engines: { node: ^20.19.0 || >=22.12.0 } hasBin: true + rollup@4.60.4: + resolution: + { + integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==, + } + engines: { node: '>=18.0.0', npm: '>=8.0.0' } + hasBin: true + router@2.2.0: resolution: { @@ -7412,6 +8190,12 @@ packages: integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==, } + semver-compare@1.0.0: + resolution: + { + integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==, + } + semver@6.3.1: resolution: { @@ -7442,6 +8226,13 @@ packages: } engines: { node: '>= 18' } + serialize-error@7.0.1: + resolution: + { + integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==, + } + engines: { node: '>=10' } + serve-static@2.2.1: resolution: { @@ -7628,6 +8419,12 @@ packages: integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==, } + sprintf-js@1.1.3: + resolution: + { + integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==, + } + stackback@0.0.2: resolution: { @@ -7852,6 +8649,13 @@ packages: peerDependencies: postcss: '>=8.5.10' + sumchecker@3.0.1: + resolution: + { + integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==, + } + engines: { node: '>= 8.0' } + superagent@10.3.0: resolution: { @@ -8145,6 +8949,13 @@ packages: } engines: { node: '>= 0.8.0' } + type-fest@0.13.1: + resolution: + { + integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==, + } + engines: { node: '>=10' } + type-fest@5.6.0: resolution: { @@ -8228,6 +9039,12 @@ packages: integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==, } + undici-types@6.21.0: + resolution: + { + integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==, + } + undici-types@7.21.0: resolution: { @@ -8290,6 +9107,13 @@ packages: integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==, } + universalify@0.1.2: + resolution: + { + integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==, + } + engines: { node: '>= 4.0.0' } + universalify@2.0.1: resolution: { @@ -8435,6 +9259,49 @@ packages: integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==, } + vite@7.3.3: + resolution: + { + integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==, + } + engines: { node: ^20.19.0 || >=22.12.0 } + hasBin: true + 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 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vite@8.0.12: resolution: { @@ -8730,6 +9597,12 @@ packages: } engines: { node: '>=12' } + yauzl@2.10.0: + resolution: + { + integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==, + } + yocto-queue@0.1.0: resolution: { @@ -8943,6 +9816,8 @@ snapshots: '@babel/helper-plugin-utils@7.28.6': {} + '@babel/helper-plugin-utils@7.29.7': {} + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -8984,6 +9859,11 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -9123,6 +10003,20 @@ snapshots: dependencies: '@noble/ciphers': 1.3.0 + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -9139,81 +10033,159 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.25.12': + optional: true + '@esbuild/aix-ppc64@0.27.7': optional: true + '@esbuild/android-arm64@0.25.12': + optional: true + '@esbuild/android-arm64@0.27.7': optional: true + '@esbuild/android-arm@0.25.12': + optional: true + '@esbuild/android-arm@0.27.7': optional: true + '@esbuild/android-x64@0.25.12': + optional: true + '@esbuild/android-x64@0.27.7': optional: true + '@esbuild/darwin-arm64@0.25.12': + optional: true + '@esbuild/darwin-arm64@0.27.7': optional: true + '@esbuild/darwin-x64@0.25.12': + optional: true + '@esbuild/darwin-x64@0.27.7': optional: true + '@esbuild/freebsd-arm64@0.25.12': + optional: true + '@esbuild/freebsd-arm64@0.27.7': optional: true + '@esbuild/freebsd-x64@0.25.12': + optional: true + '@esbuild/freebsd-x64@0.27.7': optional: true + '@esbuild/linux-arm64@0.25.12': + optional: true + '@esbuild/linux-arm64@0.27.7': optional: true + '@esbuild/linux-arm@0.25.12': + optional: true + '@esbuild/linux-arm@0.27.7': optional: true + '@esbuild/linux-ia32@0.25.12': + optional: true + '@esbuild/linux-ia32@0.27.7': optional: true + '@esbuild/linux-loong64@0.25.12': + optional: true + '@esbuild/linux-loong64@0.27.7': optional: true + '@esbuild/linux-mips64el@0.25.12': + optional: true + '@esbuild/linux-mips64el@0.27.7': optional: true + '@esbuild/linux-ppc64@0.25.12': + optional: true + '@esbuild/linux-ppc64@0.27.7': optional: true + '@esbuild/linux-riscv64@0.25.12': + optional: true + '@esbuild/linux-riscv64@0.27.7': optional: true + '@esbuild/linux-s390x@0.25.12': + optional: true + '@esbuild/linux-s390x@0.27.7': optional: true + '@esbuild/linux-x64@0.25.12': + optional: true + '@esbuild/linux-x64@0.27.7': optional: true + '@esbuild/netbsd-arm64@0.25.12': + optional: true + '@esbuild/netbsd-arm64@0.27.7': optional: true + '@esbuild/netbsd-x64@0.25.12': + optional: true + '@esbuild/netbsd-x64@0.27.7': optional: true + '@esbuild/openbsd-arm64@0.25.12': + optional: true + '@esbuild/openbsd-arm64@0.27.7': optional: true + '@esbuild/openbsd-x64@0.25.12': + optional: true + '@esbuild/openbsd-x64@0.27.7': optional: true + '@esbuild/openharmony-arm64@0.25.12': + optional: true + '@esbuild/openharmony-arm64@0.27.7': optional: true + '@esbuild/sunos-x64@0.25.12': + optional: true + '@esbuild/sunos-x64@0.27.7': optional: true + '@esbuild/win32-arm64@0.25.12': + optional: true + '@esbuild/win32-arm64@0.27.7': optional: true + '@esbuild/win32-ia32@0.25.12': + optional: true + '@esbuild/win32-ia32@0.27.7': optional: true + '@esbuild/win32-x64@0.25.12': + optional: true + '@esbuild/win32-x64@0.27.7': optional: true @@ -9642,6 +10614,81 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.3': {} + '@rollup/rollup-android-arm-eabi@4.60.4': + optional: true + + '@rollup/rollup-android-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.4': + optional: true + + '@rollup/rollup-darwin-x64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.4': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.4': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.4': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.4': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.4': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.4': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.4': + optional: true + '@scarf/scarf@1.4.0': {} '@sec-ant/readable-stream@0.4.1': {} @@ -9652,12 +10699,18 @@ snapshots: dependencies: '@simple-git/args-pathspec': 1.0.3 + '@sindresorhus/is@4.6.0': {} + '@sindresorhus/merge-streams@4.0.0': {} '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + '@tailwindcss/node@4.3.0': dependencies: '@jridgewell/remapping': 2.3.5 @@ -9819,6 +10872,13 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 25.7.0 + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.2.0 + '@types/keyv': 3.1.4 + '@types/node': 25.7.0 + '@types/responselike': 1.0.3 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -9906,6 +10966,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/http-cache-semantics@4.2.0': {} + '@types/http-errors@2.0.5': {} '@types/json-schema@7.0.15': {} @@ -9915,6 +10977,10 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 25.7.0 + '@types/keyv@3.1.4': + dependencies: + '@types/node': 25.7.0 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 @@ -9931,6 +10997,10 @@ snapshots: '@types/node@14.18.63': {} + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + '@types/node@25.7.0': dependencies: undici-types: 7.21.0 @@ -9954,6 +11024,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/responselike@1.0.3': + dependencies: + '@types/node': 25.7.0 + '@types/sanitize-html@2.16.1': dependencies: htmlparser2: 10.1.0 @@ -10007,6 +11081,11 @@ snapshots: dependencies: '@types/node': 25.7.0 + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 25.7.0 + optional: true + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.38.0(jiti@2.7.0))(typescript@6.0.3))(eslint@9.38.0(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -10135,6 +11214,15 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 + '@vitest/mocker@4.1.6(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + msw: 2.14.3(@types/node@25.7.0)(typescript@6.0.3) + vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0) + '@vitest/mocker@4.1.6(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.7)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.6 @@ -10412,6 +11500,9 @@ snapshots: transitivePeerDependencies: - supports-color + boolean@3.2.0: + optional: true + brace-expansion@5.0.5: dependencies: balanced-match: 4.0.4 @@ -10453,6 +11544,20 @@ snapshots: bytes@3.1.2: {} + cac@6.7.14: {} + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -10526,6 +11631,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + clsx@2.1.1: {} code-block-writer@13.0.3: {} @@ -10742,6 +11851,10 @@ snapshots: dependencies: character-entities: 2.0.2 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + dedent@1.7.2: {} deep-is@0.1.4: {} @@ -10755,6 +11868,8 @@ snapshots: bundle-name: 4.1.0 default-browser-id: 5.0.1 + defer-to-connect@2.0.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -10779,6 +11894,9 @@ snapshots: detect-node-es@1.1.0: {} + detect-node@2.1.0: + optional: true + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -10862,6 +11980,26 @@ snapshots: electron-to-chromium@1.5.351: {} + electron-vite@5.0.0(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.0) + cac: 6.7.14 + esbuild: 0.25.12 + magic-string: 0.30.21 + picocolors: 1.1.1 + vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0) + transitivePeerDependencies: + - supports-color + + electron@39.8.10: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 22.19.19 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + emoji-regex@10.6.0: {} emoji-regex@8.0.0: {} @@ -10996,6 +12134,38 @@ snapshots: es-toolkit@1.45.1: {} + es6-error@4.1.1: + optional: true + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.7: optionalDependencies: '@esbuild/aix-ppc64': 0.27.7 @@ -11244,6 +12414,16 @@ snapshots: extend@3.0.2: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + fast-copy@4.0.3: {} fast-csv@4.3.6: @@ -11285,6 +12465,10 @@ snapshots: dependencies: reusify: 1.1.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.4): optionalDependencies: picomatch: 4.0.4 @@ -11374,6 +12558,12 @@ snapshots: jsonfile: 6.2.1 universalify: 2.0.1 + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + fs.realpath@1.0.0: {} fsevents@2.3.2: @@ -11436,6 +12626,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + get-stream@6.0.1: {} get-stream@9.0.1: @@ -11479,6 +12673,16 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.8.0 + serialize-error: 7.0.1 + optional: true + globals@14.0.0: {} globalthis@1.0.4: @@ -11488,6 +12692,20 @@ snapshots: gopd@1.2.0: {} + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + graceful-fs@4.2.11: {} graphql@16.13.2: {} @@ -11592,6 +12810,8 @@ snapshots: domutils: 3.2.2 entities: 7.0.1 + http-cache-semantics@4.2.0: {} + http-errors@2.0.1: dependencies: depd: 2.0.0 @@ -11600,6 +12820,11 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + http2-wrapper@1.0.3: + dependencies: + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 + https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.4 @@ -11913,8 +13138,15 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: + optional: true + json5@2.2.3: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + jsonfile@6.2.1: dependencies: universalify: 2.0.1 @@ -12129,6 +13361,8 @@ snapshots: option: 0.2.4 underscore: 1.13.8 + lowercase-keys@2.0.0: {} + lowlight@3.3.0: dependencies: '@types/hast': 3.0.4 @@ -12176,6 +13410,11 @@ snapshots: markdown-table@3.0.4: {} + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + math-intrinsics@1.1.0: {} mdast-util-find-and-replace@3.0.2: @@ -12559,6 +13798,10 @@ snapshots: mimic-function@5.0.1: {} + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + min-indent@1.0.1: {} minimatch@10.2.5: @@ -12642,6 +13885,8 @@ snapshots: normalize-path@3.0.0: {} + normalize-url@6.1.0: {} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -12753,6 +13998,8 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-cancelable@2.1.1: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -12810,6 +14057,8 @@ snapshots: pathe@2.0.3: {} + pend@1.2.0: {} + picocolors@1.1.1: {} picomatch@2.3.2: {} @@ -12925,6 +14174,8 @@ snapshots: process-warning@5.0.0: {} + progress@2.0.3: {} + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -12958,6 +14209,8 @@ snapshots: quick-format-unescaped@4.0.4: {} + quick-lru@5.1.1: {} + range-parser@1.2.1: {} raw-body@3.0.2: @@ -13204,6 +14457,8 @@ snapshots: resize-observer-polyfill@1.5.1: {} + resolve-alpn@1.2.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -13217,6 +14472,10 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + restore-cursor@5.1.0: dependencies: onetime: 7.0.0 @@ -13232,6 +14491,16 @@ snapshots: dependencies: glob: 7.2.3 + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + rolldown@1.0.0: dependencies: '@oxc-project/types': 0.129.0 @@ -13253,6 +14522,37 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0 '@rolldown/binding-win32-x64-msvc': 1.0.0 + rollup@4.60.4: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.4 + '@rollup/rollup-android-arm64': 4.60.4 + '@rollup/rollup-darwin-arm64': 4.60.4 + '@rollup/rollup-darwin-x64': 4.60.4 + '@rollup/rollup-freebsd-arm64': 4.60.4 + '@rollup/rollup-freebsd-x64': 4.60.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.4 + '@rollup/rollup-linux-arm-musleabihf': 4.60.4 + '@rollup/rollup-linux-arm64-gnu': 4.60.4 + '@rollup/rollup-linux-arm64-musl': 4.60.4 + '@rollup/rollup-linux-loong64-gnu': 4.60.4 + '@rollup/rollup-linux-loong64-musl': 4.60.4 + '@rollup/rollup-linux-ppc64-gnu': 4.60.4 + '@rollup/rollup-linux-ppc64-musl': 4.60.4 + '@rollup/rollup-linux-riscv64-gnu': 4.60.4 + '@rollup/rollup-linux-riscv64-musl': 4.60.4 + '@rollup/rollup-linux-s390x-gnu': 4.60.4 + '@rollup/rollup-linux-x64-gnu': 4.60.4 + '@rollup/rollup-linux-x64-musl': 4.60.4 + '@rollup/rollup-openbsd-x64': 4.60.4 + '@rollup/rollup-openharmony-arm64': 4.60.4 + '@rollup/rollup-win32-arm64-msvc': 4.60.4 + '@rollup/rollup-win32-ia32-msvc': 4.60.4 + '@rollup/rollup-win32-x64-gnu': 4.60.4 + '@rollup/rollup-win32-x64-msvc': 4.60.4 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.3 @@ -13331,6 +14631,9 @@ snapshots: secure-json-parse@4.1.0: {} + semver-compare@1.0.0: + optional: true + semver@6.3.1: {} semver@7.7.4: {} @@ -13353,6 +14656,11 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + serve-static@2.2.1: dependencies: encodeurl: 2.0.0 @@ -13511,6 +14819,9 @@ snapshots: sprintf-js@1.0.3: {} + sprintf-js@1.1.3: + optional: true + stackback@0.0.2: {} statuses@2.0.2: {} @@ -13650,6 +14961,12 @@ snapshots: dependencies: postcss: 8.5.14 + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + superagent@10.3.0: dependencies: component-emitter: 1.3.1 @@ -13819,6 +15136,9 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.13.1: + optional: true + type-fest@5.6.0: dependencies: tagged-tag: 1.0.0 @@ -13882,6 +15202,8 @@ snapshots: underscore@1.13.8: {} + undici-types@6.21.0: {} + undici-types@7.21.0: {} undici@7.25.0: {} @@ -13926,6 +15248,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universalify@0.1.2: {} + universalify@2.0.1: {} unpdf@1.6.2: {} @@ -14015,6 +15339,23 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0): + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 + rollup: 4.60.4 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.7.0 + fsevents: 2.3.3 + jiti: 2.7.0 + lightningcss: 1.32.0 + sugarss: 5.0.1(postcss@8.5.14) + tsx: 4.21.0 + yaml: 2.9.0 + vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.7)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 @@ -14031,6 +15372,35 @@ snapshots: tsx: 4.21.0 yaml: 2.9.0 + vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@29.1.1(@noble/hashes@1.8.0))(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.7.0 + '@vitest/coverage-v8': 4.1.6(vitest@4.1.6) + jsdom: 29.1.1(@noble/hashes@1.8.0) + transitivePeerDependencies: + - msw + vitest@4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@29.1.1(@noble/hashes@1.8.0))(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.7)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.6 @@ -14187,6 +15557,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yocto-queue@0.1.0: {} yocto-spinner@1.2.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dd76322d..cf85cbe8 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - - "server" - - "web" - - "shared" - - "cli" - - "mcp" + - 'server' + - 'web' + - 'shared' + - 'cli' + - 'mcp' + - 'desktop' diff --git a/server/src/config/env.ts b/server/src/config/env.ts index f4476517..1bcfdf40 100644 --- a/server/src/config/env.ts +++ b/server/src/config/env.ts @@ -49,6 +49,9 @@ export const envSchema = z.object({ /** HTTP port the server listens on */ PORT: portSchema.default(3001), + /** Optional HTTP host/bind address */ + HOST: z.string().optional(), + /** Node environment: development | production | test */ NODE_ENV: z.enum(['development', 'production', 'test']).optional().default('development'), diff --git a/server/src/index.ts b/server/src/index.ts index ba9ffdc9..d93d1cb2 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -133,6 +133,7 @@ if (trustProxy !== undefined && trustProxy !== '') { } const PORT = process.env.PORT || 3001; +const HOST = process.env.HOST?.trim() || undefined; // ============================================ // Performance: ETag Generation @@ -1052,7 +1053,7 @@ process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); process.on('SIGINT', () => gracefulShutdown('SIGINT')); // Start server -server.listen(PORT, () => { +server.listen(Number(PORT), HOST, () => { const authStatus = getAuthStatus(); const localhostInfo = authStatus.localhostBypass ? `, localhost bypass [${authStatus.localhostRole}]` @@ -1065,8 +1066,9 @@ server.listen(PORT, () => { log.info( { port: PORT, - api: `http://localhost:${PORT}`, - ws: `ws://localhost:${PORT}/ws`, + host: HOST || 'default', + api: `http://${HOST || 'localhost'}:${PORT}`, + ws: `ws://${HOST || 'localhost'}:${PORT}/ws`, auth: authLine, cors: corsLine, helmet: true, diff --git a/web/src/__tests__/mantine-ui-primitives.test.tsx b/web/src/__tests__/mantine-ui-primitives.test.tsx index 84de6556..2731e58c 100644 --- a/web/src/__tests__/mantine-ui-primitives.test.tsx +++ b/web/src/__tests__/mantine-ui-primitives.test.tsx @@ -256,6 +256,28 @@ describe('Mantine-backed shared UI primitives', () => { expect(screen.getByTestId('scroll-area').getAttribute('data-slot')).toBe('scroll-area'); }); + it('applies legacy text field classes to the native input slot', () => { + renderWithProviders( +
+ + +