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 (
+
setIsGraphOpen(true)} />
@@ -871,7 +921,7 @@ function NotesApp() {
{
- handleSelectNote(noteId);
+ void handleSelectNote(noteId);
setIsGraphOpen(false);
}}
onClose={() => setIsGraphOpen(false)}
@@ -928,6 +978,7 @@ function NotesApp() {
+
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 */}
@@ -309,6 +317,11 @@ export function NoteList({
)}
+ setShowFilterBar(prev => !prev)}
+ />
{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 => (
+ handleStatusClick(opt.value)}
+ >
+ {opt.label}
+
+ ))}
+
+
+ {/* Tag + Sort row */}
+
+
+ Tag
+
+
+ All tags
+ {tags.map(tag => (
+
+ {tag}
+
+ ))}
+
+
+
+
+
+ Sort
+
+
+ {SORT_BY_OPTIONS.map(opt => (
+
+ {opt.label}
+
+ ))}
+
+
+ {sortOrder === 'asc' ? '\u2191 Asc' : '\u2193 Desc'}
+
+
+
+ );
+}
+
+// ============================================================================
+// 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}
+
+ Download
+
+ >
+ )}
+ {state.kind === 'downloading' && (
+
+ Downloading v{state.version}... {state.percent}%
+
+ )}
+ {state.kind === 'error' && (
+ <>
+ Download failed
+
+ Retry
+
+ >
+ )}
+ {state.kind === 'ready' && (
+ <>
+ v{state.version} ready to install
+
+ Restart to Update
+
+ >
+ )}
+ setDismissed(true)}
+ aria-label="Dismiss update banner"
+ >
+
+
+
+ );
+}
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 => (
+
+ ))}
+
+
+
+ onComplete(true)}>
+ Create Your First Note
+
+ onComplete(false)}>
+ Skip
+
+
+
+
+ );
+}
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' && (
+
+ Retry
+
+ )}
+
+ );
+ }
+
+ // 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
+
+ Retry
+
+
+ );
+ }
+
+ // 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 (
@@ -51,6 +63,13 @@ export function SettingsSidebar({ activeSection, onSectionChange }: SettingsSide
);
})}
+
+
+
+
+ Reset to Defaults
+
+
);
}
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() {
- }
onClick={handleSignOut}
disabled={isLoading}
>
-
- Sign Out
-
+ 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"
>
-
}
+ loading={isLoading}
onClick={handleSignIn}
- disabled={isLoading}
>
- {isLoading ? (
- <>
-
-
Loading...
- >
- ) : (
- <>
-
-
Sign In
- >
- )}
-
+ {isLoading ? 'Loading...' : 'Sign In'}
+
)}
@@ -269,24 +260,15 @@ export function AccountSection() {
<>
- }
+ loading={isSyncing || syncStatus === 'syncing'}
onClick={handleSync}
- disabled={isSyncing || syncStatus === 'syncing'}
>
- {isSyncing || syncStatus === 'syncing' ? (
- <>
-
- Syncing...
- >
- ) : (
- <>
-
- Sync
- >
- )}
-
+ {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"
>
- }
+ loading={isManaging}
onClick={handleManageSubscription}
- disabled={isManaging}
>
-
- {isManaging ? 'Opening...' : 'Manage Subscription'}
-
+ {isManaging ? 'Opening...' : 'Manage Subscription'}
+
)}
@@ -381,25 +363,25 @@ export function AccountSection() {
description="Get cloud sync, advanced search, and all pro features"
>
- }
onClick={() => handleUpgrade('monthly')}
disabled={isUpgrading}
>
-
- Monthly - {proPricing.intervals.monthly.label}
-
-
+ }
onClick={() => handleUpgrade('annual')}
disabled={isUpgrading}
>
-
- Annual - {proPricing.intervals.annual.label}
+ Annual - {proPricing.intervals.annual.label}
Save {proPricing.annualSavings}
-
+
)}
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() {
- }
onClick={handleDisconnect}
- style={{ gap: '0.375rem', fontSize: '0.8125rem' }}
>
-
Disconnect
-
+
) : (
@@ -412,25 +403,14 @@ export function AiSection() {
>
Connect your {providerInfo.name} account
- }
onClick={handleOpenKeyPage}
- style={{
- display: 'inline-flex',
- alignItems: 'center',
- gap: '0.375rem',
- padding: '0.375rem 0.75rem',
- background: 'transparent',
- border: '1px solid var(--border)',
- borderRadius: '0.375rem',
- color: 'var(--text-secondary)',
- fontSize: '0.75rem',
- cursor: 'pointer',
- }}
>
-
Get API Key
-
+
{
- if (e.key === 'Enter') handleConnect();
+ if (e.key === 'Enter') void handleConnect();
}}
/>
-
}
+ loading={isConnecting}
onClick={handleConnect}
disabled={isConnecting || !apiKeyInput.trim()}
- style={{ gap: '0.375rem', whiteSpace: 'nowrap' }}
>
- {isConnecting ? (
-
- ) : (
-
- )}
{isConnecting ? 'Connecting...' : 'Connect'}
-
+
>
)}
@@ -481,20 +457,15 @@ export function AiSection() {
Ollama runs locally — no API key needed. Make sure Ollama is running on your
machine.
- }
+ loading={isConnecting}
onClick={handleConnect}
- disabled={isConnecting}
- style={{ alignSelf: 'flex-start', gap: '0.375rem' }}
>
- {isConnecting ? (
-
- ) : (
-
- )}
{isConnecting ? 'Connecting...' : 'Connect to Ollama'}
-
+
)}
@@ -553,22 +524,26 @@ export function AiSection() {
{/* ── Presets ── */}
-
-
- Import
-
+ }
+ onClick={handleImportPreset}
+ >
+ Import
+
- }
onClick={handleExportPreset}
disabled={registeredAiCommands.length === 0}
>
-
- Export
-
+ 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() {
- }
+ loading={isExporting}
onClick={handleExport}
- disabled={isExporting}
>
- {isExporting ? (
-
- ) : (
-
- )}
- {isExporting ? 'Exporting...' : 'Export'}
-
+ {isExporting ? 'Exporting...' : 'Export'}
+
@@ -114,44 +111,40 @@ export function BackupSection() {
label="Import Notes"
description="Import from Obsidian, Markdown folder, or Readied export"
>
- }
+ loading={isImporting}
onClick={handleImport}
- disabled={isImporting}
>
- {isImporting ? (
-
- ) : (
-
- )}
- {isImporting ? 'Importing...' : 'Import'}
-
+ {isImporting ? 'Importing...' : 'Import'}
+
- }
+ loading={isBackingUp}
onClick={handleBackup}
- disabled={isBackingUp}
>
- {isBackingUp ? (
-
- ) : (
-
- )}
- {isBackingUp ? 'Backing up...' : 'Backup Now'}
-
+ {isBackingUp ? 'Backing up...' : 'Backup Now'}
+
-
-
- Open Folder
-
+ }
+ onClick={handleOpenDataFolder}
+ >
+ Open Folder
+
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 && (
- }
onClick={handleRevokeOthers}
disabled={revokeOthersMutation.isPending}
>
-
- Sign out other devices ({otherDeviceCount})
-
+ Sign out other devices ({otherDeviceCount})
+
)}
>
@@ -261,16 +262,12 @@ export function DevicesSection() {
This will sign you out of this device. Continue?
-
+
Sign Out
-
- setConfirmRevokeId(null)}
- >
+
+ setConfirmRevokeId(null)}>
Cancel
-
+
)}
@@ -282,20 +279,12 @@ export function DevicesSection() {
Sign out {otherDeviceCount} other device{otherDeviceCount > 1 ? 's' : ''}?
-
+
Sign Out Others
-
- setConfirmRevokeOthers(false)}
- >
+
+ setConfirmRevokeOthers(false)}>
Cancel
-
+
)}
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"
>
-
-
- Open Folder
-
+ }
+ onClick={handleOpenDataFolder}
+ >
+ Open Folder
+
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 grid */}
- {filteredMarketplace.length > 0 ? (
+ {/* Loading state */}
+ {isLoading ? (
+
+
+
Loading marketplace...
+
+ ) : filteredMarketplace.length > 0 ? (
- {filteredMarketplace.map(plugin => (
-
-
-
{plugin.icon}
-
-
{plugin.name}
-
- {plugin.author} · v{plugin.version}
-
+ {filteredMarketplace.map(plugin => {
+ const isInstalled = installedPluginIds.has(plugin.slug);
+ const isInstalling = installingSlug === plugin.slug;
+
+ return (
+
+
+
{plugin.icon}
+
+ {plugin.name}
+
+ {plugin.author} · v{plugin.version}
+ {plugin.downloads > 0 && (
+ <> · {plugin.downloads.toLocaleString()} downloads>
+ )}
+
+
-
-
{plugin.description}
-
-
- {plugin.tags.slice(0, 3).map(tag => (
-
- {tag}
+ {plugin.description}
+
+
+ {plugin.tags.slice(0, 3).map(tag => (
+
+ {tag}
+
+ ))}
+
+ {plugin.isBuiltIn ? (
+
+
+ Included
- ))}
+ ) : isInstalled ? (
+
+
+ Installed
+
+ ) : (
+
handleInstallFromUrl(plugin)}
+ >
+ {isInstalling ? (
+ <>
+
+ Installing...
+ >
+ ) : (
+ <>
+
+ Install
+ >
+ )}
+
+ )}
- {plugin.builtin ? (
-
-
- Included
-
- ) : (
-
-
- Install
-
- )}
-
- ))}
+ );
+ })}
) : (
@@ -516,10 +651,14 @@ function PluginInspector() {
)}
-
-
- Force Reload All
-
+ }
+ onClick={handleForceReload}
+ >
+ Force Reload All
+
)}
@@ -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 && (
-
}
onClick={handleOpenFolder}
>
-
-
Open Plugins Folder
-
+ Open Plugins Folder
+
)}
@@ -812,31 +980,50 @@ export function PluginsSection() {
{/* Actions bar */}
-
-
- Install from File
-
- }
+ onClick={handleInstall}
+ >
+ Install from File
+
+ }
+ loading={isReloading}
onClick={handleReload}
- disabled={isReloading}
>
-
- {isReloading ? 'Reloading...' : 'Reload Plugins'}
-
+ {isReloading ? 'Reloading...' : 'Reload Plugins'}
+
{pluginsPath && (
-
-
- Open Plugins Folder
-
+ }
+ onClick={handleOpenFolder}
+ >
+ Open Plugins Folder
+
)}
>
)}
- {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 (
-
-
- Check Now
-
+
}
+ onClick={handleCheckForUpdates}
+ >
+ Check Now
+
);
case 'checking':
return (
-
-
- Checking...
-
+
+ Checking...
+
);
case 'available':
return (
-
-
- Download v{state.version}
-
+
}
+ onClick={handleStartDownload}
+ >
+ Download v{state.version}
+
);
case 'downloading':
return (
-
-
- Downloading...
-
+
+ Downloading...
+
);
case 'ready':
return (
-
-
- Restart to Update
-
+
}
+ onClick={handleInstall}
+ >
+ Restart to Update
+
);
case 'installing':
return (
-
-
- Restarting...
-
+
+ Restarting...
+
);
case 'error':
return (
-
-
- Try Again
-
+
}
+ onClick={handleRetry}
+ >
+ Try Again
+
);
}
};
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 += `${cell} `;
+ }
+ html += ' ';
+
+ for (let i = 2; i < lines.length; i++) {
+ const cells = parseRow(lines[i] ?? '');
+ html += '';
+ for (const cell of cells) {
+ html += `${cell} `;
+ }
+ html += ' ';
+ }
+ html += '
';
+ 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: )
+ html = html.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, ' ');
+
+ // 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 ``;
+ });
+
+ // 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 && (
+
+
+ {title}
+
+
+
+
+
+
+
+ )}
+
{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 (
+
+ {loading ? (
+
+ ) : icon ? (
+
+ {icon}
+
+ ) : null}
+ {children}
+
+ );
+ }
+);
+
+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);
+ }
}