diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..137f7cbb --- /dev/null +++ b/.env.example @@ -0,0 +1,30 @@ +# Readied — Environment Variables +# Copy this file to .env and fill in the values. +# The desktop app runs fully offline — these are only needed for the API and web app. + +# ─── API (Cloudflare Workers) ─────────────────────────────── +# Set via `wrangler secret put ` for deployed environments. +# For local dev, create packages/api/.dev.vars with these values. + +TURSO_DATABASE_URL=libsql://your-db.turso.io +TURSO_AUTH_TOKEN=your_token_here +JWT_SECRET=your_secret_here # openssl rand -base64 32 +RESEND_API_KEY=re_your_key_here # Resend email service +SITE_URL=https://readied.app + +# ─── Stripe ───────────────────────────────────────────────── +STRIPE_SECRET_KEY=sk_test_your_key_here +STRIPE_WEBHOOK_SECRET=whsec_your_secret_here +STRIPE_PRICE_MONTHLY=price_your_monthly_id_here +STRIPE_PRICE_ANNUAL=price_your_annual_id_here + +# ─── Admin ────────────────────────────────────────────────── +ADMIN_TOKEN=your_admin_token_here # Token for /admin endpoints + +# ─── GitHub Actions (repo secrets) ────────────────────────── +# GH_TOKEN # PAT with repo scope (releases, PRs) +# CSC_LINK # macOS code signing cert (base64) +# CSC_KEY_PASSWORD # macOS cert password +# APPLE_ID # Apple notarization +# APPLE_APP_SPECIFIC_PASSWORD # Apple app-specific password +# APPLE_TEAM_ID # Apple Developer Team ID diff --git a/.github/workflows/auto-tag.yml b/.github/workflows/auto-tag.yml deleted file mode 100644 index dd43662d..00000000 --- a/.github/workflows/auto-tag.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Auto Tag & Sync - -on: - pull_request: - types: [closed] - branches: [main] - -permissions: - contents: write - -jobs: - tag-and-sync: - if: github.event.pull_request.merged == true && startsWith(github.event.pull_request.head.ref, 'release/') - runs-on: ubuntu-latest - - steps: - - name: Checkout main - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GH_TOKEN }} - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Read version from package.json - id: version - run: | - VERSION=$(node -p "require('./package.json').version") - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" - - - name: Check tag doesn't already exist - id: check - env: - TAG_VERSION: ${{ steps.version.outputs.version }} - run: | - if git rev-parse "v${TAG_VERSION}" >/dev/null 2>&1; then - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create and push tag - if: steps.check.outputs.exists == 'false' - env: - TAG_NAME: ${{ steps.version.outputs.tag }} - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git tag -a "${TAG_NAME}" -m "Release ${TAG_NAME}" - git push origin "${TAG_NAME}" - - - name: Merge main → develop - run: | - git fetch origin develop - git checkout develop - git merge main --no-edit - git push origin develop diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ac8ccd8..e175e458 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -37,7 +37,7 @@ jobs: fetch-depth: 0 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v4 @@ -126,9 +126,9 @@ jobs: steps: - name: Publish GitHub Release env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} TAG_NAME: ${{ github.ref_name }} - run: gh release edit "$TAG_NAME" --draft=false --repo "${{ github.repository }}" + run: gh release edit "$TAG_NAME" --draft=false --repo "$GITHUB_REPOSITORY" tweet: needs: publish @@ -159,11 +159,12 @@ jobs: steps: - name: Create sync PR env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + TAG_NAME: ${{ github.ref_name }} run: | gh pr create \ --base develop \ --head main \ - --title "chore: sync release ${{ github.ref_name }} back to develop" \ + --title "chore: sync release $TAG_NAME back to develop" \ --body "Auto sync of release commit and changelog." \ - --repo "${{ github.repository }}" + --repo "$GITHUB_REPOSITORY" || echo "PR already exists or cannot be created" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2df5e36c..07c03228 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@v4 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v4 @@ -51,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -83,7 +83,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -119,7 +119,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -146,7 +146,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -174,7 +174,7 @@ jobs: if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: node-version: ${{ env.NODE_VERSION }} @@ -210,8 +210,9 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'pull_request' permissions: + contents: read pull-requests: write steps: - - uses: actions/labeler@v5 + - uses: actions/labeler@v6 with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 81cc9b2b..a3c0723d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,12 +23,12 @@ jobs: uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: javascript-typescript - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/deploy-api.yml b/.github/workflows/deploy-api.yml index cf55372a..d549e616 100644 --- a/.github/workflows/deploy-api.yml +++ b/.github/workflows/deploy-api.yml @@ -24,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: @@ -46,7 +46,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 26527dc9..fde3eb10 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,7 +15,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v5 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 25c7e392..92bc6db5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: token: ${{ secrets.GH_TOKEN }} - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - name: Setup Node.js uses: actions/setup-node@v4 diff --git a/.husky/pre-push b/.husky/pre-push index cf63d261..0db91f05 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1 +1 @@ -pnpm test && pnpm typecheck +pnpm typecheck diff --git a/CLAUDE.md b/CLAUDE.md index b04b3f64..35027589 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -73,9 +73,9 @@ Pattern for workspace packages with native deps: ## Type Version Alignment -`@types/react` is pinned to `18.3.27` via `pnpm.overrides` in root `package.json`. This prevents type mismatches when packages like `lucide-react` resolve a different `@types/react` version than the app uses. +Each app manages its own `@types/react` version: `apps/desktop` uses React 18 types and `apps/web` uses React 19 types. Global overrides were removed to prevent cross-app type conflicts. -**If you see `'X' cannot be used as a JSX component` errors:** Check that `pnpm.overrides` in root `package.json` still pins `@types/react` to match the React version used by `apps/desktop`. +**If you see `'X' cannot be used as a JSX component` errors:** Check that each app's `package.json` pins `@types/react` to match its React version. ## Testing diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 5e214df3..01a6468b 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -53,7 +53,7 @@ "better-sqlite3": "^11.7.0", "cross-fetch": "^4.1.0", "diff": "^8.0.2", - "electron-updater": "^6.6.2", + "electron-updater": "^6.8.3", "highlight.js": "^11.11.1", "isomorphic-git": "^1.36.1", "lucide-react": "^0.562.0", @@ -75,7 +75,7 @@ "@types/react-dom": "^18.2.25", "@types/turndown": "^5.0.6", "@vitejs/plugin-react": "^4.2.1", - "electron": "^39.8.5", + "electron": "^35.7.5", "electron-builder": "^26.0.12", "electron-devtools-installer": "^4.0.0", "electron-vite": "^2.1.0", diff --git a/apps/desktop/src/main/ai/ipc-ai.ts b/apps/desktop/src/main/ai/ipc-ai.ts index f545a005..31abecf2 100644 --- a/apps/desktop/src/main/ai/ipc-ai.ts +++ b/apps/desktop/src/main/ai/ipc-ai.ts @@ -86,7 +86,7 @@ export function registerAIHandlers(service: AIService, toolRegistry: ToolRegistr } activeHandles.get(windowId)!.set(handle.requestId, handle); - consumeStream(event.sender, handle); + void consumeStream(event.sender, handle); return { requestId: handle.requestId }; } diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 635d4e86..dfc4548a 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -12,7 +12,7 @@ initSentry(); import { join, normalize, basename } from 'path'; import { readFile, writeFile, unlink, mkdir, rm, readdir, stat, rename } from 'fs/promises'; import { existsSync, readFileSync, writeFileSync } from 'fs'; -import { exec } from 'child_process'; +import { execFile } from 'child_process'; import { app, BrowserWindow, ipcMain, dialog, shell, protocol, net, nativeTheme } from 'electron'; import { autoUpdater } from 'electron-updater'; // electron-devtools-installer is imported dynamically below (dev only) @@ -317,10 +317,10 @@ function createWindow(): void { // Load renderer if (process.env.NODE_ENV === 'development' && process.env.ELECTRON_RENDERER_URL) { - mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); + void mainWindow.loadURL(process.env.ELECTRON_RENDERER_URL); mainWindow.webContents.openDevTools(); } else { - mainWindow.loadFile(join(__dirname, '../renderer/index.html')); + void mainWindow.loadFile(join(__dirname, '../renderer/index.html')); } } @@ -354,9 +354,9 @@ function createNoteWindow(noteId: string, noteTitle: string): void { // Load renderer with note ID in query param const query = `?noteWindow=${encodeURIComponent(noteId)}`; if (process.env.NODE_ENV === 'development' && process.env.ELECTRON_RENDERER_URL) { - noteWindow.loadURL(`${process.env.ELECTRON_RENDERER_URL}${query}`); + void noteWindow.loadURL(`${process.env.ELECTRON_RENDERER_URL}${query}`); } else { - noteWindow.loadFile(join(__dirname, '../renderer/index.html'), { + void noteWindow.loadFile(join(__dirname, '../renderer/index.html'), { query: { noteWindow: noteId }, }); } @@ -405,9 +405,9 @@ function createSettingsWindow(): void { // Load settings page via query param (same index.html, different view) if (process.env.NODE_ENV === 'development' && process.env.ELECTRON_RENDERER_URL) { const settingsUrl = `${process.env.ELECTRON_RENDERER_URL}?view=settings`; - settingsWindow.loadURL(settingsUrl); + void settingsWindow.loadURL(settingsUrl); } else { - settingsWindow.loadFile(join(__dirname, '../renderer/index.html'), { + void settingsWindow.loadFile(join(__dirname, '../renderer/index.html'), { query: { view: 'settings' }, }); } @@ -1245,6 +1245,34 @@ function registerDataHandlers(): void { return result; }); + // Export single note to file + ipcMain.handle( + 'data:exportNote', + async (_event: Electron.IpcMainInvokeEvent, content: string, suggestedName: string) => { + const safeName = suggestedName.replace(/[^a-zA-Z0-9\s-]/g, '').substring(0, 80) || 'note'; + const { filePath, canceled } = await dialog.showSaveDialog({ + title: 'Export Note', + defaultPath: join(app.getPath('documents'), `${safeName}.md`), + buttonLabel: 'Export', + filters: [{ name: 'Markdown', extensions: ['md'] }], + }); + + if (canceled || !filePath) { + return { success: false, error: 'Export cancelled' }; + } + + try { + writeFileSync(filePath, content, 'utf-8'); + return { success: true, path: filePath }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : 'Failed to write file', + }; + } + } + ); + // Import notes ipcMain.handle('data:import', async () => { // Show folder selection dialog @@ -1306,7 +1334,7 @@ function registerDataHandlers(): void { // Open data folder in system file manager ipcMain.handle('data:openFolder', async () => { - shell.openPath(paths.root); + void shell.openPath(paths.root); return { success: true }; }); } @@ -1872,7 +1900,7 @@ function registerAuthSyncHandlers(): void { ipcMain.handle('subscription:openPortal', async (_event, returnUrl: string) => { try { const { url } = await client.createPortalSession(returnUrl); - shell.openExternal(url); + void shell.openExternal(url); return { success: true }; } catch (error) { return { @@ -1885,7 +1913,7 @@ function registerAuthSyncHandlers(): void { // Open checkout (placeholder - opens pricing page) ipcMain.handle('subscription:openCheckout', async () => { try { - shell.openExternal('https://readied.app/pricing'); + void shell.openExternal('https://readied.app/pricing'); return { success: true }; } catch (error) { return { @@ -2279,20 +2307,33 @@ function registerPluginDiscoveryHandlers(): void { await mkdir(tmpDir, { recursive: true }); await new Promise((resolve, reject) => { - let cmd: string; + const cb = (error: Error | null) => { + if (error) reject(error); + else resolve(); + }; if (fileName.endsWith('.zip')) { if (process.platform === 'win32') { - cmd = `powershell -command "Expand-Archive -Force '${archivePath}' '${tmpDir}'"`; + execFile( + 'powershell', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Expand-Archive', + '-Force', + '-Path', + archivePath, + '-DestinationPath', + tmpDir, + ], + cb + ); } else { - cmd = `unzip -o "${archivePath}" -d "${tmpDir}"`; + execFile('unzip', ['-o', archivePath, '-d', tmpDir], cb); } } else { - cmd = `tar -xzf "${archivePath}" -C "${tmpDir}"`; + execFile('tar', ['-xzf', archivePath, '-C', tmpDir], cb); } - exec(cmd, error => { - if (error) reject(error); - else resolve(); - }); }); // Find the manifest.json — could be at root or one level deep @@ -2322,8 +2363,23 @@ function registerPluginDiscoveryHandlers(): void { return { success: false, error: 'Invalid manifest: missing id or name' }; } - // Move to final destination + // Validate plugin ID - only allow alphanumeric, hyphens, underscores + if (!/^[a-zA-Z0-9_-]+$/.test(manifest.id)) { + await rm(tmpDir, { recursive: true, force: true }); + return { + success: false, + error: 'Invalid plugin ID: must be alphanumeric with hyphens/underscores only', + }; + } + + // Verify path doesn't escape plugins directory const destDir = join(paths.plugins, manifest.id); + if (!normalize(destDir).startsWith(normalize(paths.plugins))) { + await rm(tmpDir, { recursive: true, force: true }); + return { success: false, error: 'Invalid plugin ID: path traversal detected' }; + } + + // Move to final destination if (existsSync(destDir)) { await rm(destDir, { recursive: true, force: true }); } @@ -2341,6 +2397,135 @@ function registerPluginDiscoveryHandlers(): void { } }); + // Install plugin from a remote URL (marketplace download) + ipcMain.handle('plugins:installFromUrl', async (_event, url: string, _pluginSlug: string) => { + // Safety: only allow https URLs + if (!url.startsWith('https://')) { + return { success: false, error: 'Only HTTPS URLs are allowed' }; + } + + // Ensure plugins dir exists + await mkdir(paths.plugins, { recursive: true }); + + // Download to a temp file inside the plugins dir + const tmpDir = join(paths.plugins, `__downloading_${Date.now()}`); + await mkdir(tmpDir, { recursive: true }); + + try { + const response = await net.fetch(url); + if (!response.ok) { + return { success: false, error: `Download failed: HTTP ${response.status}` }; + } + + // Limit download size to 50 MB + const MAX_PLUGIN_SIZE = 50 * 1024 * 1024; + const contentLength = response.headers.get('content-length'); + if (contentLength && parseInt(contentLength, 10) > MAX_PLUGIN_SIZE) { + return { success: false, error: 'Plugin archive exceeds maximum size of 50 MB' }; + } + + const buffer = Buffer.from(await response.arrayBuffer()); + if (buffer.byteLength > MAX_PLUGIN_SIZE) { + return { success: false, error: 'Plugin archive exceeds maximum size of 50 MB' }; + } + + // Determine archive type from URL or content-type + const lowerUrl = url.toLowerCase(); + const isZip = lowerUrl.endsWith('.zip') || lowerUrl.includes('.zip'); + const archiveExt = isZip ? '.zip' : '.tar.gz'; + const archivePath = join(tmpDir, `plugin${archiveExt}`); + await writeFile(archivePath, buffer); + + // Extract to a staging dir + const stageDir = join(tmpDir, 'extracted'); + await mkdir(stageDir, { recursive: true }); + + await new Promise((resolve, reject) => { + const cb = (error: Error | null) => { + if (error) reject(error); + else resolve(); + }; + if (isZip) { + if (process.platform === 'win32') { + execFile( + 'powershell', + [ + '-NoProfile', + '-NonInteractive', + '-Command', + 'Expand-Archive', + '-Force', + '-Path', + archivePath, + '-DestinationPath', + stageDir, + ], + cb + ); + } else { + execFile('unzip', ['-o', archivePath, '-d', stageDir], cb); + } + } else { + execFile('tar', ['-xzf', archivePath, '-C', stageDir], cb); + } + }); + + // Find manifest.json — could be at root or one level deep + const entries = await readdir(stageDir); + let pluginSourceDir = stageDir; + + if (entries.length === 1 && entries[0]) { + const candidatePath = join(stageDir, entries[0]); + const candidateStat = await stat(candidatePath); + if (candidateStat.isDirectory()) { + pluginSourceDir = candidatePath; + } + } + + // Validate: must have manifest.json + const manifestPath = join(pluginSourceDir, 'manifest.json'); + if (!existsSync(manifestPath)) { + return { success: false, error: 'No manifest.json found in downloaded archive' }; + } + + const manifestRaw = await readFile(manifestPath, 'utf-8'); + const manifest = JSON.parse(manifestRaw); + if (!manifest.id || !manifest.name) { + return { success: false, error: 'Invalid manifest: missing id or name' }; + } + + // Validate plugin ID - only allow alphanumeric, hyphens, underscores + if (!/^[a-zA-Z0-9_-]+$/.test(manifest.id)) { + return { + success: false, + error: 'Invalid plugin ID: must be alphanumeric with hyphens/underscores only', + }; + } + + // Verify path doesn't escape plugins directory + const destDir = join(paths.plugins, manifest.id); + if (!normalize(destDir).startsWith(normalize(paths.plugins))) { + return { success: false, error: 'Invalid plugin ID: path traversal detected' }; + } + + // Move to final destination + if (existsSync(destDir)) { + await rm(destDir, { recursive: true, force: true }); + } + + await rename(pluginSourceDir, destDir); + + return { success: true, pluginId: manifest.id, pluginName: manifest.name }; + } catch (error) { + return { success: false, error: String(error) }; + } finally { + // Always clean up temp dir + if (existsSync(tmpDir)) { + await rm(tmpDir, { recursive: true, force: true }).catch(() => {}); + } + } + }); + // Uninstall plugin (remove its directory) ipcMain.handle('plugins:uninstall', async (_event, pluginId: string) => { // Safety: only allow removing from the plugins directory, prevent path traversal @@ -2441,7 +2626,7 @@ function initAutoUpdater(): void { // Check for updates after a short delay setTimeout(() => { - autoUpdater.checkForUpdates(); + void autoUpdater.checkForUpdates(); }, 3000); } @@ -2650,7 +2835,7 @@ app } }; - initAuthSync(); + void initAuthSync(); log.info('All IPC handlers registered'); diff --git a/apps/desktop/src/main/services/apiClient.ts b/apps/desktop/src/main/services/apiClient.ts index 882c6c40..2460b5f7 100644 --- a/apps/desktop/src/main/services/apiClient.ts +++ b/apps/desktop/src/main/services/apiClient.ts @@ -517,7 +517,13 @@ export class ApiClient { wrappedCekRecovery?: string | null; kdfParams?: { algorithm: string; iterations: number; hash: string }; }> { - return this.request('/sync/keys'); + return this.request<{ + exists: boolean; + salt?: string; + wrappedCek?: string; + wrappedCekRecovery?: string | null; + kdfParams?: { algorithm: string; iterations: number; hash: string }; + }>('/sync/keys'); } /** @@ -529,7 +535,7 @@ export class ApiClient { wrappedCekRecovery?: string | null; kdfParams: { algorithm: string; iterations: number; hash: string }; }): Promise<{ success: boolean }> { - return this.request('/sync/keys', { + return this.request<{ success: boolean }>('/sync/keys', { method: 'POST', body: JSON.stringify(data), }); @@ -616,24 +622,34 @@ export class ApiClient { createdAt: string; }>; }> { - return this.request('/devices'); + return this.request<{ + devices: Array<{ + id: string; + deviceId: string; + name: string | null; + platform: string | null; + isCurrent: boolean; + lastSeenAt: string; + createdAt: string; + }>; + }>('/devices'); } async renameDevice(deviceId: string, name: string): Promise<{ success: boolean }> { - return this.request(`/devices/${deviceId}`, { + return this.request<{ success: boolean }>(`/devices/${deviceId}`, { method: 'PATCH', body: JSON.stringify({ name }), }); } async revokeDevice(deviceId: string): Promise<{ success: boolean }> { - return this.request(`/devices/${deviceId}`, { + return this.request<{ success: boolean }>(`/devices/${deviceId}`, { method: 'DELETE', }); } async revokeOtherDevices(): Promise<{ success: boolean; revokedCount: number }> { - return this.request('/devices/revoke-others', { + return this.request<{ success: boolean; revokedCount: number }>('/devices/revoke-others', { method: 'POST', }); } diff --git a/apps/desktop/src/main/services/encryptionService.ts b/apps/desktop/src/main/services/encryptionService.ts index 02c806c0..39287b03 100644 --- a/apps/desktop/src/main/services/encryptionService.ts +++ b/apps/desktop/src/main/services/encryptionService.ts @@ -194,6 +194,10 @@ export class EncryptionService { * Unlock encryption using recovery key + server data. */ async unlockWithRecoveryKey(recoveryKeyHex: string, wrappedCekRecovery: string): Promise { + // Validate hex string before parsing — reject malformed input early + if (!/^[0-9a-fA-F]+$/.test(recoveryKeyHex) || recoveryKeyHex.length !== KEY_LENGTH * 2) { + throw new Error('Invalid recovery key format — expected 64 hex characters'); + } const recoveryKeyBuf = Buffer.from(recoveryKeyHex, 'hex'); const wrappedBuf = Buffer.from(wrappedCekRecovery, 'base64'); @@ -338,6 +342,9 @@ export class EncryptionService { */ async importKey(keyHex: string): Promise { try { + if (!/^[0-9a-fA-F]+$/.test(keyHex) || keyHex.length !== KEY_LENGTH * 2) { + throw new Error(`Invalid key format — expected ${KEY_LENGTH * 2} hex characters`); + } this.key = Buffer.from(keyHex, 'hex'); await this.cacheCek(this.key); } catch (error) { @@ -404,8 +411,16 @@ export class EncryptionService { /** * Derive a key from passphrase using PBKDF2. + * Enforces a minimum iteration count to prevent downgrade attacks + * (e.g. a compromised server sending iterations: 1). */ private deriveKey(passphrase: string, salt: Buffer, params: KdfParams): Buffer { + const MIN_ITERATIONS = 100_000; + if (!Number.isInteger(params.iterations) || params.iterations < MIN_ITERATIONS) { + throw new Error( + `Unsafe KDF parameters: iterations must be >= ${MIN_ITERATIONS}, got ${params.iterations}` + ); + } return pbkdf2Sync(passphrase, salt, params.iterations, KEY_LENGTH, params.hash); } diff --git a/apps/desktop/src/main/services/syncService.ts b/apps/desktop/src/main/services/syncService.ts index 167225bb..86870c99 100644 --- a/apps/desktop/src/main/services/syncService.ts +++ b/apps/desktop/src/main/services/syncService.ts @@ -485,6 +485,12 @@ export class SyncService { // encrypt/decrypt note content. If not ready, the user needs to set up // or unlock their sync passphrase first. if (!this.encryptionService.isReady()) { + this.emitStatus({ + type: 'sync-error', + error: 'Encryption not ready. Set up a passphrase in Settings.', + isNetworkError: false, + consecutiveFailures: this.state.consecutiveFailures, + }); return { success: false, changesApplied: 0, diff --git a/apps/desktop/src/preload/index.ts b/apps/desktop/src/preload/index.ts index 208ae80e..d3d93704 100644 --- a/apps/desktop/src/preload/index.ts +++ b/apps/desktop/src/preload/index.ts @@ -395,6 +395,11 @@ export interface ReadiedAPI { restoreBackup: (backupPath: string) => Promise; /** Export notes to Markdown + JSON */ export: () => Promise; + /** Export a single note to a .md file via save dialog */ + exportNote: ( + content: string, + suggestedName: string + ) => Promise<{ success: boolean; path?: string; error?: string }>; /** Import notes from folder (Obsidian, Markdown, or Readied export) */ import: () => Promise; /** Get data directory paths */ @@ -853,6 +858,16 @@ export interface ReadiedAPI { pluginName?: string; error?: string; }>; + /** Install plugin from a remote URL (marketplace) */ + installFromUrl: ( + url: string, + slug: string + ) => Promise<{ + success: boolean; + pluginId?: string; + pluginName?: string; + error?: string; + }>; /** Uninstall a community plugin by ID */ uninstall: (pluginId: string) => Promise<{ success: boolean; error?: string }>; }; @@ -910,6 +925,8 @@ const api: ReadiedAPI = { listBackups: () => ipcRenderer.invoke('data:backups:list'), restoreBackup: path => ipcRenderer.invoke('data:backup:restore', path), export: () => ipcRenderer.invoke('data:export'), + exportNote: (content: string, suggestedName: string) => + ipcRenderer.invoke('data:exportNote', content, suggestedName), import: () => ipcRenderer.invoke('data:import'), paths: () => ipcRenderer.invoke('data:paths'), openFolder: () => ipcRenderer.invoke('data:openFolder'), @@ -928,16 +945,20 @@ const api: ReadiedAPI = { }, log: { debug: (message, context) => { - ipcRenderer.invoke('log:write', 'debug', message, context); + if (typeof message !== 'string') return; + void ipcRenderer.invoke('log:write', 'debug', message, context); }, info: (message, context) => { - ipcRenderer.invoke('log:write', 'info', message, context); + if (typeof message !== 'string') return; + void ipcRenderer.invoke('log:write', 'info', message, context); }, warn: (message, context) => { - ipcRenderer.invoke('log:write', 'warn', message, context); + if (typeof message !== 'string') return; + void ipcRenderer.invoke('log:write', 'warn', message, context); }, error: (message, context) => { - ipcRenderer.invoke('log:write', 'error', message, context); + if (typeof message !== 'string') return; + void ipcRenderer.invoke('log:write', 'error', message, context); }, getLogPath: () => ipcRenderer.invoke('log:getPath'), }, @@ -1003,7 +1024,9 @@ const api: ReadiedAPI = { }, settings: { broadcast: (settings: Record) => { - ipcRenderer.send('settings:changed', settings); + if (settings && typeof settings === 'object') { + ipcRenderer.send('settings:changed', settings); + } }, onSync: (callback: (settings: Record) => void) => { const handler = (_event: Electron.IpcRendererEvent, settings: Record) => { @@ -1178,6 +1201,18 @@ const api: ReadiedAPI = { requestReload: () => ipcRenderer.send('plugins:requestReload'), readInitScript: () => ipcRenderer.invoke('plugins:readInitScript'), install: () => ipcRenderer.invoke('plugins:install'), + installFromUrl: (url: string, slug: string) => { + // Validate URL is HTTPS before sending to main process + try { + const parsed = new URL(url); + if (parsed.protocol !== 'https:') { + return Promise.resolve({ success: false, error: 'Only HTTPS URLs are allowed' }); + } + } catch { + return Promise.resolve({ success: false, error: 'Invalid URL' }); + } + return ipcRenderer.invoke('plugins:installFromUrl', url, slug); + }, uninstall: (pluginId: string) => ipcRenderer.invoke('plugins:uninstall', pluginId), }, }; diff --git a/apps/desktop/src/renderer/App.tsx b/apps/desktop/src/renderer/App.tsx index 0c537ae9..10a2f441 100644 --- a/apps/desktop/src/renderer/App.tsx +++ b/apps/desktop/src/renderer/App.tsx @@ -25,6 +25,7 @@ import { } from '@readied/ai-core'; import { useStore } from 'zustand'; import type { NoteSnapshot, NoteStatus } from '../preload/index'; +import { UpdateBanner } from './components/UpdateBanner'; import { NoteList } from './components/NoteList'; import { NoteEditor } from './components/NoteEditor'; import { NoteWindow } from './components/NoteWindow'; @@ -35,7 +36,9 @@ import { AiPanel } from './components/ai/AiPanel'; import type { AiInitialCommand } from './components/ai/AiPanel'; import { LicenseProvider } from './contexts/LicenseContext'; import { ToastProvider, useToast } from './components/Toast'; +import { Toaster } from './ui/primitives'; import type { PluginLoadError } from './stores/pluginRuntimeStore'; +import { Welcome } from './components/Welcome'; import { ErrorBoundary } from './components/ErrorBoundary'; import { useNavigation, @@ -56,6 +59,7 @@ import { useRegisterAiCommands } from './hooks/useRegisterAiCommands'; import { useRegisterPluginAiCommands } from './hooks/useRegisterPluginAiCommands'; import { getEditorView, registry as commandRegistry } from './hooks/useCommandRegistry'; import { builtInPlugins } from './plugins'; +import { useEditorBufferStore } from './stores/editorBufferStore'; import { useEditorPreferencesStore } from './stores/editorPreferencesStore'; import { useTagColorsStore } from './stores/tagColorsStore'; import { usePerformanceMode } from './hooks/usePerformanceMode'; @@ -97,6 +101,11 @@ function NotesApp() { useThemeOverrides(); // Applies active theme tokens useCssVariables(); + // First-run onboarding + const [showWelcome, setShowWelcome] = useState( + () => !localStorage.getItem('readied-onboarding-done') + ); + // Restore saved plugin theme on startup const appearance = useSettingsStore(selectAppearance); const registeredThemeCount = useSyncExternalStore( @@ -134,12 +143,12 @@ function NotesApp() { // Load tag colors on mount (once) useEffect(() => { - useTagColorsStore.getState().loadColors(); + void useTagColorsStore.getState().loadColors(); }, []); // Load auth session on mount (once) useEffect(() => { - useAuthStore.getState().loadSession(); + void useAuthStore.getState().loadSession(); }, []); // Auto-resume sync on network reconnect @@ -446,7 +455,7 @@ function NotesApp() { // Find exact match (case-insensitive) const match = notes.find(n => n.title.toLowerCase() === title.toLowerCase()); if (match) { - handleSelectNote(match.id); + void handleSelectNote(match.id); } } // No-op if note doesn't exist (future: could show toast or create note) @@ -489,6 +498,25 @@ function NotesApp() { [selectedNote, updateNote, syncLinks, dataAPI] ); + // Keep a ref to handleUpdateNote so beforeunload always has the latest + const handleUpdateNoteRef = useRef(handleUpdateNote); + handleUpdateNoteRef.current = handleUpdateNote; + + // Flush pending saves before window close + useEffect(() => { + const handleBeforeUnload = () => { + const bufferState = useEditorBufferStore.getState(); + if (bufferState.isDirty && bufferState.noteId) { + // Fire the save — can't await in beforeunload, but the IPC call + // will be queued before the renderer is torn down + void handleUpdateNoteRef.current(bufferState.liveContent); + } + }; + + window.addEventListener('beforeunload', handleBeforeUnload); + return () => window.removeEventListener('beforeunload', handleBeforeUnload); + }, []); + // Update note title const handleUpdateTitle = useCallback( async (title: string) => { @@ -614,7 +642,7 @@ function NotesApp() { useRegisterAppCommands({ onNewNote: handleNewNote, onDuplicateNote: useCallback(() => { - if (selectedNote) handleDuplicateNote(selectedNote.id); + if (selectedNote) void handleDuplicateNote(selectedNote.id); }, [selectedNote, handleDuplicateNote]), onFocusSearch: useCallback(() => { const searchInput = document.querySelector('.search-input') as HTMLInputElement; @@ -719,7 +747,7 @@ function NotesApp() { // Load AI plugin config once on mount useEffect(() => { - window.readied.pluginConfig.getAll('readied-ai-assistant').then(config => { + void window.readied.pluginConfig.getAll('readied-ai-assistant').then(config => { aiConfigCache.current = config ?? {}; }); }, []); @@ -761,7 +789,7 @@ function NotesApp() { const pluginErrors = useStore(pluginRuntimeStore, s => s.errors); useEffect(() => { - pluginRuntimeStore.getState().init(); + void pluginRuntimeStore.getState().init(); }, []); const allPlugins = useMemo(() => [...builtInPlugins, ...discoveredPlugins], [discoveredPlugins]); @@ -822,10 +850,32 @@ function NotesApp() { }, [isCommandPaletteOpen, isAiPanelOpen, isGraphOpen, searchQuery, selectedNote, clearSearch]), }); + // Welcome screen completion handler + const handleWelcomeComplete = useCallback( + (createNote: boolean) => { + localStorage.setItem('readied-onboarding-done', 'true'); + setShowWelcome(false); + if (createNote) { + void handleNewNote(); + } + }, + [handleNewNote] + ); + + if (showWelcome) { + return ( + + + + + ); + } + return (
+
diff --git a/apps/desktop/src/renderer/analytics.ts b/apps/desktop/src/renderer/analytics.ts index ac8eae94..5e7dc712 100644 --- a/apps/desktop/src/renderer/analytics.ts +++ b/apps/desktop/src/renderer/analytics.ts @@ -82,7 +82,7 @@ export function track(name: string, properties?: Record): void // Try to flush immediately if online if (navigator.onLine) { - flush(); + void flush(); } } @@ -162,7 +162,7 @@ window.addEventListener('beforeunload', flush); // Periodic flush (every 30 seconds if online) setInterval(() => { if (navigator.onLine && eventQueue.length > 0) { - flush(); + void flush(); } }, 30000); diff --git a/apps/desktop/src/renderer/components/NoteEditor.tsx b/apps/desktop/src/renderer/components/NoteEditor.tsx index f96a3ca2..de049a41 100644 --- a/apps/desktop/src/renderer/components/NoteEditor.tsx +++ b/apps/desktop/src/renderer/components/NoteEditor.tsx @@ -1,9 +1,9 @@ -import { useRef, useCallback, useState, useEffect, lazy, Suspense } from 'react'; +import { useRef, useCallback, useState, useEffect, useMemo, lazy, Suspense } from 'react'; import { FileText, MoreVertical, Link2 } from 'lucide-react'; import { LayoutZone } from '@readied/plugin-api'; import type { NoteSnapshot, NoteStatus } from '../../preload/index'; import { useEditorPreferencesStore } from '../stores/editorPreferencesStore'; -import { useEditorBufferStore } from '../stores/editorBufferStore'; +import { useEditorBufferStore, selectIsDirty } from '../stores/editorBufferStore'; import { useShareStore, selectShareInfo } from '../stores/shareStore'; import { useScrollSync } from '../hooks/useScrollSync'; import { useManualTags } from '../hooks/useManualTags'; @@ -116,6 +116,47 @@ export function NoteEditor({ const setShared = useShareStore(s => s.setShared); const removeShared = useShareStore(s => s.removeShared); + // Save indicator state + const isDirty = useEditorBufferStore(selectIsDirty); + const [showSaved, setShowSaved] = useState(false); + const savedTimerRef = useRef(null); + + // Show "Saved" briefly after onUpdate fires (isDirty goes false) + const prevDirtyRef = useRef(false); + const trackedNoteIdRef = useRef(note?.id); + + // Reset dirty tracking when switching notes to avoid false "Saved" flash + useEffect(() => { + if (trackedNoteIdRef.current !== note?.id) { + trackedNoteIdRef.current = note?.id; + prevDirtyRef.current = false; + } + }, [note?.id]); + + useEffect(() => { + if (prevDirtyRef.current && !isDirty) { + // Transitioned from dirty to clean — save completed + setShowSaved(true); + if (savedTimerRef.current) clearTimeout(savedTimerRef.current); + savedTimerRef.current = setTimeout(() => setShowSaved(false), 1500); + } + prevDirtyRef.current = isDirty; + }, [isDirty]); + + // Cleanup saved timer on unmount + useEffect(() => { + return () => { + if (savedTimerRef.current) clearTimeout(savedTimerRef.current); + }; + }, []); + + // Derive save status text + const saveStatus = useMemo(() => { + if (isDirty) return 'Saving...'; + if (showSaved) return 'Saved'; + return null; + }, [isDirty, showSaved]); + // Lightbox state for embedded images const [lightbox, setLightbox] = useState<{ src: string; alt: string } | null>(null); @@ -282,6 +323,14 @@ export function NoteEditor({
+ {saveStatus && ( + + {saveStatus} + + )}
{showEditor && ( diff --git a/apps/desktop/src/renderer/components/NoteList.tsx b/apps/desktop/src/renderer/components/NoteList.tsx index 6b7037af..02739517 100644 --- a/apps/desktop/src/renderer/components/NoteList.tsx +++ b/apps/desktop/src/renderer/components/NoteList.tsx @@ -14,12 +14,14 @@ import { import { LayoutZone } from '@readied/plugin-api'; import { useNotebookList, useNotebook } from '../hooks/useNotebooks'; import type { NoteWithExcerpt, SortBy, SortOrder } from '../hooks/useNavigation'; +import { useNavigationStore } from '../stores/navigationStore'; import { formatRelativeTime } from '../utils/date'; import { useTagColorsStore } from '../stores/tagColorsStore'; import { useShareStore, selectIsShared } from '../stores/shareStore'; import type { QuickFilterType } from './sidebar'; import { NoteListContextMenu } from './NoteListContextMenu'; import { NotebookPicker } from './NotebookPicker'; +import { NoteListFilterBar, FilterToggleButton } from './NoteListFilterBar'; interface NoteListProps { notes: NoteWithExcerpt[]; @@ -136,12 +138,18 @@ export function NoteList({ }: NoteListProps) { const [searchQuery, setSearchQuery] = useState(''); const [showSortDropdown, setShowSortDropdown] = useState(false); + const [showFilterBar, setShowFilterBar] = useState(false); const [contextMenu, setContextMenu] = useState(null); const [notebookPicker, setNotebookPicker] = useState(null); const sortDropdownRef = useRef(null); const { data: notebooks = [] } = useNotebookList(); const { data: notebook } = useNotebook(selectedNotebookId); + // Count active filters for the badge + const statusFilter = useNavigationStore(s => s.statusFilter); + const tagFilter = useNavigationStore(s => s.tagFilter); + const activeFilterCount = (statusFilter ? 1 : 0) + (tagFilter ? 1 : 0); + // Handler to open notebook picker from context menu const handleOpenNotebookPicker = useCallback( (noteId: string, currentNotebookId: string | null) => { @@ -283,7 +291,7 @@ export function NoteList({
- {/* Search bar with icon */} + {/* Search bar with icon + filter toggle */}
{searchQuery && ( @@ -317,6 +330,11 @@ export function NoteList({ )}
+ {/* Collapsible filter bar */} + {showFilterBar && ( + + )} + {/* Note list content */}
{isLoading ? ( diff --git a/apps/desktop/src/renderer/components/NoteListFilterBar.module.css b/apps/desktop/src/renderer/components/NoteListFilterBar.module.css new file mode 100644 index 00000000..80d8c998 --- /dev/null +++ b/apps/desktop/src/renderer/components/NoteListFilterBar.module.css @@ -0,0 +1,127 @@ +/* NoteListFilterBar — compact filter bar below search */ + +.filterBar { + padding: 6px 12px; + border-bottom: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + gap: 6px; +} + +/* Toggle button in the search area */ +.toggleBtn { + width: 28px; + height: 28px; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + color: var(--text-muted); + cursor: pointer; + border-radius: var(--radius-sm); + transition: all var(--transition-fast); + flex-shrink: 0; +} + +.toggleBtn:hover { + background: var(--bg-elevated); + color: var(--text-primary); +} + +.toggleBtnActive { + color: var(--accent); +} + +/* Row of filter controls */ +.row { + display: flex; + align-items: center; + gap: var(--space-1); + flex-wrap: wrap; +} + +.label { + font-size: var(--text-xs); + color: var(--text-muted); + flex-shrink: 0; + user-select: none; +} + +/* Status pills */ +.pill { + font-size: var(--text-xs); + line-height: 1; + padding: 3px 8px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-subtle); + background: var(--bg-hover); + color: var(--text-secondary); + cursor: pointer; + transition: all var(--transition-fast); + white-space: nowrap; +} + +.pill:hover { + background: var(--bg-elevated); + color: var(--text-primary); +} + +.pillActive { + background: var(--accent-muted); + color: var(--accent); + border-color: var(--accent-muted); +} + +/* Separator between sections */ +.sep { + width: 1px; + height: 14px; + background: var(--border-subtle); + margin: 0 2px; + flex-shrink: 0; +} + +/* Small select dropdown */ +.select { + font-size: var(--text-xs); + padding: 3px 6px; + border-radius: var(--radius-sm); + border: 1px solid var(--border-subtle); + background: var(--bg-hover); + color: var(--text-secondary); + cursor: pointer; + outline: none; + transition: all var(--transition-fast); + max-width: 100px; +} + +.select:hover { + background: var(--bg-elevated); +} + +.select:focus-visible { + border-color: var(--accent); +} + +/* Active filter count badge */ +.badge { + font-size: 9px; + line-height: 1; + min-width: 14px; + height: 14px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 7px; + background: var(--accent); + color: #fff; + position: absolute; + top: 2px; + right: 2px; + pointer-events: none; +} + +.toggleWrapper { + position: relative; +} diff --git a/apps/desktop/src/renderer/components/NoteListFilterBar.tsx b/apps/desktop/src/renderer/components/NoteListFilterBar.tsx new file mode 100644 index 00000000..7b7a73f9 --- /dev/null +++ b/apps/desktop/src/renderer/components/NoteListFilterBar.tsx @@ -0,0 +1,180 @@ +import { useState, useEffect, useCallback } from 'react'; +import { SlidersHorizontal } from 'lucide-react'; +import { useNavigationStore } from '../stores/navigationStore'; +import type { NoteStatus } from '../../preload/index'; +import type { SortBy, SortOrder } from '../hooks/useNavigation'; +import styles from './NoteListFilterBar.module.css'; + +// ============================================================================ +// Status options +// ============================================================================ + +const STATUS_OPTIONS: Array<{ value: NoteStatus | null; label: string }> = [ + { value: null, label: 'All' }, + { value: 'active', label: 'Active' }, + { value: 'on_hold', label: 'On Hold' }, + { value: 'completed', label: 'Completed' }, + { value: 'dropped', label: 'Dropped' }, +]; + +// ============================================================================ +// Sort options for the dropdown +// ============================================================================ + +const SORT_BY_OPTIONS: Array<{ value: SortBy; label: string }> = [ + { value: 'updatedAt', label: 'Updated' }, + { value: 'createdAt', label: 'Created' }, + { value: 'title', label: 'Title' }, +]; + +// ============================================================================ +// Component +// ============================================================================ + +interface NoteListFilterBarProps { + sortBy: SortBy; + sortOrder: SortOrder; + onSortChange: (sortBy: SortBy, sortOrder: SortOrder) => void; +} + +export function NoteListFilterBar({ sortBy, sortOrder, onSortChange }: NoteListFilterBarProps) { + const statusFilter = useNavigationStore(s => s.statusFilter); + const tagFilter = useNavigationStore(s => s.tagFilter); + const setStatusFilter = useNavigationStore(s => s.setStatusFilter); + const setTagFilter = useNavigationStore(s => s.setTagFilter); + + const [tags, setTags] = useState([]); + + // Load tags from the main process + useEffect(() => { + let cancelled = false; + void window.readied.notes + .tags() + .then(result => { + if (!cancelled) setTags(result); + }) + .catch(() => { + // IPC call failed — leave tags empty + }); + return () => { + cancelled = true; + }; + }, []); + + const handleStatusClick = useCallback( + (status: NoteStatus | null) => { + setStatusFilter(status); + }, + [setStatusFilter] + ); + + const handleTagChange = useCallback( + (e: React.ChangeEvent) => { + const value = e.target.value; + setTagFilter(value === '' ? null : value); + }, + [setTagFilter] + ); + + const handleSortByChange = useCallback( + (e: React.ChangeEvent) => { + onSortChange(e.target.value as SortBy, sortOrder); + }, + [onSortChange, sortOrder] + ); + + const handleSortOrderToggle = useCallback(() => { + onSortChange(sortBy, sortOrder === 'asc' ? 'desc' : 'asc'); + }, [onSortChange, sortBy, sortOrder]); + + return ( +
+ {/* Status pills */} +
+ {STATUS_OPTIONS.map(opt => ( + + ))} +
+ + {/* Tag + Sort row */} +
+ + + + + + + + +
+
+ ); +} + +// ============================================================================ +// Toggle button (used in the NoteList header area) +// ============================================================================ + +interface FilterToggleButtonProps { + isOpen: boolean; + activeCount: number; + onClick: () => void; +} + +export function FilterToggleButton({ isOpen, activeCount, onClick }: FilterToggleButtonProps) { + return ( +
+ + {activeCount > 0 && !isOpen && {activeCount}} +
+ ); +} diff --git a/apps/desktop/src/renderer/components/NoteWindow.tsx b/apps/desktop/src/renderer/components/NoteWindow.tsx index 5722f787..fbc9650e 100644 --- a/apps/desktop/src/renderer/components/NoteWindow.tsx +++ b/apps/desktop/src/renderer/components/NoteWindow.tsx @@ -53,7 +53,7 @@ function NoteWindowContent({ noteId }: NoteWindowContentProps) { setLoading(false); } } - loadNote(); + void loadNote(); }, [noteId]); // Update note content diff --git a/apps/desktop/src/renderer/components/UpdateBanner.module.css b/apps/desktop/src/renderer/components/UpdateBanner.module.css new file mode 100644 index 00000000..31d07a7d --- /dev/null +++ b/apps/desktop/src/renderer/components/UpdateBanner.module.css @@ -0,0 +1,48 @@ +.banner { + display: flex; + align-items: center; + justify-content: center; + gap: 0.75rem; + padding: 0.35rem 1rem; + background: var(--accent-primary, var(--accent)); + color: #fff; + font-size: 0.75rem; + line-height: 1; + flex-shrink: 0; +} + +.text { + font-weight: 500; +} + +.action { + background: none; + border: none; + color: #fff; + font-size: 0.75rem; + text-decoration: underline; + cursor: pointer; + padding: 0; + font-weight: 600; +} + +.action:hover { + opacity: 0.85; +} + +.dismiss { + background: none; + border: none; + color: #fff; + cursor: pointer; + padding: 0; + margin-left: 0.25rem; + opacity: 0.7; + line-height: 1; + display: flex; + align-items: center; +} + +.dismiss:hover { + opacity: 1; +} diff --git a/apps/desktop/src/renderer/components/UpdateBanner.tsx b/apps/desktop/src/renderer/components/UpdateBanner.tsx new file mode 100644 index 00000000..52fd5e90 --- /dev/null +++ b/apps/desktop/src/renderer/components/UpdateBanner.tsx @@ -0,0 +1,119 @@ +import { useState, useEffect, useCallback } from 'react'; +import { X } from 'lucide-react'; +import styles from './UpdateBanner.module.css'; + +type BannerState = + | { kind: 'hidden' } + | { kind: 'available'; version: string } + | { kind: 'downloading'; version: string; percent: number } + | { kind: 'ready'; version: string } + | { kind: 'error'; version: string }; + +export function UpdateBanner() { + const [state, setState] = useState({ kind: 'hidden' }); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + const cleanups: Array<() => void> = []; + + cleanups.push( + window.readied.updates.onAvailable(info => { + setState({ kind: 'available', version: info.version }); + setDismissed(false); + }) + ); + + cleanups.push( + window.readied.updates.onDownloadProgress(p => { + setState(prev => + prev.kind === 'hidden' + ? prev + : { + kind: 'downloading', + version: (prev as { version: string }).version, + percent: Math.round(p.percent), + } + ); + }) + ); + + cleanups.push( + window.readied.updates.onDownloadComplete(info => { + setState({ kind: 'ready', version: info.version }); + setDismissed(false); + }) + ); + + cleanups.push( + window.readied.updates.onError(() => { + setState(prev => + prev.kind === 'hidden' + ? prev + : { kind: 'error', version: (prev as { version: string }).version } + ); + setDismissed(false); + }) + ); + + return () => cleanups.forEach(fn => fn()); + }, []); + + const handleDownload = useCallback(async () => { + try { + await window.readied.updates.startDownload(); + } catch { + setState(prev => + prev.kind === 'hidden' + ? prev + : { kind: 'error', version: (prev as { version: string }).version } + ); + } + }, []); + + const handleInstall = useCallback(() => { + void window.readied.updates.installNow(); + }, []); + + if (state.kind === 'hidden' || dismissed) return null; + + return ( +
+ {state.kind === 'available' && ( + <> + Update available: v{state.version} + + + )} + {state.kind === 'downloading' && ( + + Downloading v{state.version}... {state.percent}% + + )} + {state.kind === 'error' && ( + <> + Download failed + + + )} + {state.kind === 'ready' && ( + <> + v{state.version} ready to install + + + )} + +
+ ); +} diff --git a/apps/desktop/src/renderer/components/Welcome.module.css b/apps/desktop/src/renderer/components/Welcome.module.css new file mode 100644 index 00000000..acb5ca75 --- /dev/null +++ b/apps/desktop/src/renderer/components/Welcome.module.css @@ -0,0 +1,115 @@ +/* Welcome — First-run onboarding overlay */ + +@keyframes fade-in { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.overlay { + position: fixed; + inset: 0; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-base); + animation: fade-in 400ms ease both; +} + +.container { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-8); + max-width: 520px; + padding: var(--space-10); + text-align: center; +} + +.brand { + font-family: var(--font-sans); + font-size: 28px; + font-weight: var(--font-weight-bold); + color: var(--accent); + letter-spacing: var(--tracking-tight); + user-select: none; +} + +.headline { + font-family: var(--font-sans); + font-size: var(--text-2xl); + font-weight: var(--font-weight-medium); + color: var(--text-primary); + line-height: var(--leading-tight); + margin: 0; +} + +.cards { + display: flex; + flex-direction: column; + gap: var(--space-3); + width: 100%; +} + +.card { + display: flex; + flex-direction: column; + gap: var(--space-1); + padding: var(--space-4) var(--space-5); + border-radius: var(--radius-lg); + background: var(--glass-bg); + border: 1px solid var(--glass-border); + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + text-align: left; + animation: fade-in 400ms ease both; +} + +.card:nth-child(1) { animation-delay: 100ms; } +.card:nth-child(2) { animation-delay: 200ms; } +.card:nth-child(3) { animation-delay: 300ms; } + +.cardTitle { + font-family: var(--font-sans); + font-size: var(--text-lg); + font-weight: var(--font-weight-semibold); + color: var(--text-primary); + margin: 0; +} + +.cardDesc { + font-family: var(--font-sans); + font-size: var(--text-base); + color: var(--text-muted); + line-height: var(--leading-normal); + margin: 0; +} + +.actions { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--space-3); + animation: fade-in 400ms ease both; + animation-delay: 400ms; +} + +.skip { + background: none; + border: none; + padding: var(--space-1) var(--space-2); + font-family: var(--font-sans); + font-size: var(--text-sm); + color: var(--text-faint); + cursor: pointer; + transition: color var(--transition-fast); +} + +.skip:hover { + color: var(--text-secondary); +} diff --git a/apps/desktop/src/renderer/components/Welcome.tsx b/apps/desktop/src/renderer/components/Welcome.tsx new file mode 100644 index 00000000..b6aba91b --- /dev/null +++ b/apps/desktop/src/renderer/components/Welcome.tsx @@ -0,0 +1,81 @@ +/** + * Welcome — First-run onboarding screen + * + * Shown once on first launch. Offers to create the user's first note + * or skip straight into the app. + */ + +import { useCallback, useEffect } from 'react'; +import { Button } from '../ui/primitives'; +import styles from './Welcome.module.css'; + +interface WelcomeProps { + /** Called when user finishes onboarding. `createNote` is true when they click the CTA. */ + onComplete: (createNote: boolean) => void; +} + +const features = [ + { + title: 'Local-First', + desc: 'Works fully offline. Optional end-to-end encrypted sync.', + }, + { + title: 'Pure Markdown', + desc: 'Standard .md under the hood. No lock-in.', + }, + { + title: 'Extensible', + desc: 'Plugins, themes, and AI built in.', + }, +] as const; + +export function Welcome({ onComplete }: WelcomeProps) { + const handleEscape = useCallback( + (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onComplete(false); + } + }, + [onComplete] + ); + + useEffect(() => { + document.addEventListener('keydown', handleEscape); + return () => document.removeEventListener('keydown', handleEscape); + }, [handleEscape]); + + return ( +
+
+ Readied + +

+ Your Markdown. Your Machine. Your Rules. +

+ +
+ {features.map(f => ( +
+

{f.title}

+

{f.desc}

+
+ ))} +
+ +
+ + +
+
+
+ ); +} diff --git a/apps/desktop/src/renderer/components/ai/AiPanel.tsx b/apps/desktop/src/renderer/components/ai/AiPanel.tsx index 1228f5a5..d214f569 100644 --- a/apps/desktop/src/renderer/components/ai/AiPanel.tsx +++ b/apps/desktop/src/renderer/components/ai/AiPanel.tsx @@ -29,26 +29,27 @@ interface AiPanelProps { onCommandExecuted?: () => void; } -/** Map LLM error codes to user-friendly messages */ -function formatErrorMessage(event: LLMEvent & { type: 'error' }): string { - switch (event.code) { +/** Map raw LLM error codes to user-friendly messages */ +function humanizeAiError(code: string, rawMessage: string): string { + switch (code) { case 'auth_failed': - return 'Authentication failed. Please check your API key in Settings > AI Assistant.'; + return 'API key is invalid or expired. Check your key in Settings > AI.'; case 'rate_limit': - return 'Rate limit exceeded. Please wait a moment and try again.'; + return 'Rate limit reached. Please wait a moment and try again.'; + case 'provider_error': + return 'The AI provider returned an error. Try again or switch models.'; + case 'network': + return "Can't reach the AI service. Check your internet connection."; case 'context_overflow': - return 'The conversation is too long. Try clearing the chat and starting fresh.'; + return 'Your note is too long for this model. Try selecting less text.'; case 'model_not_found': - return 'The selected model was not found. Please check your model setting.'; - case 'network': - return 'Network error. Please check your internet connection.'; + return 'The selected model is not available. Check Settings > AI.'; case 'cancelled': return 'Request was cancelled.'; case 'timeout': return 'Request timed out. Please try again.'; - case 'provider_error': default: - return event.error || 'An unexpected error occurred.'; + return rawMessage || 'Something went wrong. Please try again.'; } } @@ -174,9 +175,9 @@ export function AiPanel({ const errorEvent = event as LLMEvent & { type: 'error'; retryable?: boolean }; if (errorEvent.retryable) { // Transient retry — show message but don't tear down the stream - setError(formatErrorMessage(event)); + setError(humanizeAiError(event.code, event.error)); } else { - setError(formatErrorMessage(event)); + setError(humanizeAiError(event.code, event.error)); setLoading(false); activeRequestRef.current = null; } @@ -309,7 +310,7 @@ export function AiPanel({ break; case 'error': - setError(formatErrorMessage(event)); + setError(humanizeAiError(event.code, event.error)); setLoading(false); activeRequestRef.current = null; onCommandExecuted?.(); @@ -379,7 +380,7 @@ export function AiPanel({ } }; - execute(); + void execute(); }, [initialCommand]); // intentionally depends only on initialCommand const handleSubmit = useCallback(async () => { @@ -497,7 +498,7 @@ export function AiPanel({ (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); - handleSubmit(); + void handleSubmit(); } }, [handleSubmit] @@ -505,13 +506,13 @@ export function AiPanel({ const handleToolConfirm = useCallback((callId: string) => { if (activeRequestRef.current) { - window.readied.ai.confirmTool(activeRequestRef.current, callId, true); + void window.readied.ai.confirmTool(activeRequestRef.current, callId, true); } }, []); const handleToolReject = useCallback((callId: string) => { if (activeRequestRef.current) { - window.readied.ai.confirmTool(activeRequestRef.current, callId, false); + void window.readied.ai.confirmTool(activeRequestRef.current, callId, false); setToolCalls(prev => { const next = new Map(prev); const existing = next.get(callId); @@ -524,7 +525,7 @@ export function AiPanel({ const handleClear = useCallback(() => { // Cancel any active request if (activeRequestRef.current) { - window.readied.ai.cancel(activeRequestRef.current); + void window.readied.ai.cancel(activeRequestRef.current); activeRequestRef.current = null; } setMessages([]); diff --git a/apps/desktop/src/renderer/components/auth/MagicLinkFlow.module.css b/apps/desktop/src/renderer/components/auth/MagicLinkFlow.module.css index 08dc9be1..726c616d 100644 --- a/apps/desktop/src/renderer/components/auth/MagicLinkFlow.module.css +++ b/apps/desktop/src/renderer/components/auth/MagicLinkFlow.module.css @@ -24,7 +24,7 @@ .dialog { background: var(--bg-primary); - border-radius: 1rem; + border-radius: var(--radius-xl); box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); width: 90%; max-width: 450px; @@ -45,14 +45,14 @@ .closeButton { position: absolute; - top: 1rem; - right: 1rem; + top: var(--space-4); + right: var(--space-4); background: transparent; border: none; color: var(--text-tertiary); cursor: pointer; - padding: 0.5rem; - border-radius: 0.375rem; + padding: var(--space-2); + border-radius: var(--radius-md); transition: all 0.2s; } @@ -73,7 +73,7 @@ .icon, .successIcon, .errorIcon { - margin: 0 auto 1rem; + margin: 0 auto var(--space-4); display: block; } @@ -96,7 +96,7 @@ border-top-color: var(--accent-primary); border-radius: 50%; animation: spin 0.8s linear infinite; - margin: 0 auto 1rem; + margin: 0 auto var(--space-4); } @keyframes spin { @@ -109,7 +109,7 @@ font-size: 1.5rem; font-weight: 600; color: var(--text-primary); - margin: 0 0 0.5rem 0; + margin: 0 0 var(--space-2) 0; } .description { @@ -127,15 +127,15 @@ .form { display: flex; flex-direction: column; - gap: 1rem; + gap: var(--space-4); } .input { width: 100%; - padding: 0.75rem 1rem; + padding: var(--space-3) var(--space-4); background: var(--bg-secondary); border: 1px solid var(--border-primary); - border-radius: 0.5rem; + border-radius: var(--radius-lg); color: var(--text-primary); font-size: 1rem; transition: all 0.2s; @@ -149,9 +149,9 @@ .primaryButton, .secondaryButton { - padding: 0.75rem 1.5rem; + padding: var(--space-3) var(--space-6); border: none; - border-radius: 0.5rem; + border-radius: var(--radius-lg); font-size: 0.9375rem; font-weight: 600; cursor: pointer; @@ -185,7 +185,7 @@ color: var(--danger, #ef4444); font-size: 0.875rem; text-align: center; - margin: 0 0 0.75rem; + margin: 0 0 var(--space-3); } .actions { diff --git a/apps/desktop/src/renderer/components/editor/ActionsPanel/ActionsPanel.tsx b/apps/desktop/src/renderer/components/editor/ActionsPanel/ActionsPanel.tsx index 2fbdc68b..57876fce 100644 --- a/apps/desktop/src/renderer/components/editor/ActionsPanel/ActionsPanel.tsx +++ b/apps/desktop/src/renderer/components/editor/ActionsPanel/ActionsPanel.tsx @@ -204,7 +204,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-unordered-list'); + void dispatchCommand('editor:insert-unordered-list'); onClose(); }} > @@ -217,7 +217,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-ordered-list'); + void dispatchCommand('editor:insert-ordered-list'); onClose(); }} > @@ -230,7 +230,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-checkbox'); + void dispatchCommand('editor:insert-checkbox'); onClose(); }} > @@ -249,7 +249,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-quote'); + void dispatchCommand('editor:insert-quote'); onClose(); }} > @@ -262,7 +262,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-code-block'); + void dispatchCommand('editor:insert-code-block'); onClose(); }} > @@ -275,7 +275,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:insert-horizontal-rule'); + void dispatchCommand('editor:insert-horizontal-rule'); onClose(); }} > @@ -294,7 +294,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:undo'); + void dispatchCommand('editor:undo'); onClose(); }} > @@ -307,7 +307,7 @@ export const ActionsPanel = memo(function ActionsPanel({ type="button" className={styles.item} onClick={() => { - dispatchCommand('editor:redo'); + void dispatchCommand('editor:redo'); onClose(); }} > diff --git a/apps/desktop/src/renderer/components/editor/MarkdownPreview.tsx b/apps/desktop/src/renderer/components/editor/MarkdownPreview.tsx index 327ff655..ee89e0c3 100644 --- a/apps/desktop/src/renderer/components/editor/MarkdownPreview.tsx +++ b/apps/desktop/src/renderer/components/editor/MarkdownPreview.tsx @@ -104,7 +104,7 @@ export const MarkdownPreview = forwardRef { + void window.readied.embeds.resolveBatch(targets, noteId).then(result => { setInternalResolvedEmbeds(result); }); }, [content, noteId, resolvedEmbedsProp]); diff --git a/apps/desktop/src/renderer/components/editor/RevisionHistoryPanel/RevisionHistoryPanel.tsx b/apps/desktop/src/renderer/components/editor/RevisionHistoryPanel/RevisionHistoryPanel.tsx index 7ad86430..54b2505c 100644 --- a/apps/desktop/src/renderer/components/editor/RevisionHistoryPanel/RevisionHistoryPanel.tsx +++ b/apps/desktop/src/renderer/components/editor/RevisionHistoryPanel/RevisionHistoryPanel.tsx @@ -69,7 +69,7 @@ export const RevisionHistoryPanel = memo(function RevisionHistoryPanel({ setSelectedCommit(null); setDiffs([]); - window.readied.git.log(notebookId, 50).then(result => { + void window.readied.git.log(notebookId, 50).then(result => { if (cancelled) return; setLoading(false); if (result.success && result.commits) { diff --git a/apps/desktop/src/renderer/components/git/CommitHistory.tsx b/apps/desktop/src/renderer/components/git/CommitHistory.tsx index e18df6be..c35e6f78 100644 --- a/apps/desktop/src/renderer/components/git/CommitHistory.tsx +++ b/apps/desktop/src/renderer/components/git/CommitHistory.tsx @@ -30,7 +30,7 @@ export function CommitHistory({ notebookId, notebookName, onClose }: CommitHisto const [expandedCommit, setExpandedCommit] = useState(null); useEffect(() => { - loadCommits(); + void loadCommits(); }, [notebookId]); const loadCommits = async () => { diff --git a/apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx b/apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx index 85b34417..c483820f 100644 --- a/apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx +++ b/apps/desktop/src/renderer/components/sidebar/NotebookItem.tsx @@ -91,7 +91,7 @@ export const NotebookItem = memo(function NotebookItem({ console.error('Failed to check git status:', error); } }; - checkGitStatus(); + void checkGitStatus(); }, [node.notebook.id]); const handleClick = useCallback( diff --git a/apps/desktop/src/renderer/components/sidebar/Sidebar.tsx b/apps/desktop/src/renderer/components/sidebar/Sidebar.tsx index 8dfa85f2..3875eaf5 100644 --- a/apps/desktop/src/renderer/components/sidebar/Sidebar.tsx +++ b/apps/desktop/src/renderer/components/sidebar/Sidebar.tsx @@ -39,7 +39,7 @@ export function Sidebar({ onOpenGraph }: SidebarProps) { useEffect(() => { const result = window.readied.app.version(); // Handle both sync (old preload) and async (new preload) return - Promise.resolve(result).then(setAppVersion); + void Promise.resolve(result).then(setAppVersion); }, []); // Modal state - lives HERE, not in NotebookList diff --git a/apps/desktop/src/renderer/components/sidebar/SidebarFooter.tsx b/apps/desktop/src/renderer/components/sidebar/SidebarFooter.tsx index c5a69817..f6f921e2 100644 --- a/apps/desktop/src/renderer/components/sidebar/SidebarFooter.tsx +++ b/apps/desktop/src/renderer/components/sidebar/SidebarFooter.tsx @@ -1,5 +1,5 @@ -import { memo } from 'react'; -import { Cloud, CloudOff, RefreshCw, AlertCircle } from 'lucide-react'; +import { memo, useState, useEffect, useRef, useCallback } from 'react'; +import { Cloud, CloudOff, RefreshCw, AlertCircle, Check } from 'lucide-react'; import { useAuthStore } from '../../stores/authStore'; import { useSyncStore, @@ -7,6 +7,7 @@ import { selectLastSyncAt, selectConsecutiveFailures, selectPendingCount, + selectError, } from '../../stores/syncStore'; interface SidebarFooterProps { @@ -27,6 +28,132 @@ function formatRelativeTime(timestamp: number): string { return `${days}d ago`; } +/** + * Sync progress indicator shown in the sidebar footer. + * + * States: + * - idle + pending > 0 → "N pending" + * - syncing → "Syncing..." with spinning icon + * - just synced → "Synced" with check icon (fades after 3s) + * - error/auth-expired → "Sync error" in danger color with retry + * - offline → "Offline — N pending" + * - idle + pending === 0 → hidden (nothing to show) + */ +const SyncProgressIndicator = memo(function SyncProgressIndicator() { + const syncStatus = useSyncStore(selectStatus); + const lastSyncAt = useSyncStore(selectLastSyncAt); + const pendingCount = useSyncStore(selectPendingCount); + const consecutiveFailures = useSyncStore(selectConsecutiveFailures); + const syncError = useSyncStore(selectError); + const syncNow = useSyncStore(state => state.syncNow); + const refreshPendingCount = useSyncStore(state => state.refreshPendingCount); + + // Track "just synced" flash state + const [showSynced, setShowSynced] = useState(false); + const prevStatusRef = useRef(syncStatus); + + // When status transitions from 'syncing' to 'idle', flash "Synced" + useEffect(() => { + if (prevStatusRef.current === 'syncing' && syncStatus === 'idle') { + setShowSynced(true); + prevStatusRef.current = syncStatus; + const timer = setTimeout(() => setShowSynced(false), 3000); + return () => clearTimeout(timer); + } + prevStatusRef.current = syncStatus; + }, [syncStatus]); + + // Poll pending count every 30s + useEffect(() => { + const interval = setInterval(() => { + void refreshPendingCount(); + }, 30_000); + return () => clearInterval(interval); + }, [refreshPendingCount]); + + const handleRetry = useCallback(() => { + void syncNow(); + }, [syncNow]); + + // Syncing + if (syncStatus === 'syncing') { + return ( +
+ + Syncing... +
+ ); + } + + // Just synced flash + if (showSynced) { + return ( +
+ + Synced +
+ ); + } + + // Error or auth-expired + if (syncStatus === 'error' || syncStatus === 'auth-expired') { + return ( +
+ + + {syncStatus === 'auth-expired' ? 'Session expired' : 'Sync error'} + + {syncStatus === 'error' && ( + + )} +
+ ); + } + + // Offline + if (syncStatus === 'offline') { + return ( +
+ + {pendingCount > 0 ? `Offline \u2014 ${pendingCount} pending` : 'Offline'} +
+ ); + } + + // Idle with pending changes + if (pendingCount > 0) { + return ( +
+ + {pendingCount} pending +
+ ); + } + + // Idle with many consecutive failures (no error state yet) + if (consecutiveFailures >= 2) { + return ( +
+ + Sync unstable + +
+ ); + } + + // Idle, no pending, recently synced — show last sync time briefly + if (lastSyncAt && Date.now() - lastSyncAt < 60_000) { + return null; // "Synced" flash already handled above + } + + // Nothing to show + return null; +}); + export const SidebarFooter = memo(function SidebarFooter({ appVersion, onEnableSyncClick, @@ -34,9 +161,6 @@ export const SidebarFooter = memo(function SidebarFooter({ const isAuthenticated = useAuthStore(state => state.isAuthenticated); const email = useAuthStore(state => state.user?.email ?? null); const syncStatus = useSyncStore(selectStatus); - const lastSyncAt = useSyncStore(selectLastSyncAt); - const consecutiveFailures = useSyncStore(selectConsecutiveFailures); - const pendingCount = useSyncStore(selectPendingCount); const getSyncIcon = () => { switch (syncStatus) { @@ -53,6 +177,7 @@ export const SidebarFooter = memo(function SidebarFooter({ }; const getSyncTooltip = () => { + const lastSyncAt = useSyncStore.getState().lastSyncAt; switch (syncStatus) { case 'syncing': return 'Syncing...'; @@ -67,11 +192,6 @@ export const SidebarFooter = memo(function SidebarFooter({ } }; - // Show offline queue when offline/error with pending changes, or many consecutive failures - const isOfflineOrError = syncStatus === 'offline' || syncStatus === 'error'; - const showQueueStatus = - isAuthenticated && isOfflineOrError && (pendingCount > 0 || consecutiveFailures >= 2); - return (
{isAuthenticated && email ? ( @@ -92,13 +212,7 @@ export const SidebarFooter = memo(function SidebarFooter({ Enable Sync )} - {showQueueStatus && ( - - {pendingCount > 0 - ? `${pendingCount} change${pendingCount === 1 ? '' : 's'} pending` - : 'Offline \u2014 changes will sync when back online'} - - )} + {isAuthenticated && } v{appVersion} diff --git a/apps/desktop/src/renderer/components/sidebar/TagsList.tsx b/apps/desktop/src/renderer/components/sidebar/TagsList.tsx index 978454bc..c8df6ceb 100644 --- a/apps/desktop/src/renderer/components/sidebar/TagsList.tsx +++ b/apps/desktop/src/renderer/components/sidebar/TagsList.tsx @@ -92,7 +92,7 @@ export function TagsList({ selectedTag, onSelectTag }: TagsListProps) { } await window.readied.notes.deleteTag(tag); // Invalidate tags query for sidebar - queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); // Remove from colors cache (tag no longer exists) useTagColorsStore.getState().removeTag(tag); }, @@ -108,12 +108,12 @@ export function TagsList({ selectedTag, onSelectTag }: TagsListProps) { onSelectTag(newTag); } // Invalidate queries to refresh sidebar and notes - queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); - queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); // Update colors cache (move color from old to new) const oldColor = useTagColorsStore.getState().getColor(oldTag); if (oldColor) { - useTagColorsStore.getState().setColor(newTag, oldColor); + void useTagColorsStore.getState().setColor(newTag, oldColor); } useTagColorsStore.getState().removeTag(oldTag); } @@ -193,7 +193,7 @@ export function TagsList({ selectedTag, onSelectTag }: TagsListProps) { className="tags-list-item-delete" onClick={e => { e.stopPropagation(); - handleDeleteTag(tag); + void handleDeleteTag(tag); }} aria-label={`Delete tag ${tag}`} > diff --git a/apps/desktop/src/renderer/components/sync/LoginModal.module.css b/apps/desktop/src/renderer/components/sync/LoginModal.module.css index e5d9e980..df92218f 100644 --- a/apps/desktop/src/renderer/components/sync/LoginModal.module.css +++ b/apps/desktop/src/renderer/components/sync/LoginModal.module.css @@ -21,10 +21,10 @@ .closeButton { position: absolute; - top: 12px; - right: 12px; - width: 32px; - height: 32px; + top: var(--space-3); + right: var(--space-3); + width: var(--space-8); + height: var(--space-8); display: flex; align-items: center; justify-content: center; @@ -42,7 +42,7 @@ } .content { - padding: 32px; + padding: var(--space-8); display: flex; flex-direction: column; align-items: center; @@ -58,18 +58,18 @@ background: var(--color-primary-subtle, rgba(59, 130, 246, 0.1)); border-radius: 50%; color: var(--color-primary, #3b82f6); - margin-bottom: 16px; + margin-bottom: var(--space-4); } .title { - margin: 0 0 8px; + margin: 0 0 var(--space-2); font-size: 20px; font-weight: 600; color: var(--color-text-primary, #111); } .subtitle { - margin: 0 0 20px; + margin: 0 0 var(--space-5); font-size: 14px; color: var(--color-text-secondary, #666); line-height: 1.5; @@ -79,14 +79,14 @@ .benefits { list-style: none; padding: 0; - margin: 0 0 24px; + margin: 0 0 var(--space-6); width: 100%; text-align: left; } .benefits li { position: relative; - padding: 6px 0 6px 24px; + padding: 6px 0 6px var(--space-6); font-size: 14px; color: var(--color-text-secondary, #555); line-height: 1.4; @@ -104,12 +104,12 @@ .form { display: flex; flex-direction: column; - gap: 12px; + gap: var(--space-3); width: 100%; } .input { - padding: 10px 12px; + padding: 10px var(--space-3); font-size: 14px; border: 1px solid var(--color-border, #ddd); border-radius: var(--radius-md, 6px); @@ -131,7 +131,7 @@ /* Error */ .error { margin: 0; - padding: 8px 12px; + padding: var(--space-2) var(--space-3); font-size: 13px; color: var(--color-error, #dc2626); background: rgba(220, 38, 38, 0.1); @@ -141,7 +141,7 @@ /* Primary button */ .button { - padding: 12px 16px; + padding: var(--space-3) var(--space-4); font-size: 14px; font-weight: 500; color: white; @@ -168,18 +168,18 @@ flex-direction: column; align-items: center; text-align: center; - padding: 20px 0; + padding: var(--space-5) 0; width: 100%; } .spinner { - width: 32px; - height: 32px; + width: var(--space-8); + height: var(--space-8); border: 3px solid var(--color-border, #ddd); border-top-color: var(--color-primary, #3b82f6); border-radius: 50%; animation: spin 0.8s linear infinite; - margin-bottom: 16px; + margin-bottom: var(--space-4); } @keyframes spin { @@ -190,16 +190,16 @@ .checkIcon { color: var(--color-success, #22c55e); - margin-bottom: 16px; + margin-bottom: var(--space-4); } .successIcon { color: var(--color-success, #22c55e); - margin-bottom: 16px; + margin-bottom: var(--space-4); } .sent h3 { - margin: 0 0 8px; + margin: 0 0 var(--space-2); font-size: 18px; font-weight: 600; } @@ -211,14 +211,14 @@ } .hint { - margin-top: 12px !important; + margin-top: var(--space-3) !important; font-size: 13px; color: var(--color-text-tertiary, #999) !important; } /* Resend row */ .resendRow { - margin-top: 16px; + margin-top: var(--space-4); display: flex; align-items: center; justify-content: center; @@ -232,7 +232,7 @@ /* Link button */ .linkButton { - margin-top: 12px; + margin-top: var(--space-3); padding: 0; font-size: 13px; color: var(--color-primary, #3b82f6); @@ -241,7 +241,7 @@ cursor: pointer; display: inline-flex; align-items: center; - gap: 4px; + gap: var(--space-1); } .linkButton:hover { @@ -251,7 +251,7 @@ /* Footer */ .footer { - padding: 16px 32px; + padding: var(--space-4) var(--space-8); background: var(--color-bg-secondary, #fafafa); border-top: 1px solid var(--color-border, #eee); } @@ -284,8 +284,8 @@ display: flex; align-items: center; justify-content: center; - gap: 8px; - padding: 14px 16px; + gap: var(--space-2); + padding: 14px var(--space-4); font-size: 14px; font-weight: 500; color: var(--color-text-primary, #333); @@ -306,8 +306,8 @@ display: flex; align-items: center; justify-content: center; - gap: 8px; - padding: 14px 16px; + gap: var(--space-2); + padding: 14px var(--space-4); font-size: 14px; font-weight: 500; color: white; @@ -334,7 +334,7 @@ .savingsBadge { display: inline-block; - padding: 2px 8px; + padding: 2px var(--space-2); font-size: 11px; font-weight: 600; color: var(--color-success, #22c55e); @@ -348,7 +348,7 @@ } .waitingHint { - margin-top: 8px !important; + margin-top: var(--space-2) !important; font-size: 13px; color: var(--color-text-tertiary, #999) !important; } diff --git a/apps/desktop/src/renderer/components/sync/SyncStatusIndicator.module.css b/apps/desktop/src/renderer/components/sync/SyncStatusIndicator.module.css index 5963d619..744162e7 100644 --- a/apps/desktop/src/renderer/components/sync/SyncStatusIndicator.module.css +++ b/apps/desktop/src/renderer/components/sync/SyncStatusIndicator.module.css @@ -38,7 +38,7 @@ right: 2px; min-width: 14px; height: 14px; - padding: 0 4px; + padding: 0 var(--space-1); font-size: 10px; font-weight: 600; line-height: 14px; @@ -54,7 +54,7 @@ left: 50%; transform: translateX(-50%); margin-top: 6px; - padding: 4px 8px; + padding: var(--space-1) var(--space-2); font-size: 12px; white-space: nowrap; color: var(--color-text-primary, #333); diff --git a/apps/desktop/src/renderer/contexts/LicenseContext.tsx b/apps/desktop/src/renderer/contexts/LicenseContext.tsx index 37c3b482..0f34266e 100644 --- a/apps/desktop/src/renderer/contexts/LicenseContext.tsx +++ b/apps/desktop/src/renderer/contexts/LicenseContext.tsx @@ -50,7 +50,7 @@ export function LicenseProvider({ children }: { children: ReactNode }) { useEffect(() => { setIsLoading(true); - refresh().finally(() => setIsLoading(false)); + void refresh().finally(() => setIsLoading(false)); return stopPolling; }, [refresh, stopPolling]); diff --git a/apps/desktop/src/renderer/hooks/useEmbedResolver.ts b/apps/desktop/src/renderer/hooks/useEmbedResolver.ts index 965b5ebb..aeb60fea 100644 --- a/apps/desktop/src/renderer/hooks/useEmbedResolver.ts +++ b/apps/desktop/src/renderer/hooks/useEmbedResolver.ts @@ -50,9 +50,19 @@ export function useEmbedResolver({ // Capture noteId for async closure const currentNoteId = noteId; - window.readied.embeds.resolveBatch(localTargets, currentNoteId).then(result => { - setResolvedEmbeds(result); - }); + let cancelled = false; + window.readied.embeds + .resolveBatch(localTargets, currentNoteId) + .then(result => { + if (!cancelled) setResolvedEmbeds(result); + }) + .catch(() => { + if (!cancelled) setResolvedEmbeds({}); + }); + + return () => { + cancelled = true; + }; }, [noteId, content]); // Callback for getting resolved embed URLs diff --git a/apps/desktop/src/renderer/hooks/useLinks.ts b/apps/desktop/src/renderer/hooks/useLinks.ts index 299430d1..60fd584c 100644 --- a/apps/desktop/src/renderer/hooks/useLinks.ts +++ b/apps/desktop/src/renderer/hooks/useLinks.ts @@ -62,7 +62,7 @@ export function useSyncLinks() { }, onSuccess: () => { // Invalidate all link queries since links may have changed - queryClient.invalidateQueries({ queryKey: linkKeys.all }); + void queryClient.invalidateQueries({ queryKey: linkKeys.all }); }, }); } diff --git a/apps/desktop/src/renderer/hooks/useManualTags.ts b/apps/desktop/src/renderer/hooks/useManualTags.ts index 290dd87c..3ef8248e 100644 --- a/apps/desktop/src/renderer/hooks/useManualTags.ts +++ b/apps/desktop/src/renderer/hooks/useManualTags.ts @@ -61,7 +61,7 @@ export function useManualTags({ } } - loadManualTags(); + void loadManualTags(); return () => { cancelled = true; @@ -85,8 +85,8 @@ export function useManualTags({ try { await window.readied.notes.setManualTags(noteId, updatedTags); // Invalidate queries so sidebar and note list update - queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); - queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); // Refetch note to sync selectedNote state with updated tags if (onNoteUpdate) { @@ -118,8 +118,8 @@ export function useManualTags({ try { await window.readied.notes.setManualTags(noteId, updatedTags); // Invalidate queries so sidebar and note list update - queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); - queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.tags() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); // Refetch note to sync selectedNote state with updated tags if (onNoteUpdate) { diff --git a/apps/desktop/src/renderer/hooks/useNotebooks.ts b/apps/desktop/src/renderer/hooks/useNotebooks.ts index 26f9aa3a..b8fe330b 100644 --- a/apps/desktop/src/renderer/hooks/useNotebooks.ts +++ b/apps/desktop/src/renderer/hooks/useNotebooks.ts @@ -57,7 +57,7 @@ export function useNotebookMutations() { const queryClient = useQueryClient(); const invalidateNotebooks = () => { - queryClient.invalidateQueries({ queryKey: notebookKeys.all }); + void queryClient.invalidateQueries({ queryKey: notebookKeys.all }); }; const createNotebook = useMutation({ @@ -73,8 +73,8 @@ export function useNotebookMutations() { }, onSuccess: data => { queryClient.setQueryData(notebookKeys.detail(data.id), data); - queryClient.invalidateQueries({ queryKey: notebookKeys.tree() }); - queryClient.invalidateQueries({ queryKey: notebookKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: notebookKeys.tree() }); + void queryClient.invalidateQueries({ queryKey: notebookKeys.lists() }); }, }); @@ -92,7 +92,7 @@ export function useNotebookMutations() { onSuccess: () => { invalidateNotebooks(); // Also invalidate notes since they may have moved to Inbox - queryClient.invalidateQueries({ queryKey: ['notes'] }); + void queryClient.invalidateQueries({ queryKey: ['notes'] }); }, }); diff --git a/apps/desktop/src/renderer/hooks/useNotes.ts b/apps/desktop/src/renderer/hooks/useNotes.ts index 9f24fae0..3f9314c3 100644 --- a/apps/desktop/src/renderer/hooks/useNotes.ts +++ b/apps/desktop/src/renderer/hooks/useNotes.ts @@ -99,7 +99,7 @@ export function useNoteMutations() { const queryClient = useQueryClient(); const invalidateNotes = () => { - queryClient.invalidateQueries({ queryKey: noteKeys.all }); + void queryClient.invalidateQueries({ queryKey: noteKeys.all }); }; const createNote = useMutation({ @@ -119,7 +119,7 @@ export function useNoteMutations() { }, onSuccess: data => { queryClient.setQueryData(noteKeys.detail(data.id), data); - queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); }, }); @@ -131,7 +131,7 @@ export function useNoteMutations() { }, onSuccess: data => { queryClient.setQueryData(noteKeys.detail(data.id), data); - queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); + void queryClient.invalidateQueries({ queryKey: noteKeys.lists() }); }, }); diff --git a/apps/desktop/src/renderer/pages/settings/SettingsApp.tsx b/apps/desktop/src/renderer/pages/settings/SettingsApp.tsx index 04ae9487..77a00fdd 100644 --- a/apps/desktop/src/renderer/pages/settings/SettingsApp.tsx +++ b/apps/desktop/src/renderer/pages/settings/SettingsApp.tsx @@ -1,5 +1,6 @@ import { useState } from 'react'; import { useAppearanceSettings } from '../../hooks/useAppearanceSettings'; +import { Toaster } from '../../ui/primitives'; import styles from './SettingsApp.module.css'; import { SettingsSidebar } from './components/SettingsSidebar'; import { GeneralSection } from './sections/GeneralSection'; @@ -56,6 +57,7 @@ export function SettingsApp() {
{renderSection()}
+
); } diff --git a/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.module.css b/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.module.css index 1d369cd6..44af1fdc 100644 --- a/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.module.css +++ b/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.module.css @@ -79,3 +79,34 @@ .label { flex: 1; } + +.footer { + padding: 0.5rem 0.75rem 0.75rem; +} + +.separator { + height: 1px; + background: var(--border-subtle); + margin-bottom: 0.5rem; +} + +.resetButton { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.5rem 0.75rem; + border: none; + border-radius: 0.5rem; + background: transparent; + color: var(--text-muted); + font-size: 0.75rem; + cursor: pointer; + transition: all 0.12s ease; + text-align: left; +} + +.resetButton:hover { + background: var(--bg-hover); + color: var(--text-primary); +} diff --git a/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.tsx b/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.tsx index 44371507..48e25ce7 100644 --- a/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.tsx +++ b/apps/desktop/src/renderer/pages/settings/components/SettingsSidebar.tsx @@ -8,7 +8,10 @@ import { Info, Download, Puzzle, + RotateCcw, } from 'lucide-react'; +import { toast } from '../../../ui/primitives'; +import { useSettingsStore } from '../../../stores/settings'; import type { SettingsSection } from '../SettingsApp'; import styles from './SettingsSidebar.module.css'; @@ -31,6 +34,15 @@ const sections: { id: SettingsSection; label: string; Icon: any }[] = [ ]; export function SettingsSidebar({ activeSection, onSectionChange }: SettingsSidebarProps) { + const handleResetAll = () => { + const confirmed = window.confirm( + 'Reset all settings to their default values? This cannot be undone.' + ); + if (!confirmed) return; + useSettingsStore.getState().resetAll(); + toast.info('Settings reset to defaults'); + }; + return ( ); } diff --git a/apps/desktop/src/renderer/pages/settings/sections/AccountSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/AccountSection.tsx index ec9831f0..ae981aef 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/AccountSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/AccountSection.tsx @@ -8,7 +8,6 @@ import { useState, useCallback, useEffect } from 'react'; import { LogIn, LogOut, - Mail, User as UserIcon, RefreshCw, Sparkles, @@ -25,6 +24,7 @@ import { SettingGroup } from '../components/SettingGroup'; import { SettingRow } from '../components/SettingRow'; import { MagicLinkFlow } from '../../../components/auth/MagicLinkFlow'; import { ConflictResolver } from '../../../components/sync/ConflictResolver'; +import { Button } from '../../../ui/primitives'; import { DevicesSection } from './DevicesSection'; import styles from './Section.module.css'; @@ -69,7 +69,7 @@ export function AccountSection() { // Load session on mount useEffect(() => { - loadSession(); + void loadSession(); }, [loadSession]); const loadSyncHistory = useCallback(async () => { @@ -85,7 +85,7 @@ export function AccountSection() { useEffect(() => { if (showHistory) { - loadSyncHistory(); + void loadSyncHistory(); } }, [showHistory, loadSyncHistory]); @@ -119,7 +119,7 @@ export function AccountSection() { try { await syncNow(); setMessage('Sync completed successfully'); - if (showHistory) loadSyncHistory(); + if (showHistory) void loadSyncHistory(); } catch (error) { setMessage(`Sync failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { @@ -227,15 +227,15 @@ export function AccountSection() { - + Sign Out + ) : ( @@ -243,24 +243,15 @@ export function AccountSection() { label="Sign in" description="Sign in to enable sync and access your notes from any device" > - + {isLoading ? 'Loading...' : 'Sign In'} + )} @@ -269,24 +260,15 @@ export function AccountSection() { <> - + {isSyncing || syncStatus === 'syncing' ? 'Syncing...' : 'Sync'} + {syncStatus === 'offline' && ( @@ -363,15 +345,15 @@ export function AccountSection() { label="Manage Subscription" description="Update payment method, change plan, or cancel" > - + {isManaging ? 'Opening...' : 'Manage Subscription'} + )} @@ -381,25 +363,25 @@ export function AccountSection() { description="Get cloud sync, advanced search, and all pro features" >
- - +
)} diff --git a/apps/desktop/src/renderer/pages/settings/sections/AiSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/AiSection.tsx index f3cfd140..48f5ed46 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/AiSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/AiSection.tsx @@ -6,20 +6,12 @@ */ import { useState, useCallback, useEffect, useSyncExternalStore } from 'react'; -import { - Loader2, - CheckCircle, - XCircle, - Upload, - Download, - ExternalLink, - Unplug, - Plug, -} from 'lucide-react'; +import { CheckCircle, XCircle, Upload, Download, ExternalLink, Unplug, Plug } from 'lucide-react'; import { useSettingsStore, selectAi } from '../../../stores/settings'; import { SettingGroup } from '../components/SettingGroup'; import { SettingRow } from '../components/SettingRow'; import { Select, NumberInput } from '../components/controls'; +import { Button } from '../../../ui/primitives'; import { aiCommandStore } from '@readied/plugin-api'; import type { AiCommandRegistration } from '@readied/plugin-api'; import { validateAiCommandPreset, serializePreset } from '@readied/ai-core'; @@ -109,7 +101,7 @@ export function AiSection() { } setConnectStatus(status); } - loadConnected(); + void loadConnected(); }, []); // Fetch Ollama models when it's connected @@ -222,7 +214,7 @@ export function AiSection() { updateAi({ apiKey: key }); } } - loadKeyForProvider(); + void loadKeyForProvider(); }, [currentProvider, updateAi]); const handleExportPreset = useCallback(async () => { @@ -371,15 +363,14 @@ export function AiSection() {
- + ) : ( @@ -412,25 +403,14 @@ export function AiSection() { > Connect your {providerInfo.name} account - +
{ - if (e.key === 'Enter') handleConnect(); + if (e.key === 'Enter') void handleConnect(); }} /> - +
)} @@ -481,20 +457,15 @@ export function AiSection() { Ollama runs locally — no API key needed. Make sure Ollama is running on your machine. - + )} @@ -553,22 +524,26 @@ export function AiSection() { {/* ── Presets ── */} - + - + Export + {presetMessage?.type === 'success' && ( diff --git a/apps/desktop/src/renderer/pages/settings/sections/BackupSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/BackupSection.tsx index 109d971f..be92d1ce 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/BackupSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/BackupSection.tsx @@ -5,10 +5,11 @@ */ import { useState, useCallback } from 'react'; -import { Download, Upload, Archive, FolderOpen, RefreshCw } from 'lucide-react'; +import { Download, Upload, Archive, FolderOpen } from 'lucide-react'; import { useSettingsStore, selectBackup } from '../../../stores/settings'; import { SettingGroup } from '../components/SettingGroup'; import { SettingRow } from '../components/SettingRow'; +import { Button } from '../../../ui/primitives'; import styles from './Section.module.css'; export function BackupSection() { @@ -93,19 +94,15 @@ export function BackupSection() { - + {isExporting ? 'Exporting...' : 'Export'} + @@ -114,44 +111,40 @@ export function BackupSection() { label="Import Notes" description="Import from Obsidian, Markdown folder, or Readied export" > - + {isImporting ? 'Importing...' : 'Import'} + - + {isBackingUp ? 'Backing up...' : 'Backup Now'} + - + diff --git a/apps/desktop/src/renderer/pages/settings/sections/DevicesSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/DevicesSection.tsx index bb25db2f..c4e0e9ff 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/DevicesSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/DevicesSection.tsx @@ -6,6 +6,7 @@ import { useState, useCallback, useRef, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Monitor, Smartphone, Laptop, Trash2, Check, X, LogOut } from 'lucide-react'; import { SettingGroup } from '../components/SettingGroup'; +import { Button } from '../../../ui/primitives'; import styles from './Section.module.css'; interface Device { @@ -206,7 +207,7 @@ export function DevicesSection() { revokeMutation.mutate(confirmRevokeId); setConfirmRevokeId(null); // Current device revoked = logout - window.readied.auth.logout(); + void window.readied.auth.logout(); } }, [confirmRevokeId, revokeMutation]); @@ -242,15 +243,15 @@ export function DevicesSection() { {otherDeviceCount > 0 && (
- + Sign out other devices ({otherDeviceCount}) +
)} @@ -261,16 +262,12 @@ export function DevicesSection() {

This will sign you out of this device. Continue?

- - + +
)} @@ -282,20 +279,12 @@ export function DevicesSection() { Sign out {otherDeviceCount} other device{otherDeviceCount > 1 ? 's' : ''}?

- - + +
)} diff --git a/apps/desktop/src/renderer/pages/settings/sections/GeneralSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/GeneralSection.tsx index 6b71a7c2..a3f89197 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/GeneralSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/GeneralSection.tsx @@ -11,6 +11,7 @@ import { useNotebooks } from '../../../hooks/useNotebooks'; import { SettingGroup } from '../components/SettingGroup'; import { SettingRow } from '../components/SettingRow'; import { Select, Toggle } from '../components/controls'; +import { Button } from '../../../ui/primitives'; import styles from './Section.module.css'; export function GeneralSection() { @@ -74,10 +75,14 @@ export function GeneralSection() { label="Open Data Folder" description="Open the folder containing your notes database" > - + diff --git a/apps/desktop/src/renderer/pages/settings/sections/PluginsSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/PluginsSection.tsx index 464deef2..367c166a 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/PluginsSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/PluginsSection.tsx @@ -21,6 +21,7 @@ import type { PluginConfigSchemaField } from '../../../../preload/index'; import { validateConfigValue } from '@readied/plugin-api'; import { Toggle, TextInput, NumberInput, RangeInput, Select } from '../components/controls'; import { builtInPlugins } from '../../../plugins'; +import { Button, toast } from '../../../ui/primitives'; import styles from './Section.module.css'; // ============================================================================ @@ -63,124 +64,191 @@ const BUILT_IN_CONFIG_SCHEMAS: Record p.category))]; - // ============================================================================ // BrowseTab // ============================================================================ -function BrowseTab() { +function BrowseTab({ installedPluginIds }: { installedPluginIds: Set }) { const [browseSearch, setBrowseSearch] = useState(''); const [browseCategory, setBrowseCategory] = useState('All'); + const [marketplacePlugins, setMarketplacePlugins] = + useState(FALLBACK_PLUGINS); + const [isOffline, setIsOffline] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [installingSlug, setInstallingSlug] = useState(null); + + // Fetch plugins from API on mount + useEffect(() => { + let cancelled = false; + async function fetchPlugins() { + setIsLoading(true); + try { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), 8000); + const response = await fetch(MARKETPLACE_API_URL, { signal: controller.signal }); + clearTimeout(timeoutId); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + + const data = (await response.json()) as { plugins: MarketplacePlugin[]; total: number }; + if (!cancelled && data.plugins && Array.isArray(data.plugins)) { + setMarketplacePlugins(data.plugins); + setIsOffline(false); + } + } catch { + // Offline or API error — fall back to static list + if (!cancelled) { + setMarketplacePlugins(FALLBACK_PLUGINS); + setIsOffline(true); + } + } finally { + if (!cancelled) { + setIsLoading(false); + } + } + } + void fetchPlugins(); + return () => { + cancelled = true; + }; + }, []); + + const categories = useMemo( + () => ['All', ...new Set(marketplacePlugins.map(p => p.category))], + [marketplacePlugins] + ); const filteredMarketplace = useMemo(() => { - let result = MARKETPLACE_PLUGINS; + let result = marketplacePlugins; if (browseCategory !== 'All') { result = result.filter(p => p.category === browseCategory); } @@ -194,10 +262,45 @@ function BrowseTab() { ); } return result; - }, [browseSearch, browseCategory]); + }, [browseSearch, browseCategory, marketplacePlugins]); + + const handleInstallFromUrl = useCallback(async (plugin: MarketplacePlugin) => { + if (!plugin.bundleUrl) { + toast.error('This plugin has no download URL'); + return; + } + + setInstallingSlug(plugin.slug); + try { + const result = await window.readied.plugins.installFromUrl(plugin.bundleUrl, plugin.slug); + if (result.success) { + toast.success(`${plugin.name} installed successfully`); + // Trigger a reload so the installed tab picks it up + window.readied.plugins.requestReload(); + // Notify parent to re-scan installed plugins + window.dispatchEvent(new CustomEvent('readied:plugins:refresh')); + } else { + toast.error(`Failed to install ${plugin.name}: ${result.error ?? 'Unknown error'}`); + } + } catch (error) { + toast.error( + 'Failed to install plugin: ' + (error instanceof Error ? error.message : 'Unknown error') + ); + } finally { + setInstallingSlug(null); + } + }, []); return ( <> + {/* Offline notice */} + {isOffline && ( +
+ + Offline — showing built-in plugins only +
+ )} + {/* Search */}
@@ -212,7 +315,7 @@ function BrowseTab() { {/* Category pills */}
- {MARKETPLACE_CATEGORIES.map(cat => ( + {categories.map(cat => ( + )}
- {plugin.builtin ? ( - - - Included - - ) : ( - - )}
- - ))} + ); + })} ) : (
@@ -516,10 +651,14 @@ function PluginInspector() { )}
- +
)} @@ -538,6 +677,14 @@ export function PluginsSection() { const [pluginsPath, setPluginsPath] = useState(''); const [isReloading, setIsReloading] = useState(false); const [configValues, setConfigValues] = useState>>({}); + const [refreshKey, setRefreshKey] = useState(0); + + // Listen for plugin install events from BrowseTab + useEffect(() => { + const handler = () => setRefreshKey(k => k + 1); + window.addEventListener('readied:plugins:refresh', handler); + return () => window.removeEventListener('readied:plugins:refresh', handler); + }, []); // Load discovered plugins useEffect(() => { @@ -591,15 +738,22 @@ export function PluginsSection() { // Plugin scanning failed - leave empty } } - loadPlugins(); - }, []); + void loadPlugins(); + }, [refreshKey]); // Toggle plugin enabled/disabled const handleToggle = useCallback(async (pluginId: string, enabled: boolean) => { - await window.readied.plugins.setEnabled(pluginId, enabled); - setPlugins(prev => prev.map(p => (p.id === pluginId ? { ...p, enabled } : p))); - // Trigger reload in main window so preview updates immediately - window.readied.plugins.requestReload(); + try { + await window.readied.plugins.setEnabled(pluginId, enabled); + setPlugins(prev => prev.map(p => (p.id === pluginId ? { ...p, enabled } : p))); + // Trigger reload in main window so preview updates immediately + window.readied.plugins.requestReload(); + toast.success(`Plugin ${enabled ? 'enabled' : 'disabled'}`); + } catch (error) { + toast.error( + 'Failed to update plugin: ' + (error instanceof Error ? error.message : 'Unknown error') + ); + } }, []); // Update a plugin config value @@ -639,36 +793,50 @@ export function PluginsSection() { // Install plugin from archive const handleInstall = useCallback(async () => { - const result = await window.readied.plugins.install(); - if (result.success) { - // Re-scan to pick up the new plugin - const [scanned, stateList] = await Promise.all([ - window.readied.plugins.scan(), - window.readied.plugins.listState(), - ]); - const stateMap = new Map(stateList.map(s => [s.pluginId, s.enabled])); - setPlugins( - scanned.map(sp => ({ - id: sp.id, - name: sp.name, - version: sp.version, - description: sp.description, - enabled: stateMap.get(sp.id) ?? true, - configSchema: sp.configSchema, - })) + try { + const result = await window.readied.plugins.install(); + if (result.success) { + // Re-scan to pick up the new plugin + const [scanned, stateList] = await Promise.all([ + window.readied.plugins.scan(), + window.readied.plugins.listState(), + ]); + const stateMap = new Map(stateList.map(s => [s.pluginId, s.enabled])); + setPlugins( + scanned.map(sp => ({ + id: sp.id, + name: sp.name, + version: sp.version, + description: sp.description, + enabled: stateMap.get(sp.id) ?? true, + configSchema: sp.configSchema, + })) + ); + // Trigger reload in main window + window.readied.plugins.requestReload(); + toast.success('Plugin installed successfully'); + } + } catch (error) { + toast.error( + 'Failed to install plugin: ' + (error instanceof Error ? error.message : 'Unknown error') ); - // Trigger reload in main window - window.readied.plugins.requestReload(); } }, []); // Uninstall a community plugin const handleUninstall = useCallback(async (pluginId: string) => { - const result = await window.readied.plugins.uninstall(pluginId); - if (result.success) { - setPlugins(prev => prev.filter(p => p.id !== pluginId)); - // Trigger reload in main window - window.readied.plugins.requestReload(); + try { + const result = await window.readied.plugins.uninstall(pluginId); + if (result.success) { + setPlugins(prev => prev.filter(p => p.id !== pluginId)); + // Trigger reload in main window + window.readied.plugins.requestReload(); + toast.success('Plugin uninstalled successfully'); + } + } catch (error) { + toast.error( + 'Failed to uninstall plugin: ' + (error instanceof Error ? error.message : 'Unknown error') + ); } }, []); @@ -773,14 +941,14 @@ export function PluginsSection() {

No community plugins installed yet.

{pluginsPath && ( - + Open Plugins Folder + )}
@@ -812,31 +980,50 @@ export function PluginsSection() { {/* Actions bar */}
- - + + {isReloading ? 'Reloading...' : 'Reload Plugins'} + {pluginsPath && ( - + )}
)} - {activeTab === 'browse' && } + {activeTab === 'browse' && ( + p.id), + ...plugins.map(p => p.id), + // Include slugified IDs so marketplace slug-based comparison works + ...plugins.map(p => p.name.toLowerCase().replace(/\s+/g, '-')), + ]) + } + /> + )} {import.meta.env.DEV && } diff --git a/apps/desktop/src/renderer/pages/settings/sections/Section.module.css b/apps/desktop/src/renderer/pages/settings/sections/Section.module.css index 9eadc9ea..b843ac0b 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/Section.module.css +++ b/apps/desktop/src/renderer/pages/settings/sections/Section.module.css @@ -396,17 +396,17 @@ .pluginTabs { display: flex; gap: 2px; - padding: 4px; + padding: var(--space-1); background: var(--bg-elevated, var(--bg-tertiary)); - border-radius: 8px; - margin-bottom: 16px; + border-radius: var(--radius-lg); + margin-bottom: var(--space-4); } .pluginTab { flex: 1; - padding: 8px 16px; + padding: var(--space-2) var(--space-4); border: none; - border-radius: 6px; + border-radius: var(--radius-md); background: transparent; color: var(--text-muted, var(--text-tertiary)); font-size: 13px; @@ -433,7 +433,7 @@ .pluginSearchIcon { position: absolute; - left: 12px; + left: var(--space-3); top: 50%; transform: translateY(-50%); color: var(--text-tertiary); @@ -442,10 +442,10 @@ .pluginSearchInput { width: 100%; - padding: 8px 12px 8px 34px; + padding: var(--space-2) var(--space-3) var(--space-2) 34px; background: var(--bg-hover, var(--bg-tertiary)); border: 1px solid var(--border-subtle); - border-radius: 8px; + border-radius: var(--radius-lg); font-size: 13px; color: var(--text-primary); outline: none; @@ -495,11 +495,11 @@ display: flex; flex-wrap: wrap; gap: 6px; - margin-bottom: 16px; + margin-bottom: var(--space-4); } .pluginCategoryPill { - padding: 5px 12px; + padding: 5px var(--space-3); border: 1px solid var(--border-subtle); border-radius: 16px; background: transparent; @@ -546,16 +546,16 @@ display: flex; align-items: center; gap: 10px; - margin-bottom: 8px; + margin-bottom: var(--space-2); } .pluginMarketplaceIcon { display: flex; align-items: center; justify-content: center; - width: 32px; - height: 32px; - border-radius: 8px; + width: var(--space-8); + height: var(--space-8); + border-radius: var(--radius-lg); background: var(--bg-base); font-size: 14px; color: var(--text-tertiary); @@ -583,13 +583,13 @@ .pluginMarketplaceTags { display: flex; - gap: 4px; + gap: var(--space-1); flex-wrap: wrap; } .pluginMarketplaceTag { padding: 2px 6px; - border-radius: 4px; + border-radius: var(--radius-sm); background: var(--bg-base); font-size: 10px; color: var(--text-tertiary); @@ -608,10 +608,10 @@ .pluginMarketplaceInstallBtn { display: inline-flex; align-items: center; - gap: 4px; - padding: 4px 10px; + gap: var(--space-1); + padding: var(--space-1) 10px; border: 1px solid var(--accent-primary); - border-radius: 6px; + border-radius: var(--radius-md); background: transparent; color: var(--accent-primary); font-size: 11px; @@ -621,11 +621,40 @@ flex-shrink: 0; } -.pluginMarketplaceInstallBtn:hover { +.pluginMarketplaceInstallBtn:hover:not(:disabled) { background: var(--accent-primary); color: var(--bg-base); } +.pluginMarketplaceInstallBtn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* Offline notice */ +.pluginOfflineNotice { + display: flex; + align-items: center; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + margin-bottom: var(--space-3); + border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--warning, #f59e0b) 10%, transparent); + color: var(--warning, #f59e0b); + font-size: 0.8125rem; + font-weight: 500; +} + +/* Spinner animation for loading states */ +.plugin-spinner { + animation: plugin-spin 1s linear infinite; +} + +@keyframes plugin-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + /* ============================================================================ Update Progress ============================================================================ */ @@ -716,7 +745,7 @@ .historyStatus { display: inline-block; padding: 0.125rem 0.375rem; - border-radius: 4px; + border-radius: var(--radius-sm); font-size: var(--text-xs); font-weight: 500; } @@ -840,7 +869,7 @@ display: flex; align-items: center; justify-content: center; - padding: 4px; + padding: var(--space-1); border: none; background: none; color: var(--text-tertiary); @@ -857,7 +886,7 @@ display: flex; align-items: center; justify-content: center; - padding: 4px; + padding: var(--space-1); border: none; background: none; color: var(--text-tertiary); diff --git a/apps/desktop/src/renderer/pages/settings/sections/UpdatesSection.tsx b/apps/desktop/src/renderer/pages/settings/sections/UpdatesSection.tsx index 8f6af061..17e39b9d 100644 --- a/apps/desktop/src/renderer/pages/settings/sections/UpdatesSection.tsx +++ b/apps/desktop/src/renderer/pages/settings/sections/UpdatesSection.tsx @@ -5,11 +5,12 @@ */ import { useState, useEffect, useCallback } from 'react'; -import { RefreshCw, Download, RotateCcw } from 'lucide-react'; +import { Download, RotateCcw } from 'lucide-react'; import { useSettingsStore, selectUpdates } from '../../../stores/settings'; import { SettingGroup } from '../components/SettingGroup'; import { SettingRow } from '../components/SettingRow'; import { Toggle } from '../components/controls'; +import { Button } from '../../../ui/primitives'; import styles from './Section.module.css'; type UpdateState = @@ -98,9 +99,13 @@ export function UpdatesSection() { } }, [state]); - const handleInstall = useCallback(() => { + const handleInstall = useCallback(async () => { setState({ status: 'installing' }); - window.readied.updates.installNow(); + try { + await window.readied.updates.installNow(); + } catch { + setState({ status: 'error', message: 'Failed to install update. Please try again.' }); + } }, []); const handleRetry = useCallback(() => { @@ -123,52 +128,65 @@ export function UpdatesSection() { case 'idle': case 'up-to-date': return ( - + ); case 'checking': return ( - + ); case 'available': return ( - + ); case 'downloading': return ( - + ); case 'ready': return ( - + ); case 'installing': return ( - + ); case 'error': return ( - + ); } }; diff --git a/apps/desktop/src/renderer/plugins/exportMarkdown.ts b/apps/desktop/src/renderer/plugins/exportMarkdown.ts index 86eb2bb2..0920046d 100644 --- a/apps/desktop/src/renderer/plugins/exportMarkdown.ts +++ b/apps/desktop/src/renderer/plugins/exportMarkdown.ts @@ -1,10 +1,143 @@ import type { PluginManifest } from '@readied/plugin-api'; +/** + * Build YAML frontmatter for a single note export. + */ +function buildFrontmatter(note: { id?: string; title: string; tags?: string[] }): string { + const escapedTitle = note.title.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + const now = new Date().toISOString(); + const tagsYaml = + note.tags && note.tags.length > 0 ? `tags: [${note.tags.join(', ')}]` : 'tags: []'; + + const lines = ['---']; + if (note.id) lines.push(`id: "${note.id}"`); + lines.push(`title: "${escapedTitle}"`); + lines.push(`exported: ${now}`); + lines.push(tagsYaml); + lines.push('---', ''); + + return lines.join('\n'); +} + +/** + * Convert GFM-flavored markdown table block to HTML . + */ +function convertTable(block: string): string { + const lines = block.trim().split('\n'); + if (lines.length < 2) return block; + + const parseRow = (row: string): string[] => + row + .replace(/^\|/, '') + .replace(/\|$/, '') + .split('|') + .map(cell => cell.trim()); + + const headerLine = lines[0] ?? ''; + const separatorLine = lines[1] ?? ''; + const headerCells = parseRow(headerLine); + + // Verify line 2 is a separator row (e.g. |---|---|) + if (!/^[\s|:-]+$/.test(separatorLine)) return block; + + let html = '
'; + for (const cell of headerCells) { + html += ``; + } + html += ''; + + for (let i = 2; i < lines.length; i++) { + const cells = parseRow(lines[i] ?? ''); + html += ''; + for (const cell of cells) { + html += ``; + } + html += ''; + } + html += '
${cell}
${cell}
'; + return html; +} + +/** + * Convert markdown content to HTML using regex patterns. + * Handles: tables, blockquotes, ordered/unordered lists, images, + * horizontal rules, code blocks, headers, bold, italic, links, inline code. + */ +function markdownToHtml(content: string): string { + let html = content; + + // 1. Code blocks (must come first to protect contents) + html = html.replace(/```(\w*)\n([\s\S]*?)```/g, '
$2
'); + + // 2. Tables — find contiguous lines starting with | + html = html.replace( + /(?:^|\n)((?:\|[^\n]+\n){2,}(?:\|[^\n]+))/g, + (_match, tableBlock: string) => '\n' + convertTable(tableBlock) + ); + + // 3. Horizontal rules (must come before headers to avoid `---` confusion) + html = html.replace(/^(?:---|\*\*\*|___)$/gm, '
'); + + // 4. Headers + html = html.replace(/^### (.+)$/gm, '

$1

'); + html = html.replace(/^## (.+)$/gm, '

$1

'); + html = html.replace(/^# (.+)$/gm, '

$1

'); + + // 5. Blockquotes (consecutive > lines grouped) + html = html.replace(/^(?:>\s?(.+)\n?)+/gm, match => { + const inner = match + .split('\n') + .map(line => line.replace(/^>\s?/, '')) + .filter(Boolean) + .join('
'); + return `
${inner}
`; + }); + + // 6. Images (must come before links: ![alt](url)) + html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '$1'); + + // 7. Bold and italic + html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1'); + html = html.replace(/\*\*(.+?)\*\*/g, '$1'); + html = html.replace(/\*(.+?)\*/g, '$1'); + + // 8. Inline code + html = html.replace(/`([^`]+)`/g, '$1'); + + // 9. Links + html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); + + // 10. Ordered lists — wrap consecutive `1. ` lines + html = html.replace(/(?:^\d+\.\s+.+$\n?)+/gm, match => { + const items = match + .trim() + .split('\n') + .map(line => `
  • ${line.replace(/^\d+\.\s+/, '')}
  • `) + .join(''); + return `
      ${items}
    `; + }); + + // 11. Unordered lists — wrap consecutive `- ` or `* ` lines + html = html.replace(/(?:^[-*]\s+.+$\n?)+/gm, match => { + const items = match + .trim() + .split('\n') + .map(line => `
  • ${line.replace(/^[-*]\s+/, '')}
  • `) + .join(''); + return `
      ${items}
    `; + }); + + // 12. Paragraphs (wrap remaining non-tag lines) + html = html.replace(/^(?!<[hliupcoatb]|<\/)((?!<\/)[^\n]+)$/gm, '

    $1

    '); + + return html; +} + export const exportMarkdownPlugin: PluginManifest = { id: 'readied-export-markdown', name: 'Export Markdown', - version: '1.0.0', - description: 'Copy your notes as raw Markdown or rendered HTML to the clipboard', + version: '1.1.0', + description: 'Copy notes as Markdown or HTML, or export to file', activate(context) { const unregisterCopyMd = context.registerCommand( @@ -17,7 +150,7 @@ export const exportMarkdownPlugin: PluginManifest = { () => { const content = context.editor.getContent(); if (!content) return false; - navigator.clipboard.writeText(content); + void navigator.clipboard.writeText(content); context.log.info('Markdown copied to clipboard'); return true; } @@ -33,28 +166,9 @@ export const exportMarkdownPlugin: PluginManifest = { const content = context.editor.getContent(); if (!content) return false; - // Basic markdown-to-HTML conversion for clipboard - const html = content - // Code blocks (must come before inline code) - .replace(/```(\w*)\n([\s\S]*?)```/g, '
    $2
    ') - // Headers - .replace(/^### (.+)$/gm, '

    $1

    ') - .replace(/^## (.+)$/gm, '

    $1

    ') - .replace(/^# (.+)$/gm, '

    $1

    ') - // Bold and italic - .replace(/\*\*\*(.+?)\*\*\*/g, '$1') - .replace(/\*\*(.+?)\*\*/g, '$1') - .replace(/\*(.+?)\*/g, '$1') - // Inline code - .replace(/`([^`]+)`/g, '$1') - // Links - .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1') - // Unordered lists - .replace(/^[-*] (.+)$/gm, '
  • $1
  • ') - // Paragraphs (wrap remaining lines) - .replace(/^(?!<[hliupca])((?!<\/)[^\n]+)$/gm, '

    $1

    '); - - navigator.clipboard.write([ + const html = markdownToHtml(content); + + void navigator.clipboard.write([ new ClipboardItem({ 'text/html': new Blob([html], { type: 'text/html' }), 'text/plain': new Blob([content], { type: 'text/plain' }), @@ -65,10 +179,50 @@ export const exportMarkdownPlugin: PluginManifest = { } ); + const unregisterExportFile = context.registerCommand( + { + id: 'export-file', + name: 'Export Note to File', + icon: 'Download', + }, + () => { + const content = context.editor.getContent(); + if (!content) return false; + + const note = context.app.getCurrentNote(); + const title = note?.title ?? 'Untitled'; + + // Build content with frontmatter if not already present + let exportContent = content; + if (!content.trimStart().startsWith('---')) { + const frontmatter = buildFrontmatter({ + id: note?.id, + title, + }); + exportContent = frontmatter + content; + } + + // Use IPC to show save dialog and write file + void window.readied.data + .exportNote(exportContent, title) + .then(result => { + if (result.success) { + context.log.info(`Note exported to ${result.path}`); + } + }) + .catch(() => { + context.log.error('Failed to export note'); + }); + + return true; + } + ); + return { dispose() { unregisterCopyMd(); unregisterCopyHtml(); + unregisterExportFile(); }, }; }, diff --git a/apps/desktop/src/renderer/stores/__tests__/editorBufferStore.test.ts b/apps/desktop/src/renderer/stores/__tests__/editorBufferStore.test.ts new file mode 100644 index 00000000..ea752633 --- /dev/null +++ b/apps/desktop/src/renderer/stores/__tests__/editorBufferStore.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { + useEditorBufferStore, + selectLiveContent, + selectNoteId, + selectIsDirty, + selectContentForNote, +} from '../editorBufferStore'; + +describe('editorBufferStore', () => { + beforeEach(() => { + useEditorBufferStore.setState({ + noteId: null, + liveContent: '', + isDirty: false, + }); + }); + + describe('initial state', () => { + it('starts with null noteId and empty content', () => { + const state = useEditorBufferStore.getState(); + expect(state.noteId).toBeNull(); + expect(state.liveContent).toBe(''); + expect(state.isDirty).toBe(false); + }); + }); + + describe('setNote', () => { + it('sets noteId and content, marks clean', () => { + useEditorBufferStore.getState().setNote('note-1', '# Hello'); + + const state = useEditorBufferStore.getState(); + expect(state.noteId).toBe('note-1'); + expect(state.liveContent).toBe('# Hello'); + expect(state.isDirty).toBe(false); + }); + + it('switching notes resets dirty flag', () => { + const store = useEditorBufferStore.getState(); + store.setNote('note-1', 'content'); + store.updateBuffer('changed'); + expect(useEditorBufferStore.getState().isDirty).toBe(true); + + store.setNote('note-2', 'other'); + expect(useEditorBufferStore.getState().isDirty).toBe(false); + }); + }); + + describe('updateBuffer', () => { + it('marks dirty when content changes', () => { + const store = useEditorBufferStore.getState(); + store.setNote('note-1', 'original'); + store.updateBuffer('modified'); + + const state = useEditorBufferStore.getState(); + expect(state.liveContent).toBe('modified'); + expect(state.isDirty).toBe(true); + }); + + it('does not mark dirty when content is the same', () => { + const store = useEditorBufferStore.getState(); + store.setNote('note-1', 'same'); + store.updateBuffer('same'); + + expect(useEditorBufferStore.getState().isDirty).toBe(false); + }); + }); + + describe('markClean', () => { + it('resets isDirty to false', () => { + const store = useEditorBufferStore.getState(); + store.setNote('note-1', 'a'); + store.updateBuffer('b'); + expect(useEditorBufferStore.getState().isDirty).toBe(true); + + store.markClean(); + expect(useEditorBufferStore.getState().isDirty).toBe(false); + }); + }); + + describe('clear', () => { + it('resets all state', () => { + const store = useEditorBufferStore.getState(); + store.setNote('note-1', 'content'); + store.updateBuffer('changed'); + + store.clear(); + const state = useEditorBufferStore.getState(); + expect(state.noteId).toBeNull(); + expect(state.liveContent).toBe(''); + expect(state.isDirty).toBe(false); + }); + }); + + describe('selectors', () => { + it('selectLiveContent returns content', () => { + useEditorBufferStore.getState().setNote('n', 'hello'); + expect(selectLiveContent(useEditorBufferStore.getState())).toBe('hello'); + }); + + it('selectNoteId returns noteId', () => { + useEditorBufferStore.getState().setNote('abc', ''); + expect(selectNoteId(useEditorBufferStore.getState())).toBe('abc'); + }); + + it('selectIsDirty reflects dirty state', () => { + const store = useEditorBufferStore.getState(); + expect(selectIsDirty(useEditorBufferStore.getState())).toBe(false); + store.setNote('n', 'a'); + store.updateBuffer('b'); + expect(selectIsDirty(useEditorBufferStore.getState())).toBe(true); + }); + + it('selectContentForNote returns content only for matching noteId', () => { + useEditorBufferStore.getState().setNote('note-1', 'my content'); + + expect(selectContentForNote('note-1')(useEditorBufferStore.getState())).toBe('my content'); + expect(selectContentForNote('note-2')(useEditorBufferStore.getState())).toBeNull(); + expect(selectContentForNote(null)(useEditorBufferStore.getState())).toBeNull(); + }); + }); +}); diff --git a/apps/desktop/src/renderer/stores/__tests__/editorPreferencesStore.test.ts b/apps/desktop/src/renderer/stores/__tests__/editorPreferencesStore.test.ts new file mode 100644 index 00000000..5d0dfe07 --- /dev/null +++ b/apps/desktop/src/renderer/stores/__tests__/editorPreferencesStore.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { + useEditorPreferencesStore, + selectViewMode, + selectIsEditorVisible, + selectIsPreviewVisible, +} from '../editorPreferencesStore'; + +describe('editorPreferencesStore', () => { + beforeEach(() => { + useEditorPreferencesStore.setState({ viewMode: 'editor' }); + }); + + describe('initial state', () => { + it('starts in editor mode', () => { + expect(useEditorPreferencesStore.getState().viewMode).toBe('editor'); + }); + }); + + describe('setViewMode', () => { + it('sets view mode directly', () => { + useEditorPreferencesStore.getState().setViewMode('split'); + expect(useEditorPreferencesStore.getState().viewMode).toBe('split'); + }); + + it('can set to preview', () => { + useEditorPreferencesStore.getState().setViewMode('preview'); + expect(useEditorPreferencesStore.getState().viewMode).toBe('preview'); + }); + }); + + describe('cycleViewMode', () => { + it('cycles editor → split → preview → editor', () => { + expect(useEditorPreferencesStore.getState().viewMode).toBe('editor'); + + useEditorPreferencesStore.getState().cycleViewMode(); + expect(useEditorPreferencesStore.getState().viewMode).toBe('split'); + + useEditorPreferencesStore.getState().cycleViewMode(); + expect(useEditorPreferencesStore.getState().viewMode).toBe('preview'); + + useEditorPreferencesStore.getState().cycleViewMode(); + expect(useEditorPreferencesStore.getState().viewMode).toBe('editor'); + }); + }); + + describe('selectors', () => { + it('selectViewMode returns current mode', () => { + useEditorPreferencesStore.getState().setViewMode('split'); + expect(selectViewMode(useEditorPreferencesStore.getState())).toBe('split'); + }); + + it('selectIsEditorVisible: true for editor and split', () => { + useEditorPreferencesStore.setState({ viewMode: 'editor' }); + expect(selectIsEditorVisible(useEditorPreferencesStore.getState())).toBe(true); + + useEditorPreferencesStore.setState({ viewMode: 'split' }); + expect(selectIsEditorVisible(useEditorPreferencesStore.getState())).toBe(true); + + useEditorPreferencesStore.setState({ viewMode: 'preview' }); + expect(selectIsEditorVisible(useEditorPreferencesStore.getState())).toBe(false); + }); + + it('selectIsPreviewVisible: true for preview and split', () => { + useEditorPreferencesStore.setState({ viewMode: 'preview' }); + expect(selectIsPreviewVisible(useEditorPreferencesStore.getState())).toBe(true); + + useEditorPreferencesStore.setState({ viewMode: 'split' }); + expect(selectIsPreviewVisible(useEditorPreferencesStore.getState())).toBe(true); + + useEditorPreferencesStore.setState({ viewMode: 'editor' }); + expect(selectIsPreviewVisible(useEditorPreferencesStore.getState())).toBe(false); + }); + }); +}); diff --git a/apps/desktop/src/renderer/stores/__tests__/performanceStore.test.ts b/apps/desktop/src/renderer/stores/__tests__/performanceStore.test.ts new file mode 100644 index 00000000..2d9b98c5 --- /dev/null +++ b/apps/desktop/src/renderer/stores/__tests__/performanceStore.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { + usePerformanceStore, + selectMode, + selectBaseMode, + selectIsResizing, +} from '../performanceStore'; + +describe('performanceStore', () => { + // Reset to initial state before each test. For the "initial state" test below, + // this reset effectively sets the store to its default values, which is what + // we assert against — Zustand stores are singletons shared across tests. + beforeEach(() => { + usePerformanceStore.setState({ + mode: 'medium', + baseMode: 'medium', + isResizing: false, + }); + }); + + describe('initial state', () => { + it('starts with medium mode', () => { + const state = usePerformanceStore.getState(); + expect(state.mode).toBe('medium'); + expect(state.baseMode).toBe('medium'); + expect(state.isResizing).toBe(false); + }); + }); + + describe('setMode', () => { + it('updates active mode', () => { + usePerformanceStore.getState().setMode('low'); + expect(usePerformanceStore.getState().mode).toBe('low'); + }); + + it('does not affect baseMode', () => { + usePerformanceStore.getState().setMode('low'); + expect(usePerformanceStore.getState().baseMode).toBe('medium'); + }); + }); + + describe('setBaseMode', () => { + it('updates base mode', () => { + usePerformanceStore.getState().setBaseMode('high'); + expect(usePerformanceStore.getState().baseMode).toBe('high'); + }); + }); + + describe('setResizing', () => { + it('toggles resize state', () => { + usePerformanceStore.getState().setResizing(true); + expect(usePerformanceStore.getState().isResizing).toBe(true); + + usePerformanceStore.getState().setResizing(false); + expect(usePerformanceStore.getState().isResizing).toBe(false); + }); + }); + + describe('selectors', () => { + it('selectMode returns current mode', () => { + usePerformanceStore.getState().setMode('high'); + expect(selectMode(usePerformanceStore.getState())).toBe('high'); + }); + + it('selectBaseMode returns base mode', () => { + usePerformanceStore.getState().setBaseMode('low'); + expect(selectBaseMode(usePerformanceStore.getState())).toBe('low'); + }); + + it('selectIsResizing returns resize state', () => { + expect(selectIsResizing(usePerformanceStore.getState())).toBe(false); + usePerformanceStore.getState().setResizing(true); + expect(selectIsResizing(usePerformanceStore.getState())).toBe(true); + }); + }); +}); diff --git a/apps/desktop/src/renderer/stores/pluginRuntimeStore.ts b/apps/desktop/src/renderer/stores/pluginRuntimeStore.ts index db90e87b..593c050e 100644 --- a/apps/desktop/src/renderer/stores/pluginRuntimeStore.ts +++ b/apps/desktop/src/renderer/stores/pluginRuntimeStore.ts @@ -57,7 +57,7 @@ function attachIpcListener() { if (listenerAttached) return; listenerAttached = true; window.readied.ipc.on('plugins:reload', () => { - pluginRuntimeStore.getState().reload(); + void pluginRuntimeStore.getState().reload(); }); } diff --git a/apps/desktop/src/renderer/stores/syncStore.ts b/apps/desktop/src/renderer/stores/syncStore.ts index cc537e55..71fc5b71 100644 --- a/apps/desktop/src/renderer/stores/syncStore.ts +++ b/apps/desktop/src/renderer/stores/syncStore.ts @@ -240,7 +240,7 @@ export const useSyncStore = create()((set, get) => ({ */ initSyncStatusListener: () => { // Fetch initial pending count - get().refreshPendingCount(); + void get().refreshPendingCount(); const unsubscribe = window.readied.sync.onStatusChange((raw: unknown) => { const event = raw as SyncStatusEvent; @@ -258,7 +258,7 @@ export const useSyncStore = create()((set, get) => ({ lastSyncAt: Date.now(), }); // Refresh pending count after successful sync - get().refreshPendingCount(); + void get().refreshPendingCount(); break; case 'sync-error': { @@ -269,7 +269,7 @@ export const useSyncStore = create()((set, get) => ({ consecutiveFailures: event.consecutiveFailures ?? 0, }); // Refresh pending count so user sees queued changes - get().refreshPendingCount(); + void get().refreshPendingCount(); break; } diff --git a/apps/desktop/src/renderer/styles/global.css b/apps/desktop/src/renderer/styles/global.css index 76dd481e..b4d5ad85 100644 --- a/apps/desktop/src/renderer/styles/global.css +++ b/apps/desktop/src/renderer/styles/global.css @@ -8,6 +8,7 @@ @import './toolbar.css'; @import './preview.css'; @import './lightbox.css'; +@import './tables.css'; * { margin: 0; @@ -1589,6 +1590,66 @@ input:focus-visible { line-height: 1.3; } +/* Sync progress indicator */ +.sidebar-footer-progress { + display: flex; + align-items: center; + gap: 4px; + font-size: var(--text-xs); + line-height: 1; + padding: 2px 0; + animation: sidebar-footer-progress-fadein 200ms ease-out; +} + +@keyframes sidebar-footer-progress-fadein { + from { opacity: 0; transform: translateY(2px); } + to { opacity: 1; transform: translateY(0); } +} + +.sidebar-footer-progress--syncing { + color: var(--accent); +} + +.sidebar-footer-progress--synced { + color: var(--success); + animation: sidebar-footer-progress-fadein 200ms ease-out, + sidebar-footer-progress-fadeout 500ms ease-in 2.5s forwards; +} + +@keyframes sidebar-footer-progress-fadeout { + from { opacity: 1; } + to { opacity: 0; } +} + +.sidebar-footer-progress--error { + color: var(--danger); +} + +.sidebar-footer-progress--offline { + color: var(--text-muted); +} + +.sidebar-footer-progress--pending { + color: var(--text-muted); +} + +.sidebar-footer-progress-retry { + background: none; + border: none; + color: var(--accent); + font-size: var(--text-xs); + cursor: pointer; + padding: 0; + margin-left: 2px; + text-decoration: underline; + text-underline-offset: 2px; +} + +.sidebar-footer-progress-retry:hover { + color: var(--accent-hover, var(--accent)); + text-decoration: none; +} + .sidebar-footer-version { font-size: var(--text-xs); color: var(--text-faint); diff --git a/apps/desktop/src/renderer/styles/note-list.css b/apps/desktop/src/renderer/styles/note-list.css index 1aeaa02e..afafd7d2 100644 --- a/apps/desktop/src/renderer/styles/note-list.css +++ b/apps/desktop/src/renderer/styles/note-list.css @@ -122,6 +122,7 @@ position: relative; display: flex; align-items: center; + gap: 4px; } .search-icon { @@ -134,7 +135,8 @@ } .search-input { - width: 100%; + flex: 1; + min-width: 0; padding: 8px 32px 8px 36px; background: var(--bg-surface); border: 1px solid var(--border); diff --git a/apps/desktop/src/renderer/styles/preview.css b/apps/desktop/src/renderer/styles/preview.css index 00e69dc4..55dd01ae 100644 --- a/apps/desktop/src/renderer/styles/preview.css +++ b/apps/desktop/src/renderer/styles/preview.css @@ -322,9 +322,14 @@ } /* Tables (GFM) */ +.markdown-preview .table-wrapper { + overflow-x: auto; + margin: 1em 0; + border-radius: var(--radius-md); +} + .markdown-preview table { width: 100%; - margin: 1em 0; border-collapse: collapse; font-size: 0.9em; } @@ -333,7 +338,6 @@ .markdown-preview td { padding: 10px 12px; border: 1px solid var(--border); - text-align: left; } .markdown-preview th { @@ -346,6 +350,14 @@ background: var(--bg-surface); } +.markdown-preview tbody tr { + transition: background 0.15s ease; +} + +.markdown-preview tbody tr:hover { + background: var(--bg-hover); +} + /* Horizontal rule */ .markdown-preview hr { margin: 2em 0; @@ -546,6 +558,19 @@ background: transparent; } +/* Save indicator in toolbar row */ +.save-indicator { + margin-left: auto; + font-size: 12px; + color: var(--text-muted); + transition: opacity 0.3s ease; + user-select: none; +} + +.save-indicator--fade { + opacity: 0.7; +} + /* ============================================================================ Plugin Error Boundaries ============================================================================ */ diff --git a/apps/desktop/src/renderer/styles/tables.css b/apps/desktop/src/renderer/styles/tables.css new file mode 100644 index 00000000..2e898092 --- /dev/null +++ b/apps/desktop/src/renderer/styles/tables.css @@ -0,0 +1,173 @@ +/** + * Readied Desktop - Table Styles + * + * Covers: + * A. Editor WYSIWYG tables (.cm-table-widget / .cm-table-visual) + * B. Insert-table modal + * C. Sortable preview tables + */ + +/* =================================================================== + A. Editor WYSIWYG Tables + =================================================================== */ + +.cm-table-widget { + overflow-x: auto; + margin: var(--space-3) 0; + border-radius: var(--radius-md); + overflow: hidden; /* clip child table to border-radius */ +} + +.cm-table-visual { + width: 100%; + border-collapse: collapse; + font-size: 0.9em; +} + +.cm-table-visual thead { + background: var(--bg-elevated); +} + +.cm-table-visual th { + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border); + font-weight: 600; + color: var(--text-primary); +} + +.cm-table-visual td { + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border); + color: var(--text-primary); +} + +.cm-table-visual tr:nth-child(even) { + background: var(--bg-surface); +} + +.cm-table-visual tbody tr:hover { + background: var(--bg-hover); + transition: background 0.15s ease; +} + +/* =================================================================== + B. Insert Table Modal + =================================================================== */ + +.insert-table-modal { + background: var(--glass-bg); + backdrop-filter: blur(var(--glass-blur)); + -webkit-backdrop-filter: blur(var(--glass-blur)); + border: 1px solid var(--glass-border); + box-shadow: var(--glass-shadow); + border-radius: var(--radius-lg); + padding: var(--space-3); + min-width: 180px; + user-select: none; +} + +.insert-table-modal-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--space-2); +} + +.insert-table-modal-title { + font-weight: 600; + font-size: 0.85em; + color: var(--text-primary); +} + +.insert-table-modal-size { + font-size: 0.8em; + color: var(--text-muted); + font-variant-numeric: tabular-nums; +} + +.insert-table-modal-grid { + display: flex; + flex-direction: column; + gap: 2px; +} + +.insert-table-modal-row { + display: flex; + gap: 2px; +} + +.insert-table-modal-cell { + width: 20px; + height: 20px; + border: 1px solid var(--border-subtle); + border-radius: 2px; + background: var(--bg-elevated); + cursor: pointer; + padding: 0; + transition: background 0.1s ease, border-color 0.1s ease; +} + +.insert-table-modal-cell:hover { + border-color: var(--accent); +} + +.insert-table-modal-cell.active { + background: var(--accent-muted); + border-color: var(--accent); +} + +/* =================================================================== + C. Sortable Preview Tables + =================================================================== */ + +.sortable-table { + width: 100%; + margin: 1em 0; + border-collapse: collapse; + font-size: 0.9em; +} + +.sortable-table th, +.sortable-table td { + padding: 10px 12px; + border: 1px solid var(--border); +} + +.sortable-table th { + background: var(--bg-elevated); + font-weight: 600; + color: var(--text-primary); +} + +.sortable-table tr:nth-child(even) { + background: var(--bg-surface); +} + +.sortable-table tbody tr:hover { + background: var(--bg-hover); + transition: background 0.15s ease; +} + +.sortable-th { + cursor: pointer; + user-select: none; + transition: color 0.15s ease; +} + +.sortable-th:hover { + color: var(--accent); +} + +.sortable-th.sorted { + color: var(--accent); +} + +.sort-indicator { + color: var(--text-muted); + font-size: 0.75em; + margin-left: 4px; +} + +.sortable-th.sorted .sort-indicator { + color: var(--accent); +} diff --git a/apps/desktop/src/renderer/ui/patterns/Modal.module.css b/apps/desktop/src/renderer/ui/patterns/Modal.module.css new file mode 100644 index 00000000..2b0a855e --- /dev/null +++ b/apps/desktop/src/renderer/ui/patterns/Modal.module.css @@ -0,0 +1,122 @@ +/* ============================================================================= + Modal Pattern + Glass-effect modal with overlay, scale animation, and portal rendering. + ============================================================================= */ + +/* ── Overlay ─────────────────────────────────────────────────────────────── */ + +.overlay { + position: fixed; + inset: 0; + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); + -webkit-backdrop-filter: blur(4px); + animation: fade-in var(--transition-normal) ease both; +} + +/* ── Content ─────────────────────────────────────────────────────────────── */ + +.content { + position: relative; + width: 100%; + max-height: calc(100vh - 80px); + overflow-y: auto; + background: var(--glass-bg); + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + border: 1px solid var(--glass-border); + border-radius: var(--radius-xl); + box-shadow: var(--glass-shadow); + animation: scale-in var(--transition-normal) ease both; +} + +/* ── Sizes ───────────────────────────────────────────────────────────────── */ + +.sm { + max-width: 360px; +} + +.md { + max-width: 480px; +} + +.lg { + max-width: 640px; +} + +/* ── Header ──────────────────────────────────────────────────────────────── */ + +.header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--space-4) var(--space-5); + border-bottom: 1px solid var(--border-subtle); +} + +.title { + margin: 0; + font-family: var(--font-sans); + font-size: var(--text-lg); + font-weight: var(--font-weight-semibold); + line-height: var(--leading-tight); + letter-spacing: var(--tracking-tight); + color: var(--text-primary); +} + +.closeButton { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + padding: 0; + margin: 0; + background: transparent; + border: none; + border-radius: var(--radius-md); + color: var(--text-muted); + cursor: pointer; + transition: background var(--transition-fast), color var(--transition-fast); +} + +.closeButton:hover { + background: var(--bg-hover); + color: var(--text-primary); +} + +.closeButton:active { + background: var(--bg-active); +} + +/* ── Body ────────────────────────────────────────────────────────────────── */ + +.body { + padding: var(--space-5); +} + +/* ── Animations ──────────────────────────────────────────────────────────── */ + +@keyframes fade-in { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +@keyframes scale-in { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} diff --git a/apps/desktop/src/renderer/ui/patterns/Modal.tsx b/apps/desktop/src/renderer/ui/patterns/Modal.tsx new file mode 100644 index 00000000..288456ca --- /dev/null +++ b/apps/desktop/src/renderer/ui/patterns/Modal.tsx @@ -0,0 +1,98 @@ +import { useCallback, useEffect, useRef, type ReactNode } from 'react'; +import { createPortal } from 'react-dom'; +import styles from './Modal.module.css'; + +export interface ModalProps { + open: boolean; + onClose: () => void; + title?: string; + children: ReactNode; + size?: 'sm' | 'md' | 'lg'; + closeOnOverlay?: boolean; + closeOnEscape?: boolean; +} + +export function Modal({ + open, + onClose, + title, + children, + size = 'md', + closeOnOverlay = true, + closeOnEscape = true, +}: ModalProps) { + const handleKeyDown = useCallback( + (e: KeyboardEvent) => { + if (closeOnEscape && e.key === 'Escape') { + onClose(); + } + }, + [closeOnEscape, onClose] + ); + + useEffect(() => { + if (!open) return; + document.addEventListener('keydown', handleKeyDown); + return () => document.removeEventListener('keydown', handleKeyDown); + }, [open, handleKeyDown]); + + const handleOverlayClick = useCallback( + (e: React.MouseEvent) => { + if (closeOnOverlay && e.target === e.currentTarget) { + onClose(); + } + }, + [closeOnOverlay, onClose] + ); + + const contentRef = useRef(null); + + // Focus the modal container on open + useEffect(() => { + if (open && contentRef.current) { + contentRef.current.focus(); + } + }, [open]); + + if (!open) return null; + + const titleId = title != null ? 'modal-title' : undefined; + + return createPortal( +
    +
    + {title != null && ( +
    + + +
    + )} +
    {children}
    +
    +
    , + document.body + ); +} diff --git a/apps/desktop/src/renderer/ui/patterns/index.ts b/apps/desktop/src/renderer/ui/patterns/index.ts new file mode 100644 index 00000000..05844a90 --- /dev/null +++ b/apps/desktop/src/renderer/ui/patterns/index.ts @@ -0,0 +1,2 @@ +export { Modal } from './Modal'; +export type { ModalProps } from './Modal'; diff --git a/apps/desktop/src/renderer/ui/primitives/.gitkeep b/apps/desktop/src/renderer/ui/primitives/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/desktop/src/renderer/ui/primitives/Button.module.css b/apps/desktop/src/renderer/ui/primitives/Button.module.css new file mode 100644 index 00000000..451fe307 --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/Button.module.css @@ -0,0 +1,144 @@ +/* ============================================================================= + Button — Primitive + Variants: primary, secondary, danger, ghost + Sizes: sm, md + ============================================================================= */ + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + font-family: var(--font-sans); + font-weight: 500; + border: 1px solid transparent; + cursor: pointer; + transition: + background var(--transition-fast), + border-color var(--transition-fast), + color var(--transition-fast), + opacity var(--transition-fast); + user-select: none; + white-space: nowrap; + line-height: 1; +} + +.button:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +/* ── Sizes ─────────────────────────────────────────────────────────────────── */ + +.sm { + height: 28px; + padding: 0 var(--space-3); + font-size: var(--text-xs); + border-radius: var(--radius-sm); +} + +.md { + height: 32px; + padding: 0 var(--space-4); + font-size: var(--text-sm); + border-radius: var(--radius-md); +} + +/* ── Variants ──────────────────────────────────────────────────────────────── */ + +.primary { + background: var(--accent); + color: var(--bg-base); + border-color: var(--accent); +} + +.primary:hover:not(:disabled) { + background: var(--accent-hover); + border-color: var(--accent-hover); +} + +.primary:active:not(:disabled) { + opacity: 0.85; +} + +.secondary { + background: transparent; + color: var(--text-secondary); + border-color: var(--border); +} + +.secondary:hover:not(:disabled) { + background: var(--bg-hover); + border-color: var(--border-strong); + color: var(--text-primary); +} + +.secondary:active:not(:disabled) { + background: var(--bg-active); +} + +.danger { + background: var(--danger-muted); + color: var(--danger); + border-color: transparent; +} + +.danger:hover:not(:disabled) { + background: rgba(248, 113, 113, 0.25); +} + +.danger:active:not(:disabled) { + background: rgba(248, 113, 113, 0.3); +} + +.ghost { + background: transparent; + color: var(--text-secondary); + border-color: transparent; +} + +.ghost:hover:not(:disabled) { + background: var(--bg-hover); + color: var(--text-primary); +} + +.ghost:active:not(:disabled) { + background: var(--bg-active); +} + +/* ── States ────────────────────────────────────────────────────────────────── */ + +.button:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.loading { + pointer-events: none; +} + +/* ── Icon ──────────────────────────────────────────────────────────────────── */ + +.icon { + display: inline-flex; + align-items: center; + flex-shrink: 0; +} + +/* ── Spinner ───────────────────────────────────────────────────────────────── */ + +.spinner { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid currentcolor; + border-right-color: transparent; + border-radius: 50%; + animation: spin 600ms linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/apps/desktop/src/renderer/ui/primitives/Button.tsx b/apps/desktop/src/renderer/ui/primitives/Button.tsx new file mode 100644 index 00000000..4ca9d667 --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/Button.tsx @@ -0,0 +1,59 @@ +/** + * Button — Design system primitive + * + * Variants: primary | secondary | danger | ghost + * Sizes: sm | md + */ + +import { type ButtonHTMLAttributes, type ReactNode, forwardRef } from 'react'; +import styles from './Button.module.css'; + +export interface ButtonProps extends Omit, 'type'> { + variant?: 'primary' | 'secondary' | 'danger' | 'ghost'; + size?: 'sm' | 'md'; + loading?: boolean; + icon?: ReactNode; + type?: 'button' | 'submit' | 'reset'; +} + +export const Button = forwardRef( + ( + { + variant = 'secondary', + size = 'md', + loading = false, + disabled = false, + icon, + children, + className, + type = 'button', + ...rest + }, + ref + ) => { + const cls = [ + styles.button, + styles[variant], + styles[size], + loading ? styles.loading : '', + className ?? '', + ] + .filter(Boolean) + .join(' '); + + return ( + + ); + } +); + +Button.displayName = 'Button'; diff --git a/apps/desktop/src/renderer/ui/primitives/Toast.module.css b/apps/desktop/src/renderer/ui/primitives/Toast.module.css new file mode 100644 index 00000000..8946554b --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/Toast.module.css @@ -0,0 +1,141 @@ +/* ============================================================================= + Toast — Primitive + Fixed bottom-right container with slide-in/out notifications. + ============================================================================= */ + +.container { + position: fixed; + bottom: var(--space-4); + right: var(--space-4); + z-index: 9999; + display: flex; + flex-direction: column; + gap: var(--space-2); + pointer-events: none; + max-width: 360px; +} + +/* ── Individual Toast ──────────────────────────────────────────────────────── */ + +.toast { + display: flex; + align-items: flex-start; + gap: var(--space-3); + padding: var(--space-3) var(--space-4); + border-radius: var(--radius-lg); + font-family: var(--font-sans); + font-size: var(--text-sm); + line-height: 1.4; + color: var(--text-primary); + background: var(--glass-bg); + backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate)); + border: 1px solid var(--glass-border); + box-shadow: var(--glass-shadow); + pointer-events: auto; + animation: slide-in var(--transition-normal) ease forwards; + min-width: 240px; +} + +.toast.exiting { + animation: slide-out var(--transition-fast) ease forwards; +} + +/* ── Type Accents ──────────────────────────────────────────────────────────── */ + +.success { + border-left: 3px solid var(--success); +} + +.error { + border-left: 3px solid var(--danger); +} + +.info { + border-left: 3px solid var(--accent); +} + +.warning { + border-left: 3px solid var(--warning); +} + +/* ── Indicator Dot ─────────────────────────────────────────────────────────── */ + +.dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; + margin-top: 3px; +} + +.success .dot { + background: var(--success); +} + +.error .dot { + background: var(--danger); +} + +.info .dot { + background: var(--accent); +} + +.warning .dot { + background: var(--warning); +} + +/* ── Message ───────────────────────────────────────────────────────────────── */ + +.message { + flex: 1; + overflow-wrap: break-word; +} + +/* ── Dismiss Button ────────────────────────────────────────────────────────── */ + +.dismiss { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + flex-shrink: 0; + background: transparent; + border: none; + border-radius: var(--radius-sm); + color: var(--text-muted); + cursor: pointer; + font-size: var(--text-base); + line-height: 1; + padding: 0; + transition: color var(--transition-fast), background var(--transition-fast); +} + +.dismiss:hover { + color: var(--text-primary); + background: var(--bg-hover); +} + +/* ── Animations ────────────────────────────────────────────────────────────── */ + +@keyframes slide-in { + from { + opacity: 0; + transform: translateX(24px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes slide-out { + from { + opacity: 1; + transform: translateX(0); + } + to { + opacity: 0; + transform: translateX(24px); + } +} diff --git a/apps/desktop/src/renderer/ui/primitives/Toast.tsx b/apps/desktop/src/renderer/ui/primitives/Toast.tsx new file mode 100644 index 00000000..29467a8e --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/Toast.tsx @@ -0,0 +1,63 @@ +/** + * Toast — Design system primitive + * + * Renders toast notifications from the toast store. + * Place once in your app root. + */ + +import { useEffect, useCallback, useState } from 'react'; +import { useToastStore, type ToastItem } from './toastStore'; +import styles from './Toast.module.css'; + +// ============================================================================ +// Single Toast +// ============================================================================ + +function ToastNotification({ item }: { item: ToastItem }) { + const dismissToast = useToastStore(s => s.dismissToast); + const [exiting, setExiting] = useState(false); + + const dismiss = useCallback(() => { + setExiting(true); + // Wait for exit animation before removing from store + setTimeout(() => dismissToast(item.id), 150); + }, [item.id, dismissToast]); + + useEffect(() => { + if (item.duration <= 0) return; + const timer = setTimeout(dismiss, item.duration); + return () => clearTimeout(timer); + }, [item.duration, dismiss]); + + const cls = [styles.toast, styles[item.type], exiting ? styles.exiting : ''] + .filter(Boolean) + .join(' '); + + return ( +
    + + {item.message} + +
    + ); +} + +// ============================================================================ +// Toaster Container +// ============================================================================ + +export function Toaster() { + const toasts = useToastStore(s => s.toasts); + + if (toasts.length === 0) return null; + + return ( +
    + {toasts.map(item => ( + + ))} +
    + ); +} diff --git a/apps/desktop/src/renderer/ui/primitives/index.ts b/apps/desktop/src/renderer/ui/primitives/index.ts new file mode 100644 index 00000000..968e932d --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/index.ts @@ -0,0 +1,12 @@ +/** + * Design System Primitives + * + * Re-exports all primitive UI components and utilities. + */ + +export { Button } from './Button'; +export type { ButtonProps } from './Button'; + +export { Toaster } from './Toast'; +export { useToastStore, toast } from './toastStore'; +export type { ToastItem, ToastType } from './toastStore'; diff --git a/apps/desktop/src/renderer/ui/primitives/toastStore.ts b/apps/desktop/src/renderer/ui/primitives/toastStore.ts new file mode 100644 index 00000000..c62708f3 --- /dev/null +++ b/apps/desktop/src/renderer/ui/primitives/toastStore.ts @@ -0,0 +1,64 @@ +/** + * Toast Store — Zustand store powering the toast notification system + * + * Usage: + * import { toast } from '@/ui/primitives'; + * toast.success('Saved'); + * toast.error('Something went wrong'); + */ + +import { create } from 'zustand'; + +// ============================================================================ +// Types +// ============================================================================ + +export type ToastType = 'success' | 'error' | 'info' | 'warning'; + +export interface ToastItem { + id: string; + message: string; + type: ToastType; + duration: number; +} + +interface ToastState { + toasts: ToastItem[]; + addToast: (message: string, type?: ToastType, duration?: number) => void; + dismissToast: (id: string) => void; +} + +// ============================================================================ +// Store +// ============================================================================ + +let nextId = 0; + +export const useToastStore = create(set => ({ + toasts: [], + + addToast: (message, type = 'info', duration = 4000) => { + const id = `toast-${++nextId}-${Date.now()}`; + const item: ToastItem = { id, message, type, duration }; + set(state => ({ toasts: [...state.toasts, item] })); + }, + + dismissToast: id => { + set(state => ({ toasts: state.toasts.filter(t => t.id !== id) })); + }, +})); + +// ============================================================================ +// Convenience API +// ============================================================================ + +export const toast = { + success: (message: string, duration?: number) => + useToastStore.getState().addToast(message, 'success', duration), + error: (message: string, duration?: number) => + useToastStore.getState().addToast(message, 'error', duration), + info: (message: string, duration?: number) => + useToastStore.getState().addToast(message, 'info', duration), + warning: (message: string, duration?: number) => + useToastStore.getState().addToast(message, 'warning', duration), +}; diff --git a/apps/desktop/src/renderer/ui/tokens/tokens.css b/apps/desktop/src/renderer/ui/tokens/tokens.css index abbd69e6..759a3f39 100644 --- a/apps/desktop/src/renderer/ui/tokens/tokens.css +++ b/apps/desktop/src/renderer/ui/tokens/tokens.css @@ -97,6 +97,22 @@ --text-xl: 16px; --text-2xl: 18px; + /* ── Typography Weights ──────────────────────────────────────────────── */ + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + + /* ── Line Heights ────────────────────────────────────────────────────── */ + --leading-tight: 1.25; + --leading-normal: 1.5; + --leading-relaxed: 1.65; + + /* ── Letter Spacing ──────────────────────────────────────────────────── */ + --tracking-tight: -0.01em; + --tracking-normal: 0; + --tracking-wide: 0.025em; + /* ── Layout ───────────────────────────────────────────────────────────────── */ --sidebar-width: 200px; --list-width: 280px; diff --git a/apps/web/app/(dashboard)/dashboard/DashboardContent.tsx b/apps/web/app/(dashboard)/dashboard/DashboardContent.tsx index c140f656..71bcd4cf 100644 --- a/apps/web/app/(dashboard)/dashboard/DashboardContent.tsx +++ b/apps/web/app/(dashboard)/dashboard/DashboardContent.tsx @@ -121,14 +121,14 @@ export default function DashboardContent() { if (t) { setToken(t); localStorage.setItem('readied-admin-token', t); - fetchAll(t); + void fetchAll(t); } }, [fetchAll]); const handleLogin = () => { if (!token.trim()) return; localStorage.setItem('readied-admin-token', token.trim()); - fetchAll(token.trim()); + void fetchAll(token.trim()); }; // ── Login screen ──────────────────────────────────────────────────────────── diff --git a/apps/web/app/(marketing)/newsletter/unsubscribe/UnsubscribeContent.tsx b/apps/web/app/(marketing)/newsletter/unsubscribe/UnsubscribeContent.tsx index 1bbe94d9..f13f7ffd 100644 --- a/apps/web/app/(marketing)/newsletter/unsubscribe/UnsubscribeContent.tsx +++ b/apps/web/app/(marketing)/newsletter/unsubscribe/UnsubscribeContent.tsx @@ -35,7 +35,7 @@ export default function UnsubscribeContent() { } } - unsubscribe(); + void unsubscribe(); }, [email]); if (state === 'loading') { diff --git a/apps/web/app/(marketing)/shared/SharedNoteContent.tsx b/apps/web/app/(marketing)/shared/SharedNoteContent.tsx index f4c9945d..ac6c4e86 100644 --- a/apps/web/app/(marketing)/shared/SharedNoteContent.tsx +++ b/apps/web/app/(marketing)/shared/SharedNoteContent.tsx @@ -63,7 +63,7 @@ export default function SharedNoteContent() { } } - fetchNote(); + void fetchNote(); }, [slug]); if (state === 'loading') { diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx index 7f9219aa..9508bf15 100644 --- a/apps/web/app/layout.tsx +++ b/apps/web/app/layout.tsx @@ -4,7 +4,7 @@ import '@fontsource/inter/500.css'; import '@fontsource/inter/600.css'; import '@fontsource/inter/700.css'; import '@fontsource-variable/jetbrains-mono'; -import { RootProvider } from 'fumadocs-ui/provider'; +import { RootProvider } from 'fumadocs-ui/provider/next'; import type { ReactNode } from 'react'; import type { Metadata } from 'next'; diff --git a/apps/web/components/PluginFilter.tsx b/apps/web/components/PluginFilter.tsx index e86d8a29..91995b12 100644 --- a/apps/web/components/PluginFilter.tsx +++ b/apps/web/components/PluginFilter.tsx @@ -184,7 +184,7 @@ export default function PluginFilter({ plugins }: PluginFilterProps) { className="absolute top-1.5 right-1.5 rounded-md bg-white/5 p-1 text-[#71717a] opacity-0 transition-opacity group-hover:opacity-100 hover:text-white focus:opacity-100 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent" aria-label={`Copy install command for ${plugin.name}`} onClick={() => { - navigator.clipboard.writeText(installCmd); + void navigator.clipboard.writeText(installCmd); }} > ; @@ -155,14 +153,16 @@ function EditorMockWithPlay() { - {/* Play button overlay */} -
    -
    -
    - + {/* Play button overlay — only shown when a demo video URL is configured */} + {hasVideo && ( +
    +
    +
    + +
    -
    + )} {/* Video modal */} @@ -226,7 +226,7 @@ export default function Hero() { {/* Version badge */}
    - v0.6 Early access + Beta Early access
    @@ -267,7 +267,7 @@ export default function Hero() { {/* Version info */}

    - v0.6.2  |  macOS 13+  |  Windows & Linux coming soon + macOS 13+  |  Windows & Linux coming soon

    {/* Trust line */} @@ -282,12 +282,6 @@ export default function Hero() { {/* Video / Editor preview */}
    - {/* - TODO: Replace with real video once recorded. - 1. Upload video to YouTube, get embed URL - 2. Take a screenshot for thumbnailSrc - 3. Swap videoSrc and thumbnailSrc below - */}
    diff --git a/apps/web/components/landing/VideoGuides.tsx b/apps/web/components/landing/VideoGuides.tsx index 3ecba286..89410381 100644 --- a/apps/web/components/landing/VideoGuides.tsx +++ b/apps/web/components/landing/VideoGuides.tsx @@ -11,21 +11,21 @@ const videos = [ id: 'getting-started', title: 'Getting started with Readied', description: 'Set up your workspace and write your first note in under 2 minutes.', - videoSrc: '', // TODO: YouTube embed URL + videoSrc: '', duration: '2:30', }, { id: 'markdown-workflow', title: 'Markdown workflow tips', description: 'Keyboard shortcuts, split preview, and Cmd+P navigation.', - videoSrc: '', // TODO: YouTube embed URL + videoSrc: '', duration: '4:15', }, { id: 'plugins', title: 'Extending with plugins', description: 'How to install, configure, and build plugins for Readied.', - videoSrc: '', // TODO: YouTube embed URL + videoSrc: '', duration: '5:00', }, ]; diff --git a/apps/web/lib/source.ts b/apps/web/lib/source.ts index 4f3db25c..fedc9813 100644 --- a/apps/web/lib/source.ts +++ b/apps/web/lib/source.ts @@ -1,8 +1,7 @@ -import { docs } from '@/.source'; -import { createMDXSource } from 'fumadocs-mdx/runtime/next'; import { loader } from 'fumadocs-core/source'; +import { docs } from '@/.source/server'; export const source = loader({ baseUrl: '/docs', - source: createMDXSource(docs.docs, docs.meta), + source: docs.toFumadocsSource(), }); diff --git a/apps/web/package.json b/apps/web/package.json index ddc6ea17..50ce35a8 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,10 +16,10 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "framer-motion": "^12.35.2", - "fumadocs-core": "^15.8.5", - "fumadocs-mdx": "^13.0.8", - "fumadocs-ui": "^15.8.5", - "lucide-react": "^0.475.0", + "fumadocs-core": "^16.8.2", + "fumadocs-mdx": "^14.3.1", + "fumadocs-ui": "^16.8.2", + "lucide-react": "^0.562.0", "marked": "^17.0.4", "next": "^16.2.3", "next-themes": "^0.4.6", diff --git a/eslint.config.js b/eslint.config.js index e6d2e33e..b9c292aa 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -17,6 +17,7 @@ export default tseslint.config( '**/.next/**', '**/.source/**', '**/next-env.d.ts', + 'packages/ai-assistant/**', ], }, @@ -26,7 +27,7 @@ export default tseslint.config( // TypeScript rules ...tseslint.configs.recommended, - // Project-wide settings + // Project-wide settings (non-type-aware — applies to ALL files) { languageOptions: { ecmaVersion: 2022, @@ -37,11 +38,7 @@ export default tseslint.config( }, rules: { // === Architecture Protection === - - // Prevent circular dependencies 'import-x/no-cycle': 'error', - - // Enforce import order (optional, remove if annoying) 'import-x/order': [ 'warn', { @@ -51,14 +48,7 @@ export default tseslint.config( ], // === TypeScript Safety === - - // Allow any when truly needed, but warn '@typescript-eslint/no-explicit-any': 'warn', - - // Catch forgotten awaits - '@typescript-eslint/no-floating-promises': 'off', // Enable when we add tsconfig to eslint - - // Allow unused vars starting with _ '@typescript-eslint/no-unused-vars': [ 'error', { @@ -67,19 +57,11 @@ export default tseslint.config( caughtErrorsIgnorePattern: '^_', }, ], - - // Allow require for native modules (Electron pattern) '@typescript-eslint/no-require-imports': 'off', // === Practical Rules === - - // No console in production code (warn only) 'no-console': ['warn', { allow: ['warn', 'error'] }], - - // Prefer const 'prefer-const': 'error', - - // No var 'no-var': 'error', // === Disabled Style Rules (Prettier handles these) === @@ -90,11 +72,29 @@ export default tseslint.config( }, }, + // Type-aware rules — only for source files inside tsconfig projects + { + files: [ + 'apps/*/src/**/*.ts', + 'apps/*/src/**/*.tsx', + 'packages/*/src/**/*.ts', + 'packages/*/src/**/*.tsx', + ], + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + rules: { + '@typescript-eslint/no-floating-promises': 'warn', + }, + }, + // Package-specific overrides { files: ['packages/core/**/*.ts'], rules: { - // Core must be pure - no Node.js specific imports 'no-restricted-imports': [ 'error', { @@ -112,7 +112,6 @@ export default tseslint.config( { files: ['packages/storage-core/**/*.ts'], rules: { - // Storage-core must be pure too 'no-restricted-imports': [ 'error', { @@ -129,16 +128,16 @@ export default tseslint.config( // Test files - relax some rules { - files: ['**/*.test.ts', '**/tests/**/*.ts'], + files: ['**/*.test.ts', '**/tests/**/*.ts', '**/__tests__/**/*.ts'], rules: { '@typescript-eslint/no-explicit-any': 'off', 'no-console': 'off', }, }, - // Config files - CommonJS allowed + // Config files { - files: ['**/*.config.js', '**/*.config.ts'], + files: ['**/*.config.js', '**/*.config.ts', '**/*.config.mjs'], rules: { '@typescript-eslint/no-require-imports': 'off', }, diff --git a/package.json b/package.json index f4544a27..4c018355 100644 --- a/package.json +++ b/package.json @@ -65,12 +65,7 @@ "*.{ts,tsx,js,json,md}": "prettier --write" }, "packageManager": "pnpm@9.15.1", - "pnpm": { - "overrides": { - "@types/react": "18.3.27", - "@types/react-dom": "18.3.7" - } - }, + "pnpm": {}, "engines": { "node": ">=20.0.0", "pnpm": ">=9.0.0" diff --git a/packages/api/package.json b/packages/api/package.json index c30c5735..ea6d7b3f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -38,7 +38,7 @@ "devDependencies": { "@cloudflare/workers-types": "^4.20260218.0", "drizzle-kit": "^0.30.1", - "typescript": "^5.5.4", + "typescript": "^5.7.2", "vitest": "^2.1.8", "wrangler": "^4.66.0" } diff --git a/packages/api/src/routes/auth.ts b/packages/api/src/routes/auth.ts index d2c0c49c..9420fb8c 100644 --- a/packages/api/src/routes/auth.ts +++ b/packages/api/src/routes/auth.ts @@ -33,10 +33,12 @@ auth.post('/magic-link', zValidator('json', magicLinkSchema), async c => { const db = createDb(c.env); // Find or create user - let [user] = await db.select().from(users).where(eq(users.email, email)).limit(1); + const [existing] = await db.select().from(users).where(eq(users.email, email)).limit(1); + let user = existing; if (!user) { - [user] = await db.insert(users).values({ email }).returning(); + const [created] = await db.insert(users).values({ email }).returning(); + user = created!; } // Generate magic link token diff --git a/packages/api/src/routes/plugins.ts b/packages/api/src/routes/plugins.ts index e38eba11..1c918ca1 100644 --- a/packages/api/src/routes/plugins.ts +++ b/packages/api/src/routes/plugins.ts @@ -61,6 +61,7 @@ plugins.get('/', zValidator('query', listQuerySchema), async c => { tags: pluginCatalog.tags, icon: pluginCatalog.icon, downloads: pluginCatalog.downloads, + bundleUrl: pluginCatalog.bundleUrl, isBuiltIn: pluginCatalog.isBuiltIn, createdAt: pluginCatalog.createdAt, updatedAt: pluginCatalog.updatedAt, diff --git a/packages/api/src/routes/share.ts b/packages/api/src/routes/share.ts index 377ea6bf..1bb3e371 100644 --- a/packages/api/src/routes/share.ts +++ b/packages/api/src/routes/share.ts @@ -80,9 +80,9 @@ share.post('/', authMiddleware, zValidator('json', createShareSchema), async c = .returning({ slug: sharedNotes.slug }); const baseUrl = c.env.SITE_URL || 'https://readied.app'; - const url = `${baseUrl}/shared?slug=${result.slug}`; + const url = `${baseUrl}/shared?slug=${result!.slug}`; - return c.json({ slug: result.slug, url }); + return c.json({ slug: result!.slug, url }); }); // ─── GET /public/:userId — List public notes (no auth) ────────────────────── diff --git a/packages/api/src/routes/subscription.ts b/packages/api/src/routes/subscription.ts index 89a6f3a5..a7d6b98e 100644 --- a/packages/api/src/routes/subscription.ts +++ b/packages/api/src/routes/subscription.ts @@ -69,14 +69,19 @@ subscription.post('/webhook', async c => { const session = event.data.object as CheckoutSession; if (session.customer_email) { // Find or create user - let [user] = await db + const [existing] = await db .select() .from(users) .where(eq(users.email, session.customer_email)) .limit(1); + let user = existing; if (!user) { - [user] = await db.insert(users).values({ email: session.customer_email }).returning(); + const [created] = await db + .insert(users) + .values({ email: session.customer_email }) + .returning(); + user = created!; } // Create or update subscription @@ -276,10 +281,12 @@ subscription.post('/checkout/public', zValidator('json', publicCheckoutSchema), const db = createDb(c.env); // Find or create user - let [user] = await db.select().from(users).where(eq(users.email, email)).limit(1); + const [existingUser] = await db.select().from(users).where(eq(users.email, email)).limit(1); + let user = existingUser; if (!user) { - [user] = await db.insert(users).values({ email }).returning(); + const [created] = await db.insert(users).values({ email }).returning(); + user = created!; } const stripeSecretKey = c.env.STRIPE_SECRET_KEY; diff --git a/packages/api/src/routes/sync.ts b/packages/api/src/routes/sync.ts index b17a69af..54de7f65 100644 --- a/packages/api/src/routes/sync.ts +++ b/packages/api/src/routes/sync.ts @@ -67,7 +67,7 @@ sync.get('/', zValidator('query', pullSchema), async c => { .limit(limit); // Get max version for cursor update - const maxVersion = changes.length > 0 ? changes[changes.length - 1].version : cursor; + const maxVersion = changes.length > 0 ? changes[changes.length - 1]!.version : cursor; // Update cursor for this device if (deviceId) { @@ -337,7 +337,7 @@ sync.get('/notebooks', zValidator('query', pullSchema), async c => { .orderBy(notebookSyncLog.version) .limit(limit); - const maxVersion = changes.length > 0 ? changes[changes.length - 1].version : cursor; + const maxVersion = changes.length > 0 ? changes[changes.length - 1]!.version : cursor; return c.json({ changes: changes.map(entry => ({ @@ -506,7 +506,7 @@ sync.get('/tags', zValidator('query', tagPullSchema), async c => { .orderBy(tagSyncLog.version) .limit(limit); - const maxVersion = changes.length > 0 ? changes[changes.length - 1].version : cursor; + const maxVersion = changes.length > 0 ? changes[changes.length - 1]!.version : cursor; return c.json({ changes: changes.map(entry => ({ diff --git a/packages/api/src/services/stripe.ts b/packages/api/src/services/stripe.ts index 39f513af..62f07523 100644 --- a/packages/api/src/services/stripe.ts +++ b/packages/api/src/services/stripe.ts @@ -71,9 +71,10 @@ function parseSignatureHeader(header: string): { for (const part of parts) { const [key, value] = part.split('='); - if (key === 't') { - timestamp = parseInt(value, 10); - } else if (key === 'v1') { + if (key === 't' && value) { + const parsed = parseInt(value, 10); + if (!isNaN(parsed)) timestamp = parsed; + } else if (key === 'v1' && value) { signatures.push(value); } } diff --git a/packages/api/tsconfig.json b/packages/api/tsconfig.json index 1b5d7c56..c0bc3569 100644 --- a/packages/api/tsconfig.json +++ b/packages/api/tsconfig.json @@ -1,23 +1,10 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "lib": ["ES2022"], - "types": ["@cloudflare/workers-types"], - "strict": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, + "noEmit": false, "outDir": "./dist", "rootDir": "./src", - "esModuleInterop": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": false, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true + "types": ["@cloudflare/workers-types"] }, "include": ["src/**/*.ts"], "exclude": ["node_modules", "dist"] diff --git a/packages/commands/tsconfig.json b/packages/commands/tsconfig.json index 31d92b32..86d44a06 100644 --- a/packages/commands/tsconfig.json +++ b/packages/commands/tsconfig.json @@ -1,13 +1,6 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, "outDir": "./dist", "rootDir": "./src" }, diff --git a/packages/embeds/tsconfig.json b/packages/embeds/tsconfig.json index 31d92b32..fea7ff64 100644 --- a/packages/embeds/tsconfig.json +++ b/packages/embeds/tsconfig.json @@ -1,13 +1,7 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, + "lib": ["ES2022", "DOM"], "outDir": "./dist", "rootDir": "./src" }, diff --git a/packages/mcp-server/package.json b/packages/mcp-server/package.json index 33fc5efd..4a186901 100644 --- a/packages/mcp-server/package.json +++ b/packages/mcp-server/package.json @@ -6,6 +6,12 @@ "bin": { "readied-mcp": "./dist/index.js" }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, "scripts": { "build": "tsc", "dev": "tsx src/index.ts" diff --git a/packages/mcp-server/src/index.ts b/packages/mcp-server/src/index.ts index e416a337..176ebbb7 100644 --- a/packages/mcp-server/src/index.ts +++ b/packages/mcp-server/src/index.ts @@ -151,9 +151,9 @@ function createServer(db: Database) { const now = new Date().toISOString(); const titleMatch = content.match(/^#+ (.+)$/m); - const title = titleMatch + const title = titleMatch?.[1] ? titleMatch[1].trim() - : content.split('\n')[0].slice(0, 100) || 'Untitled'; + : content.split('\n')[0]?.slice(0, 100) || 'Untitled'; const wordCount = content.split(/\s+/).filter(Boolean).length; let notebookId = 'inbox'; @@ -188,9 +188,9 @@ function createServer(db: Database) { async ({ id, content }) => { const now = new Date().toISOString(); const titleMatch = content.match(/^#+ (.+)$/m); - const title = titleMatch + const title = titleMatch?.[1] ? titleMatch[1].trim() - : content.split('\n')[0].slice(0, 100) || 'Untitled'; + : content.split('\n')[0]?.slice(0, 100) || 'Untitled'; const wordCount = content.split(/\s+/).filter(Boolean).length; const changes = execute( diff --git a/packages/mcp-server/tsconfig.json b/packages/mcp-server/tsconfig.json index b103cd46..2fc587e3 100644 --- a/packages/mcp-server/tsconfig.json +++ b/packages/mcp-server/tsconfig.json @@ -1,15 +1,9 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", + "noEmit": false, "outDir": "dist", - "rootDir": "src", - "strict": true, - "skipLibCheck": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true + "rootDir": "src" }, "include": ["src"] } diff --git a/packages/plugin-api/src/lifecycle/PluginHost.tsx b/packages/plugin-api/src/lifecycle/PluginHost.tsx index bcb278e2..8921aa4d 100644 --- a/packages/plugin-api/src/lifecycle/PluginHost.tsx +++ b/packages/plugin-api/src/lifecycle/PluginHost.tsx @@ -93,7 +93,7 @@ export function PluginHost({ } }; - activateAll(); + void activateAll(); return () => { cancelled = true; diff --git a/packages/plugin-cli/package.json b/packages/plugin-cli/package.json index 877c711e..9201fc7f 100644 --- a/packages/plugin-cli/package.json +++ b/packages/plugin-cli/package.json @@ -7,8 +7,12 @@ "bin": { "readied-plugin": "./dist/cli.js" }, - "main": "./src/index.ts", - "types": "./src/index.ts", + "exports": { + ".": { + "import": "./dist/cli.js", + "types": "./dist/cli.d.ts" + } + }, "scripts": { "build": "tsc -p tsconfig.build.json", "test": "vitest run --passWithNoTests", diff --git a/packages/plugin-cli/src/cli.ts b/packages/plugin-cli/src/cli.ts index 6fc0afd4..7771eb1c 100644 --- a/packages/plugin-cli/src/cli.ts +++ b/packages/plugin-cli/src/cli.ts @@ -57,12 +57,12 @@ async function main() { break; case 'install': - installPlugin(args[1]); + installPlugin(args[1] ?? ''); break; case 'uninstall': case 'remove': - await uninstallPlugin(args[1]); + await uninstallPlugin(args[1] ?? ''); break; case 'link': diff --git a/packages/plugin-cli/tsconfig.json b/packages/plugin-cli/tsconfig.json index e566dc7f..8fecea11 100644 --- a/packages/plugin-cli/tsconfig.json +++ b/packages/plugin-cli/tsconfig.json @@ -1,17 +1,8 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, - "declaration": true, "outDir": "dist", "rootDir": "src", - "noEmit": true, "types": ["node"] }, "include": ["src/**/*.ts"], diff --git a/packages/storage-core/src/data/Export.ts b/packages/storage-core/src/data/Export.ts index a2309881..6861e034 100644 --- a/packages/storage-core/src/data/Export.ts +++ b/packages/storage-core/src/data/Export.ts @@ -72,6 +72,33 @@ function safeFilename(title: string, id: string): string { return sanitized ? `${sanitized}-${shortId}.md` : `note-${shortId}.md`; } +/** + * Build YAML frontmatter string for a note. + * Skips if content already starts with `---` to avoid double frontmatter. + */ +function prependFrontmatter(note: NoteSnapshot): string { + // Don't add frontmatter if content already has it + if (note.content.trimStart().startsWith('---')) { + return note.content; + } + + const escapedTitle = note.title.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); + const tagsYaml = note.tags.length > 0 ? `tags: [${note.tags.join(', ')}]` : 'tags: []'; + + const frontmatter = [ + '---', + `id: "${note.id}"`, + `title: "${escapedTitle}"`, + `created: ${note.createdAt}`, + `updated: ${note.updatedAt}`, + tagsYaml, + '---', + '', + ].join('\n'); + + return frontmatter + note.content; +} + /** * Export notes to Markdown + JSON format. */ @@ -103,9 +130,10 @@ export function exportNotes(notes: NoteSnapshot[], options: ExportOptions): Expo } usedFilenames.add(filename); - // Write markdown file + // Write markdown file with YAML frontmatter const notePath = join(notesDir, filename); - writeFileSync(notePath, note.content, 'utf-8'); + const contentWithFrontmatter = prependFrontmatter(note); + writeFileSync(notePath, contentWithFrontmatter, 'utf-8'); // Track metadata notesMetadata.push({ diff --git a/packages/storage-core/tsconfig.json b/packages/storage-core/tsconfig.json index 1ced8010..2ae72f5b 100644 --- a/packages/storage-core/tsconfig.json +++ b/packages/storage-core/tsconfig.json @@ -1,14 +1,6 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "types": ["node"] diff --git a/packages/storage-sqlite/tsconfig.json b/packages/storage-sqlite/tsconfig.json index fd750274..86d44a06 100644 --- a/packages/storage-sqlite/tsconfig.json +++ b/packages/storage-sqlite/tsconfig.json @@ -1,14 +1,6 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "sourceMap": true, "outDir": "./dist", "rootDir": "./src" }, diff --git a/packages/tasks/tsconfig.json b/packages/tasks/tsconfig.json index c8a8761c..86d44a06 100644 --- a/packages/tasks/tsconfig.json +++ b/packages/tasks/tsconfig.json @@ -1,14 +1,8 @@ { + "extends": "../../tsconfig.base.json", "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true, - "declarationMap": true, - "outDir": "./dist" + "outDir": "./dist", + "rootDir": "./src" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "tests"] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 402d0509..b1ea0b1a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,10 +4,6 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 - importers: .: @@ -151,8 +147,8 @@ importers: specifier: ^8.0.2 version: 8.0.2 electron-updater: - specifier: ^6.6.2 - version: 6.6.2 + specifier: ^6.8.3 + version: 6.8.3 highlight.js: specifier: ^11.11.1 version: 11.11.1 @@ -200,10 +196,10 @@ importers: specifier: ^4.0.4 version: 4.0.4 '@types/react': - specifier: 18.3.27 + specifier: ^18.2.79 version: 18.3.27 '@types/react-dom': - specifier: 18.3.7 + specifier: ^18.2.25 version: 18.3.7(@types/react@18.3.27) '@types/turndown': specifier: ^5.0.6 @@ -212,8 +208,8 @@ importers: specifier: ^4.2.1 version: 4.7.0(vite@6.4.2(@types/node@25.5.2)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) electron: - specifier: ^39.8.5 - version: 39.8.5 + specifier: ^35.7.5 + version: 35.7.5 electron-builder: specifier: ^26.0.12 version: 26.0.12(electron-builder-squirrel-windows@26.0.12) @@ -252,16 +248,16 @@ importers: dependencies: '@radix-ui/react-accordion': specifier: ^1.2.12 - version: 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-dialog': specifier: ^1.1.15 - version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-separator': specifier: ^1.1.8 - version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.2.4(@types/react@18.3.27)(react@19.2.4) + version: 1.2.4(@types/react@19.2.14)(react@19.2.4) '@readied/product-config': specifier: workspace:* version: link:../../packages/product-config @@ -275,17 +271,17 @@ importers: specifier: ^12.35.2 version: 12.35.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) fumadocs-core: - specifier: ^15.8.5 - version: 15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + specifier: ^16.8.2 + version: 16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) fumadocs-mdx: - specifier: ^13.0.8 - version: 13.0.8(fumadocs-core@15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.2(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + specifier: ^14.3.1 + version: 14.3.1(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.2(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) fumadocs-ui: - specifier: ^15.8.5 - version: 15.8.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) + specifier: ^16.8.2 + version: 16.8.2(@tailwindcss/oxide@4.2.1)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1) lucide-react: - specifier: ^0.475.0 - version: 0.475.0(react@19.2.4) + specifier: ^0.562.0 + version: 0.562.0(react@19.2.4) marked: specifier: ^17.0.4 version: 17.0.4 @@ -321,11 +317,11 @@ importers: specifier: 25.4.0 version: 25.4.0 '@types/react': - specifier: 18.3.27 - version: 18.3.27 + specifier: ^19.0.0 + version: 19.2.14 '@types/react-dom': - specifier: 18.3.7 - version: 18.3.7(@types/react@18.3.27) + specifier: ^19.0.0 + version: 19.2.3(@types/react@19.2.14) postcss: specifier: ^8.0.0 version: 8.5.6 @@ -376,7 +372,7 @@ importers: specifier: ^0.30.1 version: 0.30.6 typescript: - specifier: ^5.5.4 + specifier: ^5.7.2 version: 5.9.3 vitest: specifier: ^2.1.8 @@ -497,7 +493,7 @@ importers: specifier: ^6.39.8 version: 6.39.8 '@types/react': - specifier: 18.3.27 + specifier: ^18.2.79 version: 18.3.27 react: specifier: ^18.2.0 @@ -1034,8 +1030,8 @@ packages: resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} engines: {node: '>=12'} - '@electron/node-gyp@git+https://git@github.com:electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2': - resolution: {commit: 06b29aafb7708acef8b3669835c8a7857ebc92d2, repo: git@github.com:electron/node-gyp.git, type: git} + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': + resolution: {tarball: https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2} version: 10.2.0-electron.1 engines: {node: '>=12.13.0'} hasBin: true @@ -1104,6 +1100,12 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -1134,6 +1136,12 @@ packages: cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -1164,6 +1172,12 @@ packages: cpu: [arm] os: [android] + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -1194,6 +1208,12 @@ packages: cpu: [x64] os: [android] + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -1224,6 +1244,12 @@ packages: cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -1254,6 +1280,12 @@ packages: cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -1284,6 +1316,12 @@ packages: cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -1314,6 +1352,12 @@ packages: cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -1344,6 +1388,12 @@ packages: cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -1374,6 +1424,12 @@ packages: cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -1404,6 +1460,12 @@ packages: cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -1434,6 +1496,12 @@ packages: cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -1464,6 +1532,12 @@ packages: cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -1494,6 +1568,12 @@ packages: cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -1524,6 +1604,12 @@ packages: cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -1554,6 +1640,12 @@ packages: cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -1584,6 +1676,12 @@ packages: cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} @@ -1596,6 +1694,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -1626,6 +1730,12 @@ packages: cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} @@ -1638,6 +1748,12 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -1668,6 +1784,12 @@ packages: cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} @@ -1680,6 +1802,12 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -1710,6 +1838,12 @@ packages: cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -1740,6 +1874,12 @@ packages: cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -1770,6 +1910,12 @@ packages: cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -1800,6 +1946,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1859,8 +2011,16 @@ packages: '@fontsource/inter@5.2.8': resolution: {integrity: sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==} - '@formatjs/intl-localematcher@0.6.2': - resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==} + '@fumadocs/tailwind@0.0.5': + resolution: {integrity: sha512-ENKPWUDRmriccsrUDE4bDBq3FNr/ms3BP2rWlsAEMV1yP23pcCaan+ceGfeBUsAQjw7sj9Q3R4Kl3g/TCStPzQ==} + peerDependencies: + '@tailwindcss/oxide': ^4.0.0 + tailwindcss: ^4.0.0 + peerDependenciesMeta: + '@tailwindcss/oxide': + optional: true + tailwindcss: + optional: true '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} @@ -2559,8 +2719,8 @@ packages: '@radix-ui/react-accordion@1.2.12': resolution: {integrity: sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2572,8 +2732,8 @@ packages: '@radix-ui/react-arrow@1.1.7': resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2585,8 +2745,8 @@ packages: '@radix-ui/react-collapsible@1.1.12': resolution: {integrity: sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2598,8 +2758,8 @@ packages: '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2611,7 +2771,7 @@ packages: '@radix-ui/react-compose-refs@1.1.2': resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2620,7 +2780,7 @@ packages: '@radix-ui/react-context@1.1.2': resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2629,8 +2789,8 @@ packages: '@radix-ui/react-dialog@1.1.15': resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2642,7 +2802,7 @@ packages: '@radix-ui/react-direction@1.1.1': resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2651,8 +2811,8 @@ packages: '@radix-ui/react-dismissable-layer@1.1.11': resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2664,7 +2824,7 @@ packages: '@radix-ui/react-focus-guards@1.1.3': resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2673,8 +2833,8 @@ packages: '@radix-ui/react-focus-scope@1.1.7': resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2686,7 +2846,7 @@ packages: '@radix-ui/react-id@1.1.1': resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2695,8 +2855,8 @@ packages: '@radix-ui/react-navigation-menu@1.2.14': resolution: {integrity: sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2708,8 +2868,8 @@ packages: '@radix-ui/react-popover@1.1.15': resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2721,8 +2881,8 @@ packages: '@radix-ui/react-popper@1.2.8': resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2734,8 +2894,8 @@ packages: '@radix-ui/react-portal@1.1.9': resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2747,8 +2907,8 @@ packages: '@radix-ui/react-presence@1.1.5': resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2760,8 +2920,8 @@ packages: '@radix-ui/react-primitive@2.1.3': resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2773,8 +2933,8 @@ packages: '@radix-ui/react-primitive@2.1.4': resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2786,8 +2946,8 @@ packages: '@radix-ui/react-roving-focus@1.1.11': resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2799,8 +2959,8 @@ packages: '@radix-ui/react-scroll-area@1.2.10': resolution: {integrity: sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2812,8 +2972,8 @@ packages: '@radix-ui/react-separator@1.1.8': resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2825,7 +2985,7 @@ packages: '@radix-ui/react-slot@1.2.3': resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2834,7 +2994,7 @@ packages: '@radix-ui/react-slot@1.2.4': resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2843,8 +3003,8 @@ packages: '@radix-ui/react-tabs@1.1.13': resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -2856,7 +3016,7 @@ packages: '@radix-ui/react-use-callback-ref@1.1.1': resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2865,7 +3025,7 @@ packages: '@radix-ui/react-use-controllable-state@1.2.2': resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2874,7 +3034,7 @@ packages: '@radix-ui/react-use-effect-event@0.0.2': resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2883,7 +3043,7 @@ packages: '@radix-ui/react-use-escape-keydown@1.1.1': resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2892,7 +3052,7 @@ packages: '@radix-ui/react-use-layout-effect@1.1.1': resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2901,7 +3061,7 @@ packages: '@radix-ui/react-use-previous@1.1.1': resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2910,7 +3070,7 @@ packages: '@radix-ui/react-use-rect@1.1.1': resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2919,7 +3079,7 @@ packages: '@radix-ui/react-use-size@1.1.1': resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -2928,8 +3088,8 @@ packages: '@radix-ui/react-visually-hidden@1.2.3': resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} peerDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7 + '@types/react': '*' + '@types/react-dom': '*' react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc peerDependenciesMeta: @@ -3180,47 +3340,33 @@ packages: '@opentelemetry/sdk-trace-base': ^1.30.1 || ^2.1.0 '@opentelemetry/semantic-conventions': ^1.37.0 - '@shikijs/core@3.20.0': - resolution: {integrity: sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==} - - '@shikijs/core@3.23.0': - resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} - - '@shikijs/engine-javascript@3.20.0': - resolution: {integrity: sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==} - - '@shikijs/engine-javascript@3.23.0': - resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} - - '@shikijs/engine-oniguruma@3.20.0': - resolution: {integrity: sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==} - - '@shikijs/engine-oniguruma@3.23.0': - resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - - '@shikijs/langs@3.20.0': - resolution: {integrity: sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==} - - '@shikijs/langs@3.23.0': - resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} + '@shikijs/core@4.0.2': + resolution: {integrity: sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw==} + engines: {node: '>=20'} - '@shikijs/rehype@3.23.0': - resolution: {integrity: sha512-GepKJxXHbXFfAkiZZZ+4V7x71Lw3s0ALYmydUxJRdvpKjSx9FOMSaunv6WRLFBXR6qjYerUq1YZQno+2gLEPwA==} + '@shikijs/engine-javascript@4.0.2': + resolution: {integrity: sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag==} + engines: {node: '>=20'} - '@shikijs/themes@3.20.0': - resolution: {integrity: sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==} + '@shikijs/engine-oniguruma@4.0.2': + resolution: {integrity: sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg==} + engines: {node: '>=20'} - '@shikijs/themes@3.23.0': - resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} + '@shikijs/langs@4.0.2': + resolution: {integrity: sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg==} + engines: {node: '>=20'} - '@shikijs/transformers@3.23.0': - resolution: {integrity: sha512-F9msZVxdF+krQNSdQ4V+Ja5QemeAoTQ2jxt7nJCwhDsdF1JWS3KxIQXA3lQbyKwS3J61oHRUSv4jYWv3CkaKTQ==} + '@shikijs/primitive@4.0.2': + resolution: {integrity: sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw==} + engines: {node: '>=20'} - '@shikijs/types@3.20.0': - resolution: {integrity: sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==} + '@shikijs/themes@4.0.2': + resolution: {integrity: sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA==} + engines: {node: '>=20'} - '@shikijs/types@3.23.0': - resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} + '@shikijs/types@4.0.2': + resolution: {integrity: sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg==} + engines: {node: '>=20'} '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} @@ -3453,11 +3599,19 @@ packages: '@types/react-dom@18.3.7': resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': ^18.0.0 + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 '@types/react@18.3.27': resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} @@ -3923,6 +4077,10 @@ packages: resolution: {integrity: sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==} engines: {node: '>=12.0.0'} + builder-util-runtime@9.5.1: + resolution: {integrity: sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==} + engines: {node: '>=12.0.0'} + builder-util@26.0.11: resolution: {integrity: sha512-xNjXfsldUEe153h1DraD0XvDOpqGR0L5eKFkdReB7eFW5HqysDZFfly4rckda6y9dF39N3pkPlOblcfHKGw+uA==} @@ -4008,9 +4166,9 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -4256,11 +4414,6 @@ packages: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -4591,8 +4744,8 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - electron-updater@6.6.2: - resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==} + electron-updater@6.8.3: + resolution: {integrity: sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==} electron-vite@2.3.0: resolution: {integrity: sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==} @@ -4609,8 +4762,8 @@ packages: resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==} engines: {node: '>=8.0.0'} - electron@39.8.5: - resolution: {integrity: sha512-q6+LiQIcTadSyvtPgLDQkCtVA9jQJXQVMrQcctfOJILh6OFMN+UJJLRkuUTy8CZDYeCIBn1ZycqsL1dAXugxZA==} + electron@35.7.5: + resolution: {integrity: sha512-dnL+JvLraKZl7iusXTVTGYs10TKfzUi30uEDTqsmTm0guN9V2tbOjTzyIZbh9n3ygUjgEYyo+igAwMRXIi3IPw==} engines: {node: '>= 12.20.55'} hasBin: true @@ -4727,6 +4880,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5033,6 +5191,20 @@ packages: react-dom: optional: true + framer-motion@12.38.0: + resolution: {integrity: sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fresh@2.0.0: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} @@ -5075,31 +5247,50 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - fumadocs-core@15.8.5: - resolution: {integrity: sha512-hyJtKGuB2J/5y7tDfI1EnGMKlNbSXM5N5cpwvgCY0DcBJwFMDG/GpSpaVRzh3aWy67pAYDZFIwdtbKXBa/q5bg==} + fumadocs-core@16.8.2: + resolution: {integrity: sha512-2HkaGmNFoTGCTYaBrNPOuJfu5o2ylw3+DQKbODvlLbw5xokqcMyfUh1A3lIJY4kouMt32wwr7aYlb+vnmx38HA==} peerDependencies: - '@mixedbread/sdk': ^0.19.0 - '@oramacloud/client': 1.x.x || 2.x.x + '@mdx-js/mdx': '*' + '@mixedbread/sdk': 0.x.x + '@orama/core': 1.x.x + '@oramacloud/client': 2.x.x '@tanstack/react-router': 1.x.x - '@types/react': 18.3.27 + '@types/estree-jsx': '*' + '@types/hast': '*' + '@types/mdast': '*' + '@types/react': '*' algoliasearch: 5.x.x + flexsearch: '*' lucide-react: '*' - next: 14.x.x || 15.x.x - react: 18.x.x || 19.x.x - react-dom: 18.x.x || 19.x.x + next: 16.x.x + react: ^19.2.0 + react-dom: ^19.2.0 react-router: 7.x.x - waku: ^0.26.0 + waku: ^0.26.0 || ^0.27.0 || ^1.0.0 + zod: 4.x.x peerDependenciesMeta: + '@mdx-js/mdx': + optional: true '@mixedbread/sdk': optional: true + '@orama/core': + optional: true '@oramacloud/client': optional: true '@tanstack/react-router': optional: true + '@types/estree-jsx': + optional: true + '@types/hast': + optional: true + '@types/mdast': + optional: true '@types/react': optional: true algoliasearch: optional: true + flexsearch: + optional: true lucide-react: optional: true next: @@ -5112,18 +5303,29 @@ packages: optional: true waku: optional: true + zod: + optional: true - fumadocs-mdx@13.0.8: - resolution: {integrity: sha512-UbUwH0iGvYbytnxhmfd7tWJKFK8L0mrbTAmrQYnpg6Wi/h8afNMJmbHBOzVcaEWJKeFipZ1CGDAsNA2fztwXNg==} + fumadocs-mdx@14.3.1: + resolution: {integrity: sha512-0u2eXvYrZtrJB14y6fDhP0hhxLgmH8JOmRv6IVHALt5MqR9JIJxV5LJYlho8g8CJXRE8w12rVNFZN0rtUVAqGw==} hasBin: true peerDependencies: - '@fumadocs/mdx-remote': ^1.4.0 + '@types/mdast': '*' + '@types/mdx': '*' + '@types/react': '*' fumadocs-core: ^15.0.0 || ^16.0.0 + mdast-util-directive: '*' next: ^15.3.0 || ^16.0.0 - react: '*' - vite: 6.x.x || 7.x.x + react: ^19.2.0 + vite: 6.x.x || 7.x.x || 8.x.x peerDependenciesMeta: - '@fumadocs/mdx-remote': + '@types/mdast': + optional: true + '@types/mdx': + optional: true + '@types/react': + optional: true + mdast-util-directive: optional: true next: optional: true @@ -5132,21 +5334,25 @@ packages: vite: optional: true - fumadocs-ui@15.8.5: - resolution: {integrity: sha512-9pyB+9rOOsrFnmmZ9xREp/OgVhyaSq2ocEpqTNbeQ7tlJ6JWbdFWfW0C9lRXprQEB6DJWUDtDxqKS5QXLH0EGA==} + fumadocs-ui@16.8.2: + resolution: {integrity: sha512-6NMBxt8xnkZcU9bx0ETU17ZLJWRusXSirowSHB7M7Xm5xOyGry+49q4VKDvr3uxLNl+V9doljLv9AOsGiHVpPA==} peerDependencies: - '@types/react': 18.3.27 - next: 14.x.x || 15.x.x - react: 18.x.x || 19.x.x - react-dom: 18.x.x || 19.x.x - tailwindcss: ^3.4.14 || ^4.0.0 + '@takumi-rs/image-response': '*' + '@types/mdx': '*' + '@types/react': '*' + fumadocs-core: 16.8.2 + next: 16.x.x + react: ^19.2.0 + react-dom: ^19.2.0 peerDependenciesMeta: + '@takumi-rs/image-response': + optional: true + '@types/mdx': + optional: true '@types/react': optional: true next: optional: true - tailwindcss: - optional: true function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} @@ -5326,9 +5532,6 @@ packages: hast-util-to-parse5@8.0.1: resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} - hast-util-to-string@3.0.1: - resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} - hast-util-to-text@4.0.2: resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} @@ -5445,11 +5648,6 @@ packages: resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} engines: {node: '>= 4'} - image-size@2.0.2: - resolution: {integrity: sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==} - engines: {node: '>=16.x'} - hasBin: true - immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} @@ -5744,7 +5942,6 @@ packages: libsql@0.4.7: resolution: {integrity: sha512-T9eIRCs6b0J1SHKYIvD8+KCJMcWZ900iZyxdnSCdqxN12Z1ijzT+jY5nrk72Jw4B0HGzms2NgpryArlJqvc3Lw==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lie@3.3.0: @@ -5939,13 +6136,13 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lucide-react@0.475.0: - resolution: {integrity: sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==} + lucide-react@0.562.0: + resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - lucide-react@0.562.0: - resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} + lucide-react@1.8.0: + resolution: {integrity: sha512-WuvlsjngSk7TnTBJ1hsCy3ql9V9VOdcPkd3PKcSmM34vJD8KG6molxz7m7zbYFgICwsanQWmJ13JlYs4Zp7Arw==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -6300,9 +6497,29 @@ packages: motion-dom@12.35.2: resolution: {integrity: sha512-pWXFMTwvGDbx1Fe9YL5HZebv2NhvGBzRtiNUv58aoK7+XrsuaydQ0JGRKK2r+bTKlwgSWwWxHbP5249Qr/BNpg==} + motion-dom@12.38.0: + resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} + motion-utils@12.29.2: resolution: {integrity: sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==} + motion-utils@12.36.0: + resolution: {integrity: sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==} + + motion@12.38.0: + resolution: {integrity: sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -6434,10 +6651,6 @@ packages: resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} engines: {node: '>=18'} - npm-to-yarn@3.0.1: - resolution: {integrity: sha512-tt6PvKu4WyzPwWUzy/hvPFqn+uwXO0K1ZHka8az3NnrhWJDmSqI8ncWq0fkL0k/lmmi5tAC11FXwXuh0rFbt1A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - npm@11.11.1: resolution: {integrity: sha512-asazCodkFdz1ReQzukyzS/DD77uGCIqUFeRG3gtaT8b9UR0ne1m9QOBuMgT72ij1rt7TRrOox4A1WzntMWIuEg==} engines: {node: ^20.17.0 || >=22.9.0} @@ -6746,10 +6959,6 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - picomatch@4.0.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -6798,10 +7007,6 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss-selector-parser@7.1.1: - resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} - engines: {node: '>=4'} - postcss@8.4.31: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} @@ -6983,15 +7188,9 @@ packages: react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '>=18' react: '>=18' - react-medium-image-zoom@5.4.1: - resolution: {integrity: sha512-DD2iZYaCfAwiQGR8AN62r/cDJYoXhezlYJc5HY4TzBUGuGge43CptG0f7m0PEIM72aN6GfpjohvY1yYdtCJB7g==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-refresh@0.17.0: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} @@ -7000,7 +7199,7 @@ packages: resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': @@ -7010,7 +7209,7 @@ packages: resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -7026,7 +7225,7 @@ packages: resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -7071,9 +7270,9 @@ packages: resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} @@ -7308,11 +7507,9 @@ packages: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} engines: {node: '>= 0.4'} - shiki@3.20.0: - resolution: {integrity: sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==} - - shiki@3.23.0: - resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} + shiki@4.0.2: + resolution: {integrity: sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ==} + engines: {node: '>=20'} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -7658,10 +7855,18 @@ packages: resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} engines: {node: '>=18'} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinypool@1.1.1: resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} engines: {node: ^18.0.0 || >=20.0.0} @@ -7932,7 +8137,7 @@ packages: resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -7942,7 +8147,7 @@ packages: resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} engines: {node: '>=10'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '*' react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc peerDependenciesMeta: '@types/react': @@ -8252,7 +8457,7 @@ packages: resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} engines: {node: '>=12.20.0'} peerDependencies: - '@types/react': 18.3.27 + '@types/react': '>=18.0.0' immer: '>=9.0.6' react: '>=18.0.0' use-sync-external-store: '>=1.2.0' @@ -8938,7 +9143,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/node-gyp@git+https://git@github.com:electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2': + '@electron/node-gyp@https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2': dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.3 @@ -8975,7 +9180,7 @@ snapshots: '@electron/rebuild@3.7.0': dependencies: - '@electron/node-gyp': git+https://git@github.com:electron/node-gyp.git#06b29aafb7708acef8b3669835c8a7857ebc92d2 + '@electron/node-gyp': https://codeload.github.com/electron/node-gyp/tar.gz/06b29aafb7708acef8b3669835c8a7857ebc92d2 '@malept/cross-spawn-promise': 2.0.0 chalk: 4.1.2 debug: 4.4.3 @@ -9054,6 +9259,9 @@ snapshots: '@esbuild/aix-ppc64@0.27.3': optional: true + '@esbuild/aix-ppc64@0.28.0': + optional: true + '@esbuild/android-arm64@0.18.20': optional: true @@ -9069,6 +9277,9 @@ snapshots: '@esbuild/android-arm64@0.27.3': optional: true + '@esbuild/android-arm64@0.28.0': + optional: true + '@esbuild/android-arm@0.18.20': optional: true @@ -9084,6 +9295,9 @@ snapshots: '@esbuild/android-arm@0.27.3': optional: true + '@esbuild/android-arm@0.28.0': + optional: true + '@esbuild/android-x64@0.18.20': optional: true @@ -9099,6 +9313,9 @@ snapshots: '@esbuild/android-x64@0.27.3': optional: true + '@esbuild/android-x64@0.28.0': + optional: true + '@esbuild/darwin-arm64@0.18.20': optional: true @@ -9114,6 +9331,9 @@ snapshots: '@esbuild/darwin-arm64@0.27.3': optional: true + '@esbuild/darwin-arm64@0.28.0': + optional: true + '@esbuild/darwin-x64@0.18.20': optional: true @@ -9129,6 +9349,9 @@ snapshots: '@esbuild/darwin-x64@0.27.3': optional: true + '@esbuild/darwin-x64@0.28.0': + optional: true + '@esbuild/freebsd-arm64@0.18.20': optional: true @@ -9144,6 +9367,9 @@ snapshots: '@esbuild/freebsd-arm64@0.27.3': optional: true + '@esbuild/freebsd-arm64@0.28.0': + optional: true + '@esbuild/freebsd-x64@0.18.20': optional: true @@ -9159,6 +9385,9 @@ snapshots: '@esbuild/freebsd-x64@0.27.3': optional: true + '@esbuild/freebsd-x64@0.28.0': + optional: true + '@esbuild/linux-arm64@0.18.20': optional: true @@ -9174,6 +9403,9 @@ snapshots: '@esbuild/linux-arm64@0.27.3': optional: true + '@esbuild/linux-arm64@0.28.0': + optional: true + '@esbuild/linux-arm@0.18.20': optional: true @@ -9189,6 +9421,9 @@ snapshots: '@esbuild/linux-arm@0.27.3': optional: true + '@esbuild/linux-arm@0.28.0': + optional: true + '@esbuild/linux-ia32@0.18.20': optional: true @@ -9204,6 +9439,9 @@ snapshots: '@esbuild/linux-ia32@0.27.3': optional: true + '@esbuild/linux-ia32@0.28.0': + optional: true + '@esbuild/linux-loong64@0.18.20': optional: true @@ -9219,6 +9457,9 @@ snapshots: '@esbuild/linux-loong64@0.27.3': optional: true + '@esbuild/linux-loong64@0.28.0': + optional: true + '@esbuild/linux-mips64el@0.18.20': optional: true @@ -9234,6 +9475,9 @@ snapshots: '@esbuild/linux-mips64el@0.27.3': optional: true + '@esbuild/linux-mips64el@0.28.0': + optional: true + '@esbuild/linux-ppc64@0.18.20': optional: true @@ -9249,6 +9493,9 @@ snapshots: '@esbuild/linux-ppc64@0.27.3': optional: true + '@esbuild/linux-ppc64@0.28.0': + optional: true + '@esbuild/linux-riscv64@0.18.20': optional: true @@ -9264,6 +9511,9 @@ snapshots: '@esbuild/linux-riscv64@0.27.3': optional: true + '@esbuild/linux-riscv64@0.28.0': + optional: true + '@esbuild/linux-s390x@0.18.20': optional: true @@ -9279,6 +9529,9 @@ snapshots: '@esbuild/linux-s390x@0.27.3': optional: true + '@esbuild/linux-s390x@0.28.0': + optional: true + '@esbuild/linux-x64@0.18.20': optional: true @@ -9294,12 +9547,18 @@ snapshots: '@esbuild/linux-x64@0.27.3': optional: true + '@esbuild/linux-x64@0.28.0': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true '@esbuild/netbsd-arm64@0.27.3': optional: true + '@esbuild/netbsd-arm64@0.28.0': + optional: true + '@esbuild/netbsd-x64@0.18.20': optional: true @@ -9315,12 +9574,18 @@ snapshots: '@esbuild/netbsd-x64@0.27.3': optional: true + '@esbuild/netbsd-x64@0.28.0': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true '@esbuild/openbsd-arm64@0.27.3': optional: true + '@esbuild/openbsd-arm64@0.28.0': + optional: true + '@esbuild/openbsd-x64@0.18.20': optional: true @@ -9336,12 +9601,18 @@ snapshots: '@esbuild/openbsd-x64@0.27.3': optional: true + '@esbuild/openbsd-x64@0.28.0': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true '@esbuild/openharmony-arm64@0.27.3': optional: true + '@esbuild/openharmony-arm64@0.28.0': + optional: true + '@esbuild/sunos-x64@0.18.20': optional: true @@ -9357,6 +9628,9 @@ snapshots: '@esbuild/sunos-x64@0.27.3': optional: true + '@esbuild/sunos-x64@0.28.0': + optional: true + '@esbuild/win32-arm64@0.18.20': optional: true @@ -9372,6 +9646,9 @@ snapshots: '@esbuild/win32-arm64@0.27.3': optional: true + '@esbuild/win32-arm64@0.28.0': + optional: true + '@esbuild/win32-ia32@0.18.20': optional: true @@ -9387,6 +9664,9 @@ snapshots: '@esbuild/win32-ia32@0.27.3': optional: true + '@esbuild/win32-ia32@0.28.0': + optional: true + '@esbuild/win32-x64@0.18.20': optional: true @@ -9402,6 +9682,9 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@esbuild/win32-x64@0.28.0': + optional: true + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))': dependencies: eslint: 9.39.2(jiti@2.6.1) @@ -9469,9 +9752,10 @@ snapshots: '@fontsource/inter@5.2.8': {} - '@formatjs/intl-localematcher@0.6.2': - dependencies: - tslib: 2.8.1 + '@fumadocs/tailwind@0.0.5(@tailwindcss/oxide@4.2.1)(tailwindcss@4.2.1)': + optionalDependencies: + '@tailwindcss/oxide': 4.2.1 + tailwindcss: 4.2.1 '@gar/promisify@1.1.3': {} @@ -10259,373 +10543,373 @@ snapshots: '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-accordion@1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-accordion@1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-arrow@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collapsible@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collapsible@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-collection@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-context@1.1.2(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dialog@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-direction@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-id@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-navigation-menu@1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popover@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) aria-hidden: 1.2.6 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-popper@1.2.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-rect': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@19.2.14)(react@19.2.4) '@radix-ui/rect': 1.1.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-portal@1.1.9(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-presence@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-primitive@2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-collection': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-scroll-area@1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/number': 1.1.1 '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-separator@1.1.8(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-slot@1.2.3(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.4(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-tabs@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-previous@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-rect@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: '@radix-ui/rect': 1.1.1 react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-use-size@1.1.1(@types/react@18.3.27)(react@19.2.4)': + '@radix-ui/react-use-size@1.1.1(@types/react@19.2.14)(react@19.2.4)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.27)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.4) react: 19.2.4 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 - '@types/react-dom': 18.3.7(@types/react@18.3.27) + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) '@radix-ui/rect@1.1.1': {} @@ -10919,78 +11203,40 @@ snapshots: '@opentelemetry/semantic-conventions': 1.39.0 '@sentry/core': 10.36.0 - '@shikijs/core@3.20.0': - dependencies: - '@shikijs/types': 3.20.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - hast-util-to-html: 9.0.5 - - '@shikijs/core@3.23.0': + '@shikijs/core@4.0.2': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/primitive': 4.0.2 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 hast-util-to-html: 9.0.5 - '@shikijs/engine-javascript@3.20.0': + '@shikijs/engine-javascript@4.0.2': dependencies: - '@shikijs/types': 3.20.0 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 oniguruma-to-es: 4.3.4 - '@shikijs/engine-javascript@3.23.0': + '@shikijs/engine-oniguruma@4.0.2': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.4 - '@shikijs/engine-oniguruma@3.20.0': + '@shikijs/langs@4.0.2': dependencies: - '@shikijs/types': 3.20.0 - '@shikijs/vscode-textmate': 10.0.2 + '@shikijs/types': 4.0.2 - '@shikijs/engine-oniguruma@3.23.0': + '@shikijs/primitive@4.0.2': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@3.20.0': - dependencies: - '@shikijs/types': 3.20.0 - - '@shikijs/langs@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/rehype@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 '@types/hast': 3.0.4 - hast-util-to-string: 3.0.1 - shiki: 3.23.0 - unified: 11.0.5 - unist-util-visit: 5.1.0 - - '@shikijs/themes@3.20.0': - dependencies: - '@shikijs/types': 3.20.0 - '@shikijs/themes@3.23.0': + '@shikijs/themes@4.0.2': dependencies: - '@shikijs/types': 3.23.0 + '@shikijs/types': 4.0.2 - '@shikijs/transformers@3.23.0': - dependencies: - '@shikijs/core': 3.23.0 - '@shikijs/types': 3.23.0 - - '@shikijs/types@3.20.0': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - '@shikijs/types@3.23.0': + '@shikijs/types@4.0.2': dependencies: '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -11227,11 +11473,19 @@ snapshots: dependencies: '@types/react': 18.3.27 + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + '@types/react@18.3.27': dependencies: '@types/prop-types': 15.7.15 csstype: 3.2.3 + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + '@types/responselike@1.0.3': dependencies: '@types/node': 22.19.17 @@ -11429,22 +11683,6 @@ snapshots: chai: 5.3.3 tinyrainbow: 1.2.0 - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.31.1))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 5.4.21(@types/node@20.19.27)(lightningcss@1.31.1) - - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.3)(lightningcss@1.31.1))': - dependencies: - '@vitest/spy': 2.1.9 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 5.4.21(@types/node@22.19.3)(lightningcss@1.31.1) - '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@25.5.2)(lightningcss@1.31.1))': dependencies: '@vitest/spy': 2.1.9 @@ -11763,6 +12001,13 @@ snapshots: transitivePeerDependencies: - supports-color + builder-util-runtime@9.5.1: + dependencies: + debug: 4.4.3 + sax: 1.4.3 + transitivePeerDependencies: + - supports-color + builder-util@26.0.11: dependencies: 7zip-bin: 5.2.0 @@ -11884,9 +12129,9 @@ snapshots: check-error@2.1.1: {} - chokidar@4.0.3: + chokidar@5.0.0: dependencies: - readdirp: 4.1.2 + readdirp: 5.0.0 chownr@1.1.4: {} @@ -12114,8 +12359,6 @@ snapshots: dependencies: type-fest: 1.4.0 - cssesc@3.0.0: {} - csstype@3.2.3: {} d3-array@3.2.4: @@ -12395,15 +12638,15 @@ snapshots: electron-to-chromium@1.5.267: {} - electron-updater@6.6.2: + electron-updater@6.8.3: dependencies: - builder-util-runtime: 9.3.1 + builder-util-runtime: 9.5.1 fs-extra: 10.1.0 js-yaml: 4.1.1 lazy-val: 1.0.5 lodash.escaperegexp: 4.1.2 lodash.isequal: 4.5.0 - semver: 7.7.3 + semver: 7.7.4 tiny-typed-emitter: 2.1.0 transitivePeerDependencies: - supports-color @@ -12432,7 +12675,7 @@ snapshots: transitivePeerDependencies: - supports-color - electron@39.8.5: + electron@35.7.5: dependencies: '@electron/get': 2.0.3 '@types/node': 22.19.17 @@ -12661,6 +12904,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + esbuild@0.28.0: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -13053,6 +13325,15 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + framer-motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + motion-dom: 12.38.0 + motion-utils: 12.36.0 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + fresh@2.0.0: {} from2@2.3.0: @@ -13102,99 +13383,105 @@ snapshots: fsevents@2.3.3: optional: true - fumadocs-core@15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + fumadocs-core@16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6): dependencies: - '@formatjs/intl-localematcher': 0.6.2 '@orama/orama': 3.1.18 - '@shikijs/rehype': 3.23.0 - '@shikijs/transformers': 3.23.0 + estree-util-value-to-estree: 3.5.0 github-slugger: 2.0.0 hast-util-to-estree: 3.1.3 hast-util-to-jsx-runtime: 2.3.6 - image-size: 2.0.2 - negotiator: 1.0.0 - npm-to-yarn: 3.0.1 - path-to-regexp: 8.3.0 - react-remove-scroll: 2.7.2(@types/react@18.3.27)(react@19.2.4) + js-yaml: 4.1.1 + mdast-util-mdx: 3.0.0 + mdast-util-to-markdown: 2.1.2 remark: 15.0.1 remark-gfm: 4.0.1 remark-rehype: 11.1.2 scroll-into-view-if-needed: 3.1.0 - shiki: 3.20.0 + shiki: 4.0.2 + tinyglobby: 0.2.16 + unified: 11.0.5 unist-util-visit: 5.1.0 + vfile: 6.0.3 optionalDependencies: - '@types/react': 18.3.27 + '@mdx-js/mdx': 3.1.1 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/react': 19.2.14 algoliasearch: 5.46.2 - lucide-react: 0.475.0(react@19.2.4) + lucide-react: 0.562.0(react@19.2.4) next: 16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + zod: 4.3.6 transitivePeerDependencies: - supports-color - fumadocs-mdx@13.0.8(fumadocs-core@15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.2(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): + fumadocs-mdx@14.3.1(@types/mdast@4.0.4)(@types/mdx@2.0.13)(@types/react@19.2.14)(fumadocs-core@16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react@19.2.4)(vite@6.4.2(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)): dependencies: '@mdx-js/mdx': 3.1.1 '@standard-schema/spec': 1.1.0 - chokidar: 4.0.3 - esbuild: 0.25.12 + chokidar: 5.0.0 + esbuild: 0.28.0 estree-util-value-to-estree: 3.5.0 - fumadocs-core: 15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + fumadocs-core: 16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) js-yaml: 4.1.1 - lru-cache: 11.2.6 + mdast-util-mdx: 3.0.0 mdast-util-to-markdown: 2.1.2 picocolors: 1.1.1 - picomatch: 4.0.3 - remark-mdx: 3.1.1 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 + picomatch: 4.0.4 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 unified: 11.0.5 unist-util-remove-position: 5.0.0 unist-util-visit: 5.1.0 + vfile: 6.0.3 zod: 4.3.6 optionalDependencies: + '@types/mdast': 4.0.4 + '@types/mdx': 2.0.13 + '@types/react': 19.2.14 next: 16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) react: 19.2.4 vite: 6.4.2(@types/node@25.4.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color - fumadocs-ui@15.8.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1): - dependencies: - '@radix-ui/react-accordion': 1.2.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popover': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.4(@types/react@18.3.27)(react@19.2.4) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.27))(@types/react@18.3.27)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + fumadocs-ui@16.8.2(@tailwindcss/oxide@4.2.1)(@types/mdx@2.0.13)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(fumadocs-core@16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(tailwindcss@4.2.1): + dependencies: + '@fumadocs/tailwind': 0.0.5(@tailwindcss/oxide@4.2.1)(tailwindcss@4.2.1) + '@radix-ui/react-accordion': 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-collapsible': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-navigation-menu': 1.2.14(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popover': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-scroll-area': 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.4) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) class-variance-authority: 0.7.1 - fumadocs-core: 15.8.5(@types/react@18.3.27)(algoliasearch@5.46.2)(lucide-react@0.475.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - lodash.merge: 4.6.2 + fumadocs-core: 16.8.2(@mdx-js/mdx@3.1.1)(@types/estree-jsx@1.0.5)(@types/hast@3.0.4)(@types/mdast@4.0.4)(@types/react@19.2.14)(algoliasearch@5.46.2)(lucide-react@0.562.0(react@19.2.4))(next@16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6) + lucide-react: 1.8.0(react@19.2.4) + motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-themes: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - postcss-selector-parser: 7.1.1 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - react-medium-image-zoom: 5.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.4) + rehype-raw: 7.0.0 scroll-into-view-if-needed: 3.1.0 + shiki: 4.0.2 tailwind-merge: 3.5.0 + unist-util-visit: 5.1.0 optionalDependencies: - '@types/react': 18.3.27 + '@types/mdx': 2.0.13 + '@types/react': 19.2.14 next: 16.2.3(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - tailwindcss: 4.2.1 transitivePeerDependencies: - - '@mixedbread/sdk' - - '@oramacloud/client' - - '@tanstack/react-router' + - '@emotion/is-prop-valid' + - '@tailwindcss/oxide' - '@types/react-dom' - - algoliasearch - - lucide-react - - react-router - - supports-color - - waku + - tailwindcss function-bind@1.1.2: {} @@ -13476,10 +13763,6 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-to-string@3.0.1: - dependencies: - '@types/hast': 3.0.4 - hast-util-to-text@4.0.2: dependencies: '@types/hast': 3.0.4 @@ -13601,8 +13884,6 @@ snapshots: ignore@7.0.5: {} - image-size@2.0.2: {} - immediate@3.0.6: {} import-fresh@3.3.1: @@ -14021,13 +14302,17 @@ snapshots: lru-cache@7.18.3: {} - lucide-react@0.475.0(react@19.2.4): + lucide-react@0.562.0(react@18.3.1): + dependencies: + react: 18.3.1 + + lucide-react@0.562.0(react@19.2.4): dependencies: react: 19.2.4 - lucide-react@0.562.0(react@18.3.1): + lucide-react@1.8.0(react@19.2.4): dependencies: - react: 18.3.1 + react: 19.2.4 magic-string@0.30.21: dependencies: @@ -14648,8 +14933,22 @@ snapshots: dependencies: motion-utils: 12.29.2 + motion-dom@12.38.0: + dependencies: + motion-utils: 12.36.0 + motion-utils@12.29.2: {} + motion-utils@12.36.0: {} + + motion@12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + framer-motion: 12.38.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + tslib: 2.8.1 + optionalDependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + ms@2.1.3: {} mz@2.7.0: @@ -14771,8 +15070,6 @@ snapshots: path-key: 4.0.0 unicorn-magic: 0.3.0 - npm-to-yarn@3.0.1: {} - npm@11.11.1: {} object-assign@4.1.1: {} @@ -14996,8 +15293,6 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.3: {} - picomatch@4.0.4: {} pify@3.0.0: {} @@ -15064,11 +15359,6 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-selector-parser@7.1.1: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - postcss@8.4.31: dependencies: nanoid: 3.3.11 @@ -15257,44 +15547,39 @@ snapshots: transitivePeerDependencies: - supports-color - react-medium-image-zoom@5.4.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-refresh@0.17.0: {} - react-remove-scroll-bar@2.3.8(@types/react@18.3.27)(react@19.2.4): + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@18.3.27)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - react-remove-scroll@2.7.2(@types/react@18.3.27)(react@19.2.4): + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.27)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@18.3.27)(react@19.2.4) + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.4) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.27)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@18.3.27)(react@19.2.4) + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.4) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.4) optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 react-resizable-panels@4.2.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-style-singleton@2.2.3(@types/react@18.3.27)(react@19.2.4): + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.4): dependencies: get-nonce: 1.0.1 react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 react@18.3.1: dependencies: @@ -15360,7 +15645,7 @@ snapshots: process: 0.11.10 string_decoder: 1.3.0 - readdirp@4.1.2: {} + readdirp@5.0.0: {} real-require@0.2.0: {} @@ -15740,25 +16025,14 @@ snapshots: shell-quote@1.8.3: {} - shiki@3.20.0: + shiki@4.0.2: dependencies: - '@shikijs/core': 3.20.0 - '@shikijs/engine-javascript': 3.20.0 - '@shikijs/engine-oniguruma': 3.20.0 - '@shikijs/langs': 3.20.0 - '@shikijs/themes': 3.20.0 - '@shikijs/types': 3.20.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.4 - - shiki@3.23.0: - dependencies: - '@shikijs/core': 3.23.0 - '@shikijs/engine-javascript': 3.23.0 - '@shikijs/engine-oniguruma': 3.23.0 - '@shikijs/langs': 3.23.0 - '@shikijs/themes': 3.23.0 - '@shikijs/types': 3.23.0 + '@shikijs/core': 4.0.2 + '@shikijs/engine-javascript': 4.0.2 + '@shikijs/engine-oniguruma': 4.0.2 + '@shikijs/langs': 4.0.2 + '@shikijs/themes': 4.0.2 + '@shikijs/types': 4.0.2 '@shikijs/vscode-textmate': 10.0.2 '@types/hast': 3.0.4 @@ -16111,11 +16385,18 @@ snapshots: tinyexec@1.0.2: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinypool@1.1.1: {} tinyrainbow@1.2.0: {} @@ -16311,7 +16592,7 @@ snapshots: unist-util-remove-position@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-visit: 5.0.0 + unist-util-visit: 5.1.0 unist-util-stringify-position@4.0.0: dependencies: @@ -16384,20 +16665,20 @@ snapshots: url-join@5.0.0: {} - use-callback-ref@1.3.3(@types/react@18.3.27)(react@19.2.4): + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.4): dependencies: react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 - use-sidecar@1.1.3(@types/react@18.3.27)(react@19.2.4): + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.4): dependencies: detect-node-es: 1.1.0 react: 19.2.4 tslib: 2.8.1 optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 use-sync-external-store@1.6.0(react@18.3.1): dependencies: @@ -16557,7 +16838,7 @@ snapshots: vitest@2.1.9(@types/node@20.19.27)(lightningcss@1.31.1): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@20.19.27)(lightningcss@1.31.1)) + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@25.5.2)(lightningcss@1.31.1)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 @@ -16592,7 +16873,7 @@ snapshots: vitest@2.1.9(@types/node@22.19.3)(lightningcss@1.31.1): dependencies: '@vitest/expect': 2.1.9 - '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.3)(lightningcss@1.31.1)) + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@25.5.2)(lightningcss@1.31.1)) '@vitest/pretty-format': 2.1.9 '@vitest/runner': 2.1.9 '@vitest/snapshot': 2.1.9 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 108b1353..3ff5faaa 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,3 @@ packages: - "apps/*" - "packages/*" - - "site" diff --git a/scripts/bump-version.js b/scripts/bump-version.js index 321890a2..de1461bc 100644 --- a/scripts/bump-version.js +++ b/scripts/bump-version.js @@ -8,12 +8,22 @@ if (!version) { process.exit(1); } +if (!/^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/.test(version)) { + console.error(`Invalid version format: ${version}`); + process.exit(1); +} + const files = ['package.json', 'apps/desktop/package.json']; for (const file of files) { const path = resolve(file); - const pkg = JSON.parse(readFileSync(path, 'utf8')); - pkg.version = version; - writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); - console.log(`Updated ${file} -> ${version}`); + try { + const pkg = JSON.parse(readFileSync(path, 'utf8')); + pkg.version = version; + writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n'); + console.log(`Updated ${file} -> ${version}`); + } catch (err) { + console.error(`Failed to update ${file}: ${err.message}`); + process.exit(1); + } }