diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..2b2bee58 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,133 @@ +name: Release +on: + push: + tags: + - 'v*' + +jobs: + release-linux: + permissions: + contents: write + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: setup bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: install rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install dependencies (ubuntu) + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + + - name: install frontend dependencies + run: bun install + working-directory: ./apps/desktop + + - name: build frontend + run: bun run build + working-directory: ./apps/desktop + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectPath: ./apps/desktop + tagName: ${{ github.ref_name }} + releaseName: 'App v__VERSION__' + releaseBody: 'See the assets to download this version and install.' + releaseDraft: true + prerelease: false + + release-windows: + permissions: + contents: write + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: setup bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: install rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install frontend dependencies + run: bun install + working-directory: ./apps/desktop + + - name: build frontend + run: bun run build + working-directory: ./apps/desktop + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectPath: ./apps/desktop + tagName: ${{ github.ref_name }} + releaseName: 'App v__VERSION__' + releaseBody: 'See the assets to download this version and install.' + releaseDraft: true + prerelease: false + + release-macos: + permissions: + contents: write + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + + - name: setup node + uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: setup bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: install rust stable + uses: dtolnay/rust-toolchain@stable + + - name: install frontend dependencies + run: bun install + working-directory: ./apps/desktop + + - name: build frontend + run: bun run build + working-directory: ./apps/desktop + + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} + with: + projectPath: ./apps/desktop + tagName: ${{ github.ref_name }} + releaseName: 'App v__VERSION__' + releaseBody: 'See the assets to download this version and install.' + releaseDraft: true + prerelease: false diff --git a/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts b/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts index 015d91fd..184a958b 100644 --- a/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts +++ b/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts @@ -11,7 +11,8 @@ import { isInsideJoinParens, getTableMatch, getColumnMatch -} from '../../../../src/features/drizzle-runner/utils/lsp-patterns' +} from '../../../../../apps/desktop/src/features/drizzle-runner/utils/lsp-patterns' + describe('Drizzle LSP Patterns', () => { describe('getDbName', () => { diff --git a/__tests__/apps/desktop/src/features/docker-manager/api/container-service.test.ts b/__tests__/apps/desktop/src/features/docker-manager/api/container-service.test.ts index 2ed960af..ed6c9295 100644 --- a/__tests__/apps/desktop/src/features/docker-manager/api/container-service.test.ts +++ b/__tests__/apps/desktop/src/features/docker-manager/api/container-service.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { seedDatabase } from '../../../../../../../apps/desktop/src/features/docker-manager/api/container-service' + // Mock the docker-client module vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client', () => ({ @@ -10,15 +10,36 @@ vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/api/docke })) describe('seedDatabase', () => { - beforeEach(() => { + // We need to keep a reference to the dynamically imported module + let containerService: any; + let dockerClient: any; + + beforeEach(async () => { + vi.resetModules() // Important: clear cache so isTauri is re-evaluated vi.clearAllMocks() + + // Mock Tauri environment to ensure we test the real implementation + Object.defineProperty(window, '__TAURI_INTERNALS__', { + value: {}, + writable: true, + configurable: true + }) + + // Re-import modules after setting up environment + dockerClient = await import('../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client') + containerService = await import('../../../../../../../apps/desktop/src/features/docker-manager/api/container-service') + }) + + afterEach(() => { + // Cleanup environment + // @ts-ignore + delete window['__TAURI_INTERNALS__'] }) it('should successfully copy, exec, and cleanup', async () => { - const { copyToContainer, execCommand } = - await import('../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client') + const { copyToContainer, execCommand } = dockerClient - const result = await seedDatabase('container-123', '/path/to/seed.sql', { + const result = await containerService.seedDatabase('container-123', '/path/to/seed.sql', { user: 'testuser', database: 'testdb' }) @@ -48,15 +69,15 @@ describe('seedDatabase', () => { }) it('should handle exec failure', async () => { - const { execCommand } = - await import('../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client') + const { execCommand } = dockerClient + vi.mocked(execCommand).mockResolvedValueOnce({ stdout: '', stderr: 'psql error', exitCode: 1 }) - const result = await seedDatabase('container-123', 'file.sql', { user: 'u', database: 'd' }) + const result = await containerService.seedDatabase('container-123', 'file.sql', { user: 'u', database: 'd' }) expect(result.success).toBe(false) expect(result.error).toContain('psql error') diff --git a/__tests__/apps/desktop/src/features/docker-manager/api/docker-client.test.ts b/__tests__/apps/desktop/src/features/docker-manager/api/docker-client.test.ts index 6352b263..8f76c9fb 100644 --- a/__tests__/apps/desktop/src/features/docker-manager/api/docker-client.test.ts +++ b/__tests__/apps/desktop/src/features/docker-manager/api/docker-client.test.ts @@ -4,42 +4,20 @@ import { checkDockerAvailability, startContainer, stopContainer, - removeContainer + removeContainer, + deps } from '../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client' -// Mock the Tauri shell plugin -const { mockExecute, mockCreate } = vi.hoisted(() => { - const execute = vi.fn(() => Promise.resolve({ stdout: '', stderr: '', code: 0 })) - const create = vi.fn(() => ({ - execute: execute, - on: vi.fn(), - spawn: vi.fn().mockResolvedValue({ - kill: vi.fn() - }), - stdout: { on: vi.fn() }, - stderr: { on: vi.fn() } - })) - return { mockExecute: execute, mockCreate: create } -}) - -vi.mock('@tauri-apps/plugin-shell', () => ({ - Command: { - create: (cmd: string, args: string[]) => mockCreate(cmd, args) - } -})) - -// Mock Tauri API core directly to prevent "invoke" errors if leakage occurs +// Mock Tauri API core vi.mock('@tauri-apps/api/core', () => ({ invoke: vi.fn().mockResolvedValue('') })) -// Mock window to simulate Tauri environment Object.defineProperty(window, 'Tauri', { value: {}, writable: true }) -// Mock Tauri internals required by some real modules if they slip through Object.defineProperty(window, '__TAURI_INTERNALS__', { value: { invoke: vi.fn() @@ -48,9 +26,27 @@ Object.defineProperty(window, '__TAURI_INTERNALS__', { }) describe('docker-client', () => { + let mockExecute: any + let mockCreate: any + beforeEach(() => { vi.clearAllMocks() - mockExecute.mockResolvedValue({ stdout: '', stderr: '', code: 0 }) + mockExecute = vi.fn().mockResolvedValue({ stdout: '', stderr: '', code: 0 }) + mockCreate = vi.fn() + + // Inject mock + deps.getCommand = async () => ({ + create: (cmd: string, args: string[]) => { + mockCreate(cmd, args) + return { + execute: mockExecute, + on: vi.fn(), + spawn: vi.fn().mockResolvedValue({ kill: vi.fn() }), + stdout: { on: vi.fn() }, + stderr: { on: vi.fn() } + } + } + } as any) }) describe('checkDockerAvailability', () => { @@ -65,9 +61,9 @@ describe('docker-client', () => { expect(result).toEqual({ available: true, version: '20.10.21' }) expect(mockCreate).toHaveBeenCalledWith('docker', [ - 'version', + 'info', '--format', - '{{.Server.Version}}' + '{{.ServerVersion}}' ]) }) @@ -86,14 +82,13 @@ describe('docker-client', () => { describe('listContainers', () => { it('should list and parse containers correctly including Env', async () => { - // Mock 'ps' command mockExecute.mockResolvedValueOnce({ - stdout: '{"ID":"123","Names":"test-container","Image":"postgres:14","State":"running","Status":"Up 2 hours","Ports":"0.0.0.0:5432->5432/tcp","Labels":"","CreatedAt":"2023-01-01"}\n', + stdout: + '{"ID":"123","Names":"test-container","Image":"postgres:14","State":"running","Status":"Up 2 hours","Ports":"0.0.0.0:5432->5432/tcp","Labels":"","CreatedAt":"2023-01-01"}\n', stderr: '', code: 0 }) - // Mock 'inspect' command mockExecute.mockResolvedValueOnce({ stdout: JSON.stringify([ { diff --git a/__tests__/apps/desktop/src/features/docker-manager/components/docker-view.test.tsx b/__tests__/apps/desktop/src/features/docker-manager/components/docker-view.test.tsx index 65255e84..96f6db09 100644 --- a/__tests__/apps/desktop/src/features/docker-manager/components/docker-view.test.tsx +++ b/__tests__/apps/desktop/src/features/docker-manager/components/docker-view.test.tsx @@ -1,15 +1,16 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { render, screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' import { DockerView } from '../../../../../../../apps/desktop/src/features/docker-manager/components/docker-view' import * as useContainersModule from '../../../../../../../apps/desktop/src/features/docker-manager/api/queries/use-containers' import * as useCreateContainerModule from '../../../../../../../apps/desktop/src/features/docker-manager/api/mutations/use-create-container' +import { TooltipProvider } from '@/shared/ui/tooltip' // Mock UI components to avoid dependency issues and focus on logic vi.mock('@/components/ui/use-toast', () => ({ useToast: () => ({ toast: vi.fn() }) })) -vi.mock('./container-list', () => ({ +vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/container-list', () => ({ ContainerList: ({ containers, isLoading }: any) => (
{isLoading ? 'Loading containers...' : `Containers: ${containers?.length || 0}`} @@ -17,15 +18,15 @@ vi.mock('./container-list', () => ({ ) })) -vi.mock('./container-details-panel', () => ({ +vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/container-details-panel', () => ({ ContainerDetailsPanel: () =>
})) -vi.mock('./create-container-dialog', () => ({ +vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/create-container-dialog', () => ({ CreateContainerDialog: ({ open }: any) => (open ?
: null) })) -vi.mock('./sandbox-indicator', () => ({ +vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/sandbox-indicator', () => ({ SandboxIndicator: () =>
})) @@ -44,6 +45,14 @@ describe('DockerView', () => { ) }) + const renderWithProviders = (component: any) => { + return render( + + {component} + + ) + } + it('shows loading state when checking docker availability', () => { vi.spyOn(useContainersModule, 'useDockerAvailability').mockReturnValue({ data: undefined, @@ -58,7 +67,7 @@ describe('DockerView', () => { // Mock useContainerSearch to return empty array vi.spyOn(useContainersModule, 'useContainerSearch').mockReturnValue([]) - render() + renderWithProviders() expect(screen.getByText('Checking Docker status...')).toBeInTheDocument() }) @@ -75,7 +84,7 @@ describe('DockerView', () => { vi.spyOn(useContainersModule, 'useContainerSearch').mockReturnValue([]) - render() + renderWithProviders() expect(screen.getByText('Docker Not Available')).toBeInTheDocument() expect(screen.getByText('Connection failed')).toBeInTheDocument() }) @@ -87,8 +96,8 @@ describe('DockerView', () => { } as any) const mockContainers = [ - { id: '1', name: 'test-1' }, - { id: '2', name: 'test-2' } + { id: '1', names: ['test-1'], state: 'running', created: Date.now() }, + { id: '2', names: ['test-2'], state: 'exited', created: Date.now() } ] vi.spyOn(useContainersModule, 'useContainers').mockReturnValue({ @@ -99,7 +108,7 @@ describe('DockerView', () => { // Mock search to return all containers vi.spyOn(useContainersModule, 'useContainerSearch').mockReturnValue(mockContainers as any) - render() + renderWithProviders() expect(screen.getByText('Containers: 2')).toBeInTheDocument() expect(screen.getByText('Docker Containers')).toBeInTheDocument() }) @@ -117,7 +126,7 @@ describe('DockerView', () => { vi.spyOn(useContainersModule, 'useContainerSearch').mockReturnValue([]) - render() + renderWithProviders() const newButton = screen.getByText('New Container') fireEvent.click(newButton) diff --git a/__tests__/setup/vitest.setup.ts b/__tests__/setup/vitest.setup.ts index 16c43313..5472f719 100644 --- a/__tests__/setup/vitest.setup.ts +++ b/__tests__/setup/vitest.setup.ts @@ -1,4 +1,5 @@ import { cleanup } from '@testing-library/react' +import '@testing-library/jest-dom/vitest' import { expect, afterEach } from 'vitest' // Cleanup after each test diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 7d2acfa7..da46fe35 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -19,22 +19,14 @@ "@faker-js/faker": "^10.2.0", "@hookform/resolvers": "^3.10.0", "@monaco-editor/react": "^4.6.0", - "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", - "@radix-ui/react-aspect-ratio": "^1.1.7", - "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-menubar": "^1.1.15", - "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -43,8 +35,6 @@ "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toast": "^1.2.14", - "@radix-ui/react-toggle": "^1.1.9", - "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", "@tanstack/react-query": "^5.83.0", "@tauri-apps/api": "^2.9.1", @@ -53,26 +43,18 @@ "@tauri-apps/plugin-shell": "^2.3.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cmdk": "^1.1.1", "date-fns": "^3.6.0", - "embla-carousel-react": "^8.6.0", "framer-motion": "^12.29.0", - "input-otp": "^1.4.2", "lucide-react": "^0.462.0", "monaco-vim": "^0.4.4", "next-themes": "^0.3.0", "react": "^18.3.1", - "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.61.1", "react-resizable-panels": "^2.1.9", "react-router-dom": "^6.30.1", - "recharts": "^2.15.4", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "vaul": "^0.9.9", - "zod": "^3.25.76", "zustand": "^5.0.10" }, "devDependencies": { @@ -89,6 +71,7 @@ "happy-dom": "^20.3.7", "postcss": "^8.5.6", "rolldown": "^1.0.0-beta.57", + "rollup-plugin-visualizer": "^6.0.5", "tailwindcss": "^4.1.18", "typescript": "^5.8.3", "vite": "npm:rolldown-vite@latest", diff --git a/apps/desktop/src-tauri/src/database/commands.rs b/apps/desktop/src-tauri/src/database/commands.rs index 403f76da..baaf1915 100644 --- a/apps/desktop/src-tauri/src/database/commands.rs +++ b/apps/desktop/src-tauri/src/database/commands.rs @@ -245,14 +245,13 @@ pub async fn fetch_page( query_id: usize, page_index: usize, state: State<'_, AppState>, -) -> Result, Error> { +) -> Result>, Error> { let svc = QueryService { connections: &state.connections, storage: &state.storage, stmt_manager: &state.stmt_manager, }; - let res = svc.fetch_page(query_id, page_index).await?; - Ok(res.map(|r| serde_json::from_str(r.get()).unwrap_or_default())) + svc.fetch_page(query_id, page_index).await } #[tauri::command] @@ -288,14 +287,13 @@ pub async fn get_page_count( pub async fn get_columns( query_id: usize, state: State<'_, AppState>, -) -> Result, Error> { +) -> Result>, Error> { let svc = QueryService { connections: &state.connections, storage: &state.storage, stmt_manager: &state.stmt_manager, }; - let res = svc.get_columns(query_id).await?; - Ok(res.map(|r| serde_json::from_str(r.get()).unwrap_or_default())) + svc.get_columns(query_id).await } #[tauri::command] diff --git a/apps/desktop/src-tauri/src/database/services/mutation.rs b/apps/desktop/src-tauri/src/database/services/mutation.rs index c7feb877..f392d180 100644 --- a/apps/desktop/src-tauri/src/database/services/mutation.rs +++ b/apps/desktop/src-tauri/src/database/services/mutation.rs @@ -228,8 +228,12 @@ impl<'a> MutationService<'a> { }); } - let columns: Vec = row_data.keys().cloned().collect(); - let values: Vec = row_data.values().cloned().collect(); + // Optimization: Use references to avoid cloning keys/values into new Vecs + // We still need to collect keys for the query string since we iterate twice (cols then values) + // But for values we can map directly to params. + + let columns: Vec<&String> = row_data.keys().collect(); + let values_len = row_data.len(); let (affected_rows, message) = match &client { DatabaseClient::Postgres { client } => { @@ -243,7 +247,7 @@ impl<'a> MutationService<'a> { .collect::>() .join(", "); - let placeholders: String = (1..=values.len()) + let placeholders: String = (1..=values_len) .map(|i| format!("${}", i)) .collect::>() .join(", "); @@ -253,8 +257,8 @@ impl<'a> MutationService<'a> { schema_prefix, table_name, col_names, placeholders ); - let params: Vec> = values - .iter() + let params: Vec> = row_data + .values() .map(|v| json_to_pg_param(v)) .collect(); let params_ref: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = params @@ -272,8 +276,8 @@ impl<'a> MutationService<'a> { .collect::>() .join(", "); - let placeholders: String = (0..values.len()) - .map(|_| "?") + let placeholders: String = std::iter::repeat("?") + .take(values_len) .collect::>() .join(", "); @@ -282,8 +286,8 @@ impl<'a> MutationService<'a> { table_name, col_names, placeholders ); - let params: Vec = values - .iter() + let params: Vec = row_data + .values() .map(json_to_sqlite_value) .collect(); let params_ref: Vec<&dyn rusqlite::ToSql> = @@ -298,18 +302,18 @@ impl<'a> MutationService<'a> { .collect::>() .join(", "); - let placeholders: String = (0..values.len()) - .map(|_| "?") - .collect::>() - .join(", "); + let placeholders: String = std::iter::repeat("?") + .take(values_len) + .collect::>() + .join(", "); let query = format!( "INSERT INTO \"{}\" ({}) VALUES ({})", table_name, col_names, placeholders ); - let params: Vec = values - .iter() + let params: Vec = row_data + .values() .map(json_to_libsql_value) .collect(); diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 7b88d2c6..43117805 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -6,7 +6,7 @@ "build": { "frontendDist": "../dist", "devUrl": "http://localhost:1420", - "beforeDevCommand": "bun run dev", + "beforeDevCommand": "bun vite --port 1420 --strictPort", "beforeBuildCommand": "bun run build", "removeUnusedCommands": true }, @@ -33,7 +33,9 @@ "active": true, "targets": [ "deb", - "appimage" + "appimage", + "nsis", + "dmg" ], "shortDescription": "A modern database client", "longDescription": "Dora is a modern, fast, and efficient database client for PostgreSQL, SQLite, and more.", diff --git a/apps/desktop/src/core/data-provider/hooks.ts b/apps/desktop/src/core/data-provider/hooks.ts index e0094297..5634232b 100644 --- a/apps/desktop/src/core/data-provider/hooks.ts +++ b/apps/desktop/src/core/data-provider/hooks.ts @@ -1,4 +1,4 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import { useMutation, useQuery, useQueryClient, keepPreviousData } from '@tanstack/react-query' import type { SortDescriptor, FilterDescriptor } from '@/features/database-studio/types' import { useAdapter } from './context' @@ -138,7 +138,10 @@ export function useTableData( if (!res.ok) throw new Error(res.error) return res.data }, - enabled: !!connectionId && !!tableName + enabled: !!connectionId && !!tableName, + placeholderData: keepPreviousData, + staleTime: 10000, // 10 seconds + gcTime: 5 * 60 * 1000 // 5 minutes }) } @@ -178,7 +181,43 @@ export function useDataMutation() { if (!res.ok) throw new Error(res.error) return res.data }, - onSuccess: (_data, variables) => { + onMutate: async (newEdit) => { + // Cancel any outgoing refetches (so they don't overwrite our optimistic update) + await queryClient.cancelQueries({ queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] }) + + // Snapshot the previous value + const previousTableData = queryClient.getQueriesData({ queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] }) + + // Optimistically update to the new value + queryClient.setQueriesData({ queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] }, (old: any) => { + if (!old) return old + return { + ...old, + rows: old.rows.map((row: any) => { + if (row[newEdit.primaryKeyColumn] === newEdit.primaryKeyValue) { + return { + ...row, + [newEdit.columnName]: newEdit.newValue + } + } + return row + }) + } + }) + + // Return a context object with the snapshotted value + return { previousTableData } + }, + onError: (_err, _newEdit, context) => { + // If the mutation fails, use the context returned from onMutate to roll back + if (context?.previousTableData) { + context.previousTableData.forEach(([key, data]) => { + queryClient.setQueryData(key, data) + }) + } + }, + onSettled: (_data, _error, variables) => { + // Always refetch after error or success: queryClient.invalidateQueries({ queryKey: ['tableData', variables.connectionId, variables.tableName] }) diff --git a/apps/desktop/src/features/database-studio/components/data-grid.tsx b/apps/desktop/src/features/database-studio/components/data-grid.tsx index 4d0d13b6..bcc192eb 100644 --- a/apps/desktop/src/features/database-studio/components/data-grid.tsx +++ b/apps/desktop/src/features/database-studio/components/data-grid.tsx @@ -549,14 +549,6 @@ export function DataGrid({ ] ) - if (columns.length === 0) { - return ( -
- No columns found for this table -
- ) - } - function handleRightDragValues(e: React.MouseEvent | MouseEvent) { if (!rightDragStartRef.current || !scrollContainerRef.current) return @@ -597,6 +589,14 @@ export function DataGrid({ } }, [isRightDragging]) + if (columns.length === 0) { + return ( +
+ No columns found for this table +
+ ) + } + return (
| null>(null) const [isLoading, setIsLoading] = useState(false) - const [showSkeleton, setShowSkeleton] = useState(false) + const [isTableTransitioning, setIsTableTransitioning] = useState(false) const [viewMode, setViewMode] = useState('content') + const previousTableRef = useRef<{ columns: number; rows: number } | null>(null) const [pagination, setPagination] = useState({ limit: 50, offset: 0 }) const [sort, setSort] = useState() const [filters, setFilters] = useState([]) @@ -124,16 +125,14 @@ export function DatabaseStudio({ ] ) - // Delay showing skeleton to avoid flash for fast queries - useEffect(() => { - let timer: ReturnType - if (isLoading && !tableData) { - timer = setTimeout(() => setShowSkeleton(true), 150) - } else { - setShowSkeleton(false) + useEffect(function cachePreviousTableDimensions() { + if (tableData && !isLoading) { + previousTableRef.current = { + columns: tableData.columns.length, + rows: Math.min(tableData.rows.length, 12) + } } - return () => clearTimeout(timer) - }, [isLoading, tableData]) + }, [tableData, isLoading]) const loadTableData = useCallback(async () => { console.log('[DatabaseStudio] loadTableData called', { tableId, activeConnectionId }) @@ -205,15 +204,27 @@ export function DatabaseStudio({ const { trackCellMutation, trackBatchCellMutation } = useUndo({ onUndoComplete: loadTableData }) - // Reset state when table changes - useEffect(() => { + useEffect(function handleTableChange() { + if (!tableId) return setPagination({ limit: 50, offset: 0 }) setSort(undefined) setFilters([]) setVisibleColumns(new Set()) initializedFromUrlRef.current = false + setIsTableTransitioning(true) }, [tableId]) + useEffect(function clearTransitionOnLoad() { + if (!isLoading && tableData) { + const timer = setTimeout(function () { + setIsTableTransitioning(false) + }, 50) + return function () { + clearTimeout(timer) + } + } + }, [isLoading, tableData]) + useEffect( function initializeFromUrl() { if (initializedFromUrlRef.current || !tableData) return @@ -1268,49 +1279,63 @@ export function DatabaseStudio({ onDryEditModeChange={setDryEditMode} /> -
- {showSkeleton ? ( - - ) : tableData ? ( - visibleColumns.has(col.name))} - rows={tableData.rows} - selectedRows={selectedRows} - onRowSelect={handleRowSelect} - onRowsSelect={handleRowsSelect} - onSelectAll={handleSelectAll} - sort={sort} - onSortChange={setSort} - onFilterAdd={function (filter) { - setFilters(function (prev) { - return [...prev, filter] - }) - }} - onCellEdit={handleCellEdit} - onBatchCellEdit={handleBatchCellEdit} - onRowAction={handleRowAction} - tableName={tableName || tableId} - selectedCells={selectedCells} - onCellSelectionChange={setSelectedCells} - initialFocusedCell={focusedCell} - onFocusedCellChange={setFocusedCell} - onContextMenuChange={setContextMenuState} - draftRow={draftRow} - onDraftChange={handleDraftChange} - onDraftSave={handleDraftSave} - onDraftCancel={handleDraftCancel} - pendingEdits={ - tableId - ? new Set( - getEditsForTable(tableId).map( - (e) => `${e.primaryKeyValue}:${e.columnName}` +
+ {tableData && ( +
+ - ) : ( + : undefined + } + draftInsertIndex={draftInsertIndex} + /> +
+ )} + {isTableTransitioning && ( +
+ +
+ )} + {!tableData && !isTableTransitioning && (
No data available
diff --git a/apps/desktop/src/features/database-studio/utils/get-column-icon.tsx b/apps/desktop/src/features/database-studio/utils/get-column-icon.tsx deleted file mode 100644 index 4efa9c02..00000000 --- a/apps/desktop/src/features/database-studio/utils/get-column-icon.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { - Hash, - Type, - Calendar, - ToggleLeft, - Braces, - Fingerprint, - FileText, - List, - Clock -} from 'lucide-react' - -export function getColumnIcon(type: string) { - const normalizeType = type.toLowerCase() - - if ( - normalizeType.includes('int') || - normalizeType.includes('serial') || - normalizeType.includes('numeric') || - normalizeType.includes('decimal') || - normalizeType.includes('double') || - normalizeType.includes('real') - ) { - return Hash - } - - if ( - normalizeType.includes('char') || - normalizeType.includes('text') || - normalizeType.includes('string') - ) { - return Type - } - - if (normalizeType.includes('bool')) { - return ToggleLeft - } - - if (normalizeType.includes('timestamp') || normalizeType.includes('date')) { - return Calendar - } - - if (normalizeType.includes('time')) { - return Clock - } - - if (normalizeType.includes('json') || normalizeType.includes('jsonb')) { - return Braces - } - - if (normalizeType.includes('array')) { - return List - } - - if (normalizeType.includes('uuid')) { - return Fingerprint - } - - return FileText -} diff --git a/apps/desktop/src/features/docker-manager/api/docker-client.ts b/apps/desktop/src/features/docker-manager/api/docker-client.ts index b84df555..ee4a3c0b 100644 --- a/apps/desktop/src/features/docker-manager/api/docker-client.ts +++ b/apps/desktop/src/features/docker-manager/api/docker-client.ts @@ -50,11 +50,19 @@ type DockerPsResult = { CreatedAt: string } +// Exported for testing +export const deps = { + getCommand: async () => { + const { Command } = await import('@tauri-apps/plugin-shell') + return Command + } +} + export async function executeDockerCommand( args: string[] ): Promise<{ stdout: string; stderr: string; exitCode: number }> { if (typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)) { - const { Command } = await import('@tauri-apps/plugin-shell') + const Command = await deps.getCommand() const command = Command.create('docker', args) const output = await command.execute() return { diff --git a/apps/desktop/src/features/docker-manager/api/queries/use-container-sizes.ts b/apps/desktop/src/features/docker-manager/api/queries/use-container-sizes.ts deleted file mode 100644 index ae0f7b35..00000000 --- a/apps/desktop/src/features/docker-manager/api/queries/use-container-sizes.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import type { ContainerSize } from '../../types' -import { getContainerSizes } from '../docker-client' - -type UseContainerSizesOptions = { - enabled?: boolean -} - -export function useContainerSizes(options: UseContainerSizesOptions = {}) { - const { enabled = false } = options - - return useQuery({ - queryKey: ['docker-container-sizes'], - queryFn: getContainerSizes, - enabled, - staleTime: 30000, - refetchOnWindowFocus: false - }) -} diff --git a/apps/desktop/src/features/docker-manager/components/docker-view.tsx b/apps/desktop/src/features/docker-manager/components/docker-view.tsx index 7f2b9aa3..05ef35f8 100644 --- a/apps/desktop/src/features/docker-manager/components/docker-view.tsx +++ b/apps/desktop/src/features/docker-manager/components/docker-view.tsx @@ -65,13 +65,15 @@ export function DockerView({ onOpenInDataViewer }: Props) { // Sort return [...result].sort(function (a, b) { if (sortBy === 'name') { - return a.names[0].localeCompare(b.names[0]) + const nameA = a.names?.[0] || '' + const nameB = b.names?.[0] || '' + return nameA.localeCompare(nameB) } if (sortBy === 'created') { return b.created - a.created // Newest first } if (sortBy === 'status') { - return a.state.localeCompare(b.state) + return (a.state || '').localeCompare(b.state || '') } return 0 }) diff --git a/apps/desktop/src/features/docker-manager/hooks/use-container-history.ts b/apps/desktop/src/features/docker-manager/hooks/use-container-history.ts deleted file mode 100644 index f68ab357..00000000 --- a/apps/desktop/src/features/docker-manager/hooks/use-container-history.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { useMemo, useCallback } from 'react' -import { useDockerManagerStore } from '../stores/docker-manager-store' -import type { ContainerEvent, ContainerEventType } from '../types' - -export function useContainerHistory(containerId: string | null) { - const events = useDockerManagerStore(function (s) { return s.events }) - const addEvent = useDockerManagerStore(function (s) { return s.addEvent }) - const clearHistory = useDockerManagerStore(function (s) { return s.clearHistory }) - - const containerEvents = useMemo(function () { - if (!containerId) return [] - return events.filter(function (e) { - return e.containerId === containerId - }) - }, [events, containerId]) - - const trackEvent = useCallback(function ( - type: ContainerEventType, - containerName: string - ) { - if (!containerId) return - addEvent({ - containerId, - containerName, - type - }) - }, [containerId, addEvent]) - - const clearContainerHistory = useCallback(function () { - if (containerId) { - clearHistory(containerId) - } - }, [containerId, clearHistory]) - - return { - events: containerEvents, - trackEvent, - clearContainerHistory - } -} diff --git a/apps/desktop/src/features/docker-manager/hooks/use-container-sort-filter.ts b/apps/desktop/src/features/docker-manager/hooks/use-container-sort-filter.ts deleted file mode 100644 index 8730be0f..00000000 --- a/apps/desktop/src/features/docker-manager/hooks/use-container-sort-filter.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { useMemo } from 'react' -import { useDockerManagerStore } from '../stores/docker-manager-store' -import { useContainerSizes } from '../api/queries/use-container-sizes' -import type { DockerContainer, ContainerSize } from '../types' - -export function useContainerSortFilter( - containers: DockerContainer[] | undefined, - searchQuery: string -) { - const sort = useDockerManagerStore(function (s) { return s.sort }) - const filter = useDockerManagerStore(function (s) { return s.filter }) - - const { data: sizes } = useContainerSizes({ - enabled: sort.field === 'size' - }) - - const result = useMemo(function () { - if (!containers) return [] - - let filtered = containers - - // Text search - if (searchQuery.trim()) { - const query = searchQuery.toLowerCase().trim() - filtered = filtered.filter(function (c) { - return ( - c.name.toLowerCase().includes(query) || - c.image.toLowerCase().includes(query) || - c.id.toLowerCase().startsWith(query) - ) - }) - } - - // State filter - if (filter.states.length > 0) { - filtered = filtered.filter(function (c) { - return filter.states.includes(c.state) - }) - } - - // Health filter - if (filter.healths.length > 0) { - filtered = filtered.filter(function (c) { - return filter.healths.includes(c.health) - }) - } - - // Origin filter - if (filter.origins.length > 0) { - filtered = filtered.filter(function (c) { - return filter.origins.includes(c.origin) - }) - } - - // Sort - const sorted = [...filtered].sort(function (a, b) { - let comparison = 0 - - switch (sort.field) { - case 'name': - comparison = a.name.localeCompare(b.name) - break - case 'createdAt': - comparison = a.createdAt - b.createdAt - break - case 'state': - comparison = a.state.localeCompare(b.state) - break - case 'origin': - comparison = a.origin.localeCompare(b.origin) - break - case 'size': - comparison = getSizeForContainer(a.id, sizes) - getSizeForContainer(b.id, sizes) - break - } - - return sort.direction === 'asc' ? comparison : -comparison - }) - - return sorted - }, [containers, searchQuery, filter, sort, sizes]) - - return result -} - -function getSizeForContainer(containerId: string, sizes: ContainerSize[] | undefined): number { - if (!sizes) return 0 - const entry = sizes.find(function (s) { - return containerId.startsWith(s.containerId) || s.containerId.startsWith(containerId) - }) - return entry ? entry.virtualSize : 0 -} diff --git a/apps/desktop/src/features/drizzle-runner/components/lsp-demo-widget.tsx b/apps/desktop/src/features/drizzle-runner/components/lsp-demo-widget.tsx index 20ab8d01..6d674bff 100644 --- a/apps/desktop/src/features/drizzle-runner/components/lsp-demo-widget.tsx +++ b/apps/desktop/src/features/drizzle-runner/components/lsp-demo-widget.tsx @@ -112,6 +112,8 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { const [currentStepIndex, setCurrentStepIndex] = useState(0) const [showSettings, setShowSettings] = useState(false) const [autoAdvance, setAutoAdvance] = useState(true) + // ... existing state ... + const [isSuccess, setIsSuccess] = useState(false) const [demos, setDemos] = useState(DEFAULT_DEMOS) const timeoutRef = useRef | null>(null) @@ -164,19 +166,11 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { const stepText = typeof stepData === 'string' ? stepData : stepData.text const typeCharsLimit = typeof stepData === 'object' ? stepData.typeChars : undefined - // Determine target length: existing text length + diff - // But here stepText IS the full text we want to achieve. - // We start typing from 'startTextLength' index. - const charsToType = stepText.slice(startTextLength) - // If simulated autocomplete is used, we only type 'typeCharsLimit' characters - // then pause, then jump to full text. - const effectiveLimit = typeCharsLimit !== undefined ? typeCharsLimit : charsToType.length - // Current index relative to the diff part const relativeIndex = charIndex - startTextLength if (relativeIndex < effectiveLimit && charIndex < stepText.length) { @@ -187,21 +181,13 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { typeStep(stepData, charIndex + 1, startTextLength) }, speed) } else { - // Typing finished (either fully typed or hit the simulated limit) - - // If we hit limit but haven't finished text, we do the "autocomplete jump" if (charIndex < stepText.length) { - // Pause to simulate user looking at suggestions timeoutRef.current = setTimeout(function performAutocomplete() { if (!isPlayingRef.current) return - // Jump to full text setEditorContent(stepText) - - // Then schedule next step scheduleNextStep() - }, pauseBetweenSteps) // Use same pause or separate shorter pause? + }, pauseBetweenSteps) } else { - // Fully typed, just wait for next step scheduleNextStep() } } @@ -214,12 +200,16 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { if (nextStepIndex < currentDemo.steps.length) { setCurrentStepIndex(nextStepIndex) } else if (autoAdvance) { - // Move to next demo - const nextDemoIndex = (currentDemoIndex + 1) % demos.length - setCurrentDemoIndex(nextDemoIndex) - setCurrentStepIndex(0) + const nextDemoIndex = currentDemoIndex + 1 + if (nextDemoIndex < demos.length) { + setCurrentDemoIndex(nextDemoIndex) + setCurrentStepIndex(0) + } else { + // All demos finished + finishSequence() + } } else { - // Stop at end of demo + // Single demo finished setIsPlaying(false) isPlayingRef.current = false } @@ -239,19 +229,39 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { ] ) + function finishSequence() { + setIsPlaying(false) + isPlayingRef.current = false + setIsSuccess(true) + } + + // Auto-close on success + useEffect( + function handleSuccessAutoClose() { + if (isSuccess) { + const timer = setTimeout(function closeWidget() { + setIsSuccess(false) + if (onClose) onClose() + }, 2000) + return function cleanup() { + clearTimeout(timer) + } + } + }, + [isSuccess, onClose] + ) + // Start typing when step changes useEffect( function onStepChange() { if (isPlayingRef.current && currentDemo) { const stepData = currentDemo.steps[currentStepIndex] if (stepData) { - // Get previous step to know where to start typing from const prevStepData = currentStepIndex > 0 ? currentDemo.steps[currentStepIndex - 1] : '' const prevText = typeof prevStepData === 'string' ? prevStepData : prevStepData.text - // Start typing from where previous left off typeStep(stepData, prevText.length, prevText.length) } } @@ -267,6 +277,13 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { } else { setIsPlaying(true) isPlayingRef.current = true + if (isSuccess) { + // Restart if we were in success state + setIsSuccess(false) + setCurrentDemoIndex(0) + setCurrentStepIndex(0) + setEditorContent('') + } // Start logic const stepData = currentDemo.steps[currentStepIndex] @@ -285,6 +302,7 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { const nextDemoIndex = (currentDemoIndex + 1) % demos.length setCurrentDemoIndex(nextDemoIndex) setCurrentStepIndex(0) + setIsSuccess(false) if (isPlaying) { // Will auto-start via useEffect @@ -295,6 +313,7 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { clearTimeouts() setIsPlaying(false) isPlayingRef.current = false + setIsSuccess(false) setCurrentDemoIndex(0) setCurrentStepIndex(0) setEditorContent('') @@ -304,6 +323,7 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { clearTimeouts() setCurrentDemoIndex(index) setCurrentStepIndex(0) + setIsSuccess(false) setEditorContent('') } @@ -328,7 +348,8 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { outline: 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground', secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80', - ghost: 'hover:bg-accent hover:text-accent-foreground' + ghost: 'hover:bg-accent hover:text-accent-foreground', + success: 'bg-green-500 text-white shadow hover:bg-green-600' } const sizes: Record = { default: 'h-9 px-4 py-2', @@ -352,7 +373,7 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { {/* Header */}
- ๐ŸŽฌ LSP Demo Widget + {isSuccess ? '๐ŸŽ‰ Demo Complete!' : '๐ŸŽฌ LSP Demo Widget'}
- - -
+ {/* Playback Controls */} +
+ + + +
+ + )} {/* Settings Panel */} - {showSettings && ( + {showSettings && !isSuccess && (
@@ -457,26 +495,28 @@ export function LspDemoWidget({ editorRef, onClose }: Props) { )} {/* Demo Selector */} -
- -
- {demos.map(function renderDemo(demo, index) { - return ( - - ) - })} + {!isSuccess && ( +
+ +
+ {demos.map(function renderDemo(demo, index) { + return ( + + ) + })} +
-
+ )}
) diff --git a/apps/desktop/src/features/sidebar/components/appearance-panel.tsx b/apps/desktop/src/features/sidebar/components/appearance-panel.tsx index 6877eb2e..0e93c90c 100644 --- a/apps/desktop/src/features/sidebar/components/appearance-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/appearance-panel.tsx @@ -36,7 +36,7 @@ const THEME_OPTIONS: ThemeConfig[] = [ { value: 'forest', name: 'Forest', variant: 'dark', accentColor: '#34d399' }, { value: 'claude', name: 'Claude Light', variant: 'light', accentColor: '#d97706' }, { value: 'claude-dark', name: 'Claude Dark', variant: 'dark', accentColor: '#b45309' }, - { value: 'haptic', name: 'Haptic', variant: 'dark', accentColor: '#f5f5f5' } + { value: 'night', name: 'Night', variant: 'dark', accentColor: '#f5f5f5' } ] const FONT_OPTIONS: FontConfig[] = [ diff --git a/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx b/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx index 5391de57..0dd01848 100644 --- a/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx +++ b/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx @@ -9,7 +9,7 @@ import { ChangelogPanel } from './changelog-panel' import { ProjectInfoPanel } from './project-info-panel' import { SettingsPanel } from './settings-panel' -type Theme = 'dark' | 'light' | 'midnight' | 'forest' | 'claude' | 'claude-dark' | 'haptic' +type Theme = 'dark' | 'light' | 'midnight' | 'forest' | 'claude' | 'claude-dark' | 'night' type ToolbarAction = 'settings' | 'theme' | 'changelog' | 'project-info' diff --git a/apps/desktop/src/features/sidebar/components/nav-buttons.tsx b/apps/desktop/src/features/sidebar/components/nav-buttons.tsx deleted file mode 100644 index 95f41eec..00000000 --- a/apps/desktop/src/features/sidebar/components/nav-buttons.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Terminal, Database } from 'lucide-react' -import { cn } from '@/shared/utils/cn' - -type NavItem = { - id: string - label: string - icon: React.ComponentType<{ className?: string }> -} - -const NAV_ITEMS: NavItem[] = [ - { id: 'sql-console', label: 'Console', icon: Terminal }, - { id: 'database-studio', label: 'Data Viewer', icon: Database } -] - -type Props = { - activeId?: string - onSelect?: (id: string) => void -} - -export function NavButtons({ activeId = 'database-studio', onSelect }: Props) { - return ( - - ) -} diff --git a/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx b/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx index 6720d253..a5352563 100644 --- a/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx @@ -38,7 +38,7 @@ function getColumnTooltip(type: string, isPk?: boolean, isFk?: boolean): string export function SidebarBottomPanel({ table }: Props) { return ( -
+
STRUCTURE diff --git a/apps/desktop/src/features/sidebar/components/spotlight-trigger.tsx b/apps/desktop/src/features/sidebar/components/spotlight-trigger.tsx deleted file mode 100644 index f3701b15..00000000 --- a/apps/desktop/src/features/sidebar/components/spotlight-trigger.tsx +++ /dev/null @@ -1,17 +0,0 @@ -type Props = { - onSpotlightOpen?: () => void -} - -export function SpotlightTrigger({ onSpotlightOpen }: Props) { - return ( - - ) -} diff --git a/apps/desktop/src/features/sidebar/components/theme-panel.tsx b/apps/desktop/src/features/sidebar/components/theme-panel.tsx deleted file mode 100644 index 006b3884..00000000 --- a/apps/desktop/src/features/sidebar/components/theme-panel.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { SidebarPanel } from './sidebar-panel' -import { ThemePreviewCard } from './theme-preview-card' - -type Theme = 'dark' | 'light' | 'midnight' | 'forest' | 'claude' | 'claude-dark' | 'haptic' - -type ThemeConfig = { - value: Theme - name: string - variant: 'dark' | 'light' - accentColor: string -} - -const THEME_OPTIONS: ThemeConfig[] = [ - { value: 'dark', name: 'Classic Dark', variant: 'dark', accentColor: '#e5e5e5' }, - { value: 'light', name: 'Light', variant: 'light', accentColor: '#171717' }, - { value: 'midnight', name: 'Midnight', variant: 'dark', accentColor: '#818cf8' }, - { value: 'forest', name: 'Forest', variant: 'dark', accentColor: '#34d399' }, - { value: 'claude', name: 'Claude Light', variant: 'light', accentColor: '#d97706' }, - { value: 'claude-dark', name: 'Claude Dark', variant: 'dark', accentColor: '#b45309' }, - { value: 'haptic', name: 'Haptic', variant: 'dark', accentColor: '#f5f5f5' } -] - -type Props = { - theme: Theme - onThemeChange: (theme: Theme) => void -} - -export function ThemePanel({ theme, onThemeChange }: Props) { - return ( - -
-
-

- Choose Your Theme -

-

- Pick a style that suits your workflow -

-
- -
- {THEME_OPTIONS.map((option) => ( - onThemeChange(option.value)} - variant={option.variant} - accentColor={option.accentColor} - /> - ))} -
-
-
- ) -} diff --git a/apps/desktop/src/features/sidebar/database-sidebar.tsx b/apps/desktop/src/features/sidebar/database-sidebar.tsx index 5e74713c..e389741f 100644 --- a/apps/desktop/src/features/sidebar/database-sidebar.tsx +++ b/apps/desktop/src/features/sidebar/database-sidebar.tsx @@ -1,5 +1,5 @@ -import { Plus, Database as DatabaseIcon } from 'lucide-react' -import { useState, useMemo, useEffect } from 'react' +import { Plus, Database as DatabaseIcon, GripHorizontal } from 'lucide-react' +import { useState, useMemo, useEffect, useRef, useCallback } from 'react' import { SidebarTableSkeleton } from '@/components/ui/skeleton' import { useToast } from '@/components/ui/use-toast' import { useAdapter } from '@/core/data-provider' @@ -22,6 +22,7 @@ import { TableList } from './components/table-list' import type { TableRightClickAction } from './components/table-list' import { TableSearch, FilterState } from './components/table-search' import { Schema, TableItem } from './types' +import { cn } from '@/shared/utils/cn' const DEFAULT_FILTERS: FilterState = { showTables: true, @@ -55,9 +56,9 @@ export function DatabaseSidebar({ onAutoSelectComplete, connections = [], activeConnectionId, - onConnectionSelect = function () {}, - onAddConnection = function () {}, - onManageConnections = function () {}, + onConnectionSelect = function () { }, + onAddConnection = function () { }, + onManageConnections = function () { }, onViewConnection, onEditConnection, onDeleteConnection @@ -451,7 +452,7 @@ export function DatabaseSidebar({ } } - function handleToolbarAction(action: ToolbarAction) {} + function handleToolbarAction(action: ToolbarAction) { } async function handleExportTableSchema(tableName: string) { if (!activeConnectionId) return @@ -539,6 +540,41 @@ export function DatabaseSidebar({ } } + const [topPanelRatio, setTopPanelRatio] = useState(0.7) + const [isResizing, setIsResizing] = useState(false) + const sidebarRef = useRef(null) + + useEffect(() => { + if (!isResizing) return + + const handleMouseMove = (e: MouseEvent) => { + if (sidebarRef.current) { + const rect = sidebarRef.current.getBoundingClientRect() + const relativeY = e.clientY - rect.top + // Calculate used space by header (approx 40px + padding) + // We want ratio of the REMAINING space or total space? + // Total space is simpler. + const newRatio = Math.max(0.2, Math.min(0.85, relativeY / rect.height)) + setTopPanelRatio(newRatio) + } + } + + const handleMouseUp = () => { + setIsResizing(false) + document.body.style.cursor = '' + } + + document.body.style.cursor = 'row-resize' + document.addEventListener('mousemove', handleMouseMove) + document.addEventListener('mouseup', handleMouseUp) + + return () => { + document.body.style.cursor = '' + document.removeEventListener('mousemove', handleMouseMove) + document.removeEventListener('mouseup', handleMouseUp) + } + }, [isResizing]) + const availableSchemas = schema?.schemas.map(function (s) { return { @@ -549,8 +585,11 @@ export function DatabaseSidebar({ }) || [] return ( -
-
+
+
{schema && ( -
+
{availableSchemas.length > 1 && ( )} - + {isLoadingSchema ? ( ) : schemaError ? ( @@ -634,9 +676,22 @@ export function DatabaseSidebar({ {activeTable && ( -
- -
+ <> + {/* Resizer Handle */} +
{ + e.preventDefault() + setIsResizing(true) + }} + > +
+
+ +
+ +
+ )} {isMultiSelectMode && selectedTableIds.length > 0 && ( diff --git a/apps/desktop/src/features/sql-console/components/query-sidebar.tsx b/apps/desktop/src/features/sql-console/components/query-sidebar.tsx deleted file mode 100644 index 32e9a638..00000000 --- a/apps/desktop/src/features/sql-console/components/query-sidebar.tsx +++ /dev/null @@ -1,289 +0,0 @@ -import { - Search, - FolderPlus, - FilePlus, - ChevronLeft, - FileCode, - ChevronRight, - Folder, - Edit2, - Trash2, - MoreVertical -} from 'lucide-react' -import { useState, useMemo, useRef, useEffect } from 'react' -import { Button } from '@/shared/ui/button' -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuTrigger, - ContextMenuSeparator -} from '@/shared/ui/context-menu' -import { Input } from '@/shared/ui/input' -import { ScrollArea } from '@/shared/ui/scroll-area' -import { cn } from '@/shared/utils/cn' -import { SqlSnippet } from '../types' - -type Props = { - snippets: SqlSnippet[] - activeSnippetId: string | null - onSnippetSelect: (id: string) => void - onNewSnippet: (parentId?: string | null) => void - onNewFolder: (parentId?: string | null) => void - onRenameSnippet: (id: string, newName: string) => void - onDeleteSnippet: (id: string) => void - onBackToTables?: () => void -} - -export function QuerySidebar({ - snippets, - activeSnippetId, - onSnippetSelect, - onNewSnippet, - onNewFolder, - onRenameSnippet, - onDeleteSnippet, - onBackToTables -}: Props) { - const [searchValue, setSearchValue] = useState('') - const [expandedFolders, setExpandedFolders] = useState>({}) - const [editingId, setEditingId] = useState(null) - const [editValue, setEditValue] = useState('') - const [originalEditValue, setOriginalEditValue] = useState('') - const editInputRef = useRef(null) - - function toggleFolder(id: string, e: React.MouseEvent) { - e.stopPropagation() - setExpandedFolders((prev) => ({ ...prev, [id]: !prev[id] })) - } - - function startRenaming(node: SqlSnippet) { - setEditingId(node.id) - setEditValue(node.name) - setOriginalEditValue(node.name) - } - - function handleRenameSubmit() { - if (editingId && editValue.trim()) { - onRenameSnippet(editingId, editValue.trim()) - } - setEditingId(null) - } - - function handleRenameUndo() { - setEditValue(originalEditValue) - } - - useEffect(() => { - if (editingId && editInputRef.current) { - editInputRef.current.focus() - editInputRef.current.select() - } - }, [editingId]) - - const tree = useMemo(() => { - function buildTree(parentId: string | null = null): SqlSnippet[] { - return snippets - .filter((s) => s.parentId === parentId) - .sort((a, b) => { - if (a.isFolder && !b.isFolder) return -1 - if (!a.isFolder && b.isFolder) return 1 - return a.name.localeCompare(b.name) - }) - } - return buildTree(null) - }, [snippets]) - - function renderNode(node: SqlSnippet, depth: number = 0) { - const isExpanded = expandedFolders[node.id] - const children = snippets.filter((s) => s.parentId === node.id) - const isActive = activeSnippetId === node.id - const isEditing = editingId === node.id - - const NodeContent = ( -
- {node.isFolder ? ( - <> - {isExpanded ? ( - - ) : ( - - )} - - - ) : ( - - )} - - {isEditing ? ( - setEditValue(e.target.value)} - onBlur={handleRenameSubmit} - onKeyDown={(e) => { - if (e.key === 'Enter') handleRenameSubmit() - if (e.key === 'Escape') setEditingId(null) - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'z') { - e.preventDefault() - handleRenameUndo() - } - }} - className='h-6 py-0 px-1 text-xs bg-sidebar-accent border-primary' - onClick={(e) => e.stopPropagation()} - /> - ) : ( - {node.name} - )} -
- ) - - return ( - - -
- - - - )} -
- )} - - {node.isFolder && - isExpanded && - children.map((child) => renderNode(child, depth + 1))} -
- - - startRenaming(node)}> - - Rename - - {node.isFolder && ( - <> - onNewSnippet(node.id)}> - - New Snippet - - onNewFolder(node.id)}> - - New Folder - - - )} - - onDeleteSnippet(node.id)} - > - - Delete - - - - ) - } - - return ( -
- {/* Header */} - {onBackToTables && ( - - )} - - {/* Actions */} -
- - Snippets - -
- - -
-
- - {/* Search */} -
-
- - setSearchValue(e.target.value)} - /> -
-
- - {/* Snippets list */} - -
{tree.map((node) => renderNode(node))}
-
-
- ) -} diff --git a/apps/desktop/src/features/sql-console/components/schema-browser.tsx b/apps/desktop/src/features/sql-console/components/schema-browser.tsx deleted file mode 100644 index 301ac37b..00000000 --- a/apps/desktop/src/features/sql-console/components/schema-browser.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import { - Search, - Table2, - Eye, - ChevronRight, - ChevronDown, - Key, - Hash, - Type, - Calendar, - ToggleLeft, - Copy, - Database, - FileText -} from 'lucide-react' -import { useState, useCallback } from 'react' -import { - ContextMenu, - ContextMenuContent, - ContextMenuItem, - ContextMenuTrigger, - ContextMenuSeparator -} from '@/shared/ui/context-menu' -import { Input } from '@/shared/ui/input' -import { ScrollArea } from '@/shared/ui/scroll-area' -import { cn } from '@/shared/utils/cn' -import { TableInfo } from '../types' - -type Props = { - tables: TableInfo[] - onTableSelect?: (tableName: string) => void - onInsertQuery?: (query: string) => void -} - -function formatRowCount(count: number): string { - if (count >= 1000) { - return `${(count / 1000).toFixed(count >= 10000 ? 1 : 2).replace(/\.?0+$/, '')}K` - } - return count.toString() -} - -function ColumnIcon({ type, isPrimaryKey }: { type: string; isPrimaryKey?: boolean }) { - if (isPrimaryKey) return - if (type.includes('int') || type.includes('serial') || type.includes('decimal')) - return - if (type.includes('char') || type.includes('text')) - return - if (type.includes('date') || type.includes('time')) - return - if (type.includes('bool')) return - return
-} - -export function SchemaBrowser({ tables, onTableSelect, onInsertQuery }: Props) { - const [searchValue, setSearchValue] = useState('') - const [expandedTables, setExpandedTables] = useState>(new Set()) - - function toggleTable(tableName: string) { - const next = new Set(expandedTables) - if (next.has(tableName)) { - next.delete(tableName) - } else { - next.add(tableName) - } - setExpandedTables(next) - } - - const copyToClipboard = useCallback((text: string) => { - navigator.clipboard.writeText(text) - }, []) - - const filteredTables = tables.filter((t) => - t.name.toLowerCase().includes(searchValue.toLowerCase()) - ) - - return ( -
- {/* Search */} -
-
- - setSearchValue(e.target.value)} - className='h-7 bg-transparent border-sidebar-border/60 text-xs pl-7' - /> -
-
- - {/* Tables list */} - -
- {filteredTables.map((table) => { - const isExpanded = expandedTables.has(table.name) - const hasColumns = table.columns && table.columns.length > 0 - - return ( -
- - -
- - - - - - {formatRowCount(table.rowCount)} - -
-
- - - onInsertQuery?.( - `SELECT * FROM ${table.name} LIMIT 100;` - ) - } - > - - SELECT * FROM {table.name} - - - onInsertQuery?.( - `SELECT COUNT(*) FROM ${table.name};` - ) - } - > - - COUNT rows - - - copyToClipboard(table.name)} - > - - Copy table name - - -
- - {/* Columns list */} - {isExpanded && hasColumns && ( -
- {/* Guide line */} -
- - {table.columns?.map((col) => ( - - -
- - - {col.name} - - - {col.type} - -
-
- - onInsertQuery?.(col.name)} - > - - Insert column name - - - onInsertQuery?.( - `SELECT ${col.name} FROM ${table.name} LIMIT 100;` - ) - } - > - - SELECT {col.name} - - - copyToClipboard(col.name)} - > - - Copy column name - - copyToClipboard(col.type)} - > - - Copy type: {col.type} - - -
- ))} -
- )} -
- ) - })} -
- -
- ) -} diff --git a/apps/desktop/src/hooks/use-github-release.ts b/apps/desktop/src/hooks/use-github-release.ts deleted file mode 100644 index 5d673255..00000000 --- a/apps/desktop/src/hooks/use-github-release.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { useQuery } from '@tanstack/react-query' -import { siteConfig } from '@/config/site' - -type ReleaseAsset = { - name: string - browser_download_url: string - size: number - download_count: number - content_type: string -} - -type GitHubRelease = { - tag_name: string - name: string - published_at: string - html_url: string - body: string - assets: ReleaseAsset[] -} - -type ParsedRelease = { - version: string - name: string - publishedAt: Date - releaseUrl: string - releaseNotes: string - downloads: { - mac?: string - linux?: string - linuxDeb?: string - linuxRpm?: string - windows?: string - } - totalDownloads: number -} - -function parseRelease(release: GitHubRelease): ParsedRelease { - const downloads: ParsedRelease['downloads'] = {} - let totalDownloads = 0 - - for (const asset of release.assets) { - totalDownloads += asset.download_count - const name = asset.name.toLowerCase() - - if (name.endsWith('.dmg') || name.endsWith('.app.zip')) { - downloads.mac = asset.browser_download_url - } else if (name.endsWith('.appimage')) { - downloads.linux = asset.browser_download_url - } else if (name.endsWith('.deb')) { - downloads.linuxDeb = asset.browser_download_url - } else if (name.endsWith('.rpm')) { - downloads.linuxRpm = asset.browser_download_url - } else if (name.endsWith('.exe') || name.endsWith('.msi')) { - downloads.windows = asset.browser_download_url - } - } - - return { - version: release.tag_name.replace(/^v/, ''), - name: release.name, - publishedAt: new Date(release.published_at), - releaseUrl: release.html_url, - releaseNotes: release.body, - downloads, - totalDownloads - } -} - -async function fetchLatestRelease(): Promise { - const response = await fetch(siteConfig.github.apiUrl, { - headers: { - Accept: 'application/vnd.github.v3+json' - } - }) - - if (!response.ok) { - if (response.status === 404) { - return null - } - throw new Error(`Failed to fetch release: ${response.statusText}`) - } - - const release: GitHubRelease = await response.json() - return parseRelease(release) -} - -export function useGitHubRelease() { - return useQuery({ - queryKey: ['github-release', siteConfig.github.owner, siteConfig.github.repo], - queryFn: fetchLatestRelease, - staleTime: 1000 * 60 * 30, - gcTime: 1000 * 60 * 60, - retry: 1, - refetchOnWindowFocus: false - }) -} - -export type { ParsedRelease, ReleaseAsset } diff --git a/apps/desktop/src/hooks/use-horizontal-scroll.ts b/apps/desktop/src/hooks/use-horizontal-scroll.ts deleted file mode 100644 index 764febd6..00000000 --- a/apps/desktop/src/hooks/use-horizontal-scroll.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { useEffect, useRef, useCallback } from 'react' - -type HorizontalScrollConfig = { - sensitivity?: number - invertDirection?: boolean - cursorStyle?: string -} - -type HorizontalScrollState = { - isActive: boolean - startX: number - scrollableContainer: HTMLElement | null -} - -const DEFAULT_CONFIG: Required = { - sensitivity: 1, - invertDirection: false, - cursorStyle: 'ew-resize' -} - -function findScrollableParent(element: HTMLElement | null): HTMLElement | null { - if (!element) return null - - let current: HTMLElement | null = element - - while (current) { - const { overflowX } = window.getComputedStyle(current) - const isScrollable = current.scrollWidth > current.clientWidth - const hasOverflow = overflowX === 'auto' || overflowX === 'scroll' - - if (isScrollable && hasOverflow) { - return current - } - - current = current.parentElement - } - - return null -} - -export function useHorizontalScroll(config: HorizontalScrollConfig = {}) { - const mergedConfig = { ...DEFAULT_CONFIG, ...config } - const stateRef = useRef({ - isActive: false, - startX: 0, - scrollableContainer: null - }) - const originalCursorRef = useRef('') - - const handleMouseDown = useCallback( - function onMouseDown(event: MouseEvent) { - if (event.button !== 1) return - - event.preventDefault() - event.stopPropagation() - - const target = event.target as HTMLElement - const scrollableContainer = findScrollableParent(target) - - if (!scrollableContainer) return - - stateRef.current = { - isActive: true, - startX: event.clientX, - scrollableContainer - } - - originalCursorRef.current = document.body.style.cursor - document.body.style.cursor = mergedConfig.cursorStyle - }, - [mergedConfig.cursorStyle] - ) - - const handleMouseMove = useCallback( - function onMouseMove(event: MouseEvent) { - const state = stateRef.current - - if (!state.isActive || !state.scrollableContainer) return - - event.preventDefault() - - const deltaX = event.clientX - state.startX - const scrollAmount = mergedConfig.invertDirection ? deltaX : -deltaX - - state.scrollableContainer.scrollBy({ - left: scrollAmount * mergedConfig.sensitivity, - behavior: 'auto' - }) - - stateRef.current.startX = event.clientX - }, - [mergedConfig.sensitivity, mergedConfig.invertDirection] - ) - - const handleMouseUp = useCallback(function onMouseUp(event: MouseEvent) { - if (event.button !== 1) return - - stateRef.current = { - isActive: false, - startX: 0, - scrollableContainer: null - } - - document.body.style.cursor = originalCursorRef.current - }, []) - - const handleContextMenu = useCallback(function onContextMenu(event: MouseEvent) { - if (stateRef.current.isActive) { - event.preventDefault() - } - }, []) - - const handleAuxClick = useCallback(function onAuxClick(event: MouseEvent) { - if (event.button === 1) { - event.preventDefault() - } - }, []) - - useEffect( - function setupEventListeners() { - document.addEventListener('mousedown', handleMouseDown) - document.addEventListener('mousemove', handleMouseMove) - document.addEventListener('mouseup', handleMouseUp) - document.addEventListener('contextmenu', handleContextMenu) - document.addEventListener('auxclick', handleAuxClick) - - return function cleanup() { - document.removeEventListener('mousedown', handleMouseDown) - document.removeEventListener('mousemove', handleMouseMove) - document.removeEventListener('mouseup', handleMouseUp) - document.removeEventListener('contextmenu', handleContextMenu) - document.removeEventListener('auxclick', handleAuxClick) - - document.body.style.cursor = originalCursorRef.current - } - }, - [handleMouseDown, handleMouseMove, handleMouseUp, handleContextMenu, handleAuxClick] - ) -} - -export type { HorizontalScrollConfig } diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index e033d2e9..30d24414 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -399,7 +399,7 @@ --table-border: calc(32 + var(--hue-shift)) 15% 85%; } - .haptic { + .night { --background: 0 0% 0%; /* #000000 */ --foreground: 0 0% 93%; diff --git a/apps/desktop/src/main.tsx b/apps/desktop/src/main.tsx index e0584943..785d20df 100644 --- a/apps/desktop/src/main.tsx +++ b/apps/desktop/src/main.tsx @@ -3,6 +3,7 @@ import { getAppearanceSettings, applyAppearanceToDOM } from '@/shared/lib/appear import { loadFontPair } from '@/shared/lib/font-loader' import App from './App.tsx' import './index.css' +import './monaco-workers' // Initialize appearance before rendering to prevent theme flash const settings = getAppearanceSettings() diff --git a/apps/desktop/src/monaco-workers.ts b/apps/desktop/src/monaco-workers.ts new file mode 100644 index 00000000..c3beb865 --- /dev/null +++ b/apps/desktop/src/monaco-workers.ts @@ -0,0 +1,26 @@ +import * as monaco from 'monaco-editor' +import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker' +import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker' +import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker' +import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker' +import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker' + +self.MonacoEnvironment = { + getWorker(_: any, label: string) { + if (label === 'json') { + return new jsonWorker() + } + if (label === 'css' || label === 'scss' || label === 'less') { + return new cssWorker() + } + if (label === 'html' || label === 'handlebars' || label === 'razor') { + return new htmlWorker() + } + if (label === 'typescript' || label === 'javascript') { + return new tsWorker() + } + return new editorWorker() + } +} + +monaco.languages.typescript.typescriptDefaults.setEagerModelSync(true) diff --git a/apps/desktop/src/shared/lib/appearance-store.ts b/apps/desktop/src/shared/lib/appearance-store.ts index 94597411..1a34915d 100644 --- a/apps/desktop/src/shared/lib/appearance-store.ts +++ b/apps/desktop/src/shared/lib/appearance-store.ts @@ -3,7 +3,7 @@ * Manages theme, font pair, and UI density preferences with localStorage persistence. */ -export type Theme = 'dark' | 'light' | 'midnight' | 'forest' | 'claude' | 'claude-dark' | 'haptic' +export type Theme = 'dark' | 'light' | 'midnight' | 'forest' | 'claude' | 'claude-dark' | 'night' export type FontPair = 'system' | 'serif' | 'compact' | 'playful' | 'technical' | 'vintage' export type AppearanceSettings = { @@ -52,7 +52,7 @@ export function applyAppearanceToDOM(settings: AppearanceSettings): void { const root = document.documentElement // Theme - root.classList.remove('light', 'dark', 'midnight', 'forest', 'claude', 'claude-dark', 'haptic') + root.classList.remove('light', 'dark', 'midnight', 'forest', 'claude', 'claude-dark', 'night') root.classList.add(settings.theme) // Font Pair diff --git a/apps/desktop/stats.html b/apps/desktop/stats.html new file mode 100644 index 00000000..dda3130d --- /dev/null +++ b/apps/desktop/stats.html @@ -0,0 +1,4949 @@ + + + + + + + + Rollup Visualizer + + + +
+ + + + + diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 65a7b5c8..cfd958ce 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -1,9 +1,10 @@ import react from '@vitejs/plugin-react-swc' import path from 'path' import { defineConfig } from 'vite' +import { visualizer } from 'rollup-plugin-visualizer' export default defineConfig({ - plugins: [react()], + plugins: [react(), visualizer({ open: false, filename: 'stats.html' })], clearScreen: false, server: { port: 1420, @@ -18,7 +19,27 @@ export default defineConfig({ emptyOutDir: true, target: ['es2021', 'chrome105', 'safari13'], minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, - sourcemap: !!process.env.TAURI_DEBUG + sourcemap: !!process.env.TAURI_DEBUG, + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('node_modules')) { + if (id.includes('react') || id.includes('react-dom') || id.includes('react-router-dom')) { + return 'vendor-react'; + } + if (id.includes('@radix-ui') || id.includes('lucide-react') || id.includes('clsx') || id.includes('tailwind-merge') || id.includes('class-variance-authority') || id.includes('framer-motion')) { + return 'vendor-ui'; + } + if (id.includes('@monaco-editor') || id.includes('monaco-vim')) { + return 'vendor-editor'; + } + if (id.includes('date-fns') || id.includes('zustand') || id.includes('@tanstack/react-query')) { + return 'vendor-utils'; + } + } + } + } + } }, resolve: { alias: { diff --git a/bun.lock b/bun.lock index 46b208b1..c3c2e841 100644 --- a/bun.lock +++ b/bun.lock @@ -7,42 +7,39 @@ "dependencies": { "@google/generative-ai": "^0.24.1", "@tauri-apps/plugin-dialog": "^2.6.0", + "glob": "^13.0.0", + "monaco-editor": "^0.55.1", "sqlite3": "^5.1.7", "zustand": "^5.0.10", }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@vitejs/plugin-react-swc": "^4.2.2", "@vitest/coverage-v8": "^4.0.17", "@vitest/ui": "^4.0.17", "happy-dom": "^20.3.1", + "react": "18.3.1", + "react-dom": "18.3.1", "turbo": "^2.3.0", "vitest": "^4.0.17", }, }, "apps/desktop": { "name": "@dora/desktop", - "version": "0.0.92", + "version": "0.0.925", "dependencies": { "@faker-js/faker": "^10.2.0", "@hookform/resolvers": "^3.10.0", "@monaco-editor/react": "^4.6.0", - "@radix-ui/react-accordion": "^1.2.11", "@radix-ui/react-alert-dialog": "^1.1.14", - "@radix-ui/react-aspect-ratio": "^1.1.7", - "@radix-ui/react-avatar": "^1.1.10", "@radix-ui/react-checkbox": "^1.3.2", "@radix-ui/react-collapsible": "^1.1.12", "@radix-ui/react-context-menu": "^2.2.16", "@radix-ui/react-dialog": "^1.1.14", "@radix-ui/react-dropdown-menu": "^2.1.15", - "@radix-ui/react-hover-card": "^1.1.14", "@radix-ui/react-label": "^2.1.7", - "@radix-ui/react-menubar": "^1.1.15", - "@radix-ui/react-navigation-menu": "^1.2.13", "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-progress": "^1.1.7", - "@radix-ui/react-radio-group": "^1.3.7", "@radix-ui/react-scroll-area": "^1.2.9", "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-separator": "^1.1.7", @@ -51,8 +48,6 @@ "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tabs": "^1.1.12", "@radix-ui/react-toast": "^1.2.14", - "@radix-ui/react-toggle": "^1.1.9", - "@radix-ui/react-toggle-group": "^1.1.10", "@radix-ui/react-tooltip": "^1.2.7", "@tanstack/react-query": "^5.83.0", "@tauri-apps/api": "^2.9.1", @@ -61,26 +56,18 @@ "@tauri-apps/plugin-shell": "^2.3.4", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "cmdk": "^1.1.1", "date-fns": "^3.6.0", - "embla-carousel-react": "^8.6.0", "framer-motion": "^12.29.0", - "input-otp": "^1.4.2", "lucide-react": "^0.462.0", "monaco-vim": "^0.4.4", "next-themes": "^0.3.0", "react": "^18.3.1", - "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", - "react-hook-form": "^7.61.1", "react-resizable-panels": "^2.1.9", "react-router-dom": "^6.30.1", - "recharts": "^2.15.4", "sonner": "^1.7.4", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "vaul": "^0.9.9", - "zod": "^3.25.76", "zustand": "^5.0.10", }, "devDependencies": { @@ -97,6 +84,7 @@ "happy-dom": "^20.3.7", "postcss": "^8.5.6", "rolldown": "^1.0.0-beta.57", + "rollup-plugin-visualizer": "^6.0.5", "tailwindcss": "^4.1.18", "typescript": "^5.8.3", "vite": "npm:rolldown-vite@latest", @@ -277,16 +265,10 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="], - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collapsible": "1.1.12", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA=="], - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw=="], "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w=="], - - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.11", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q=="], - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.3", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw=="], "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA=="], @@ -311,18 +293,12 @@ "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg=="], - "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], "@radix-ui/react-label": ["@radix-ui/react-label@2.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A=="], "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg=="], - "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.16", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.16", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA=="], - - "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w=="], - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA=="], "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.8", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw=="], @@ -333,10 +309,6 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.8", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA=="], - - "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ=="], - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="], "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.10", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A=="], @@ -355,10 +327,6 @@ "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g=="], - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ=="], - - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-toggle": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q=="], - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], @@ -369,8 +337,6 @@ "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="], - "@radix-ui/react-use-is-hydrated": ["@radix-ui/react-use-is-hydrated@0.1.0", "", { "dependencies": { "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA=="], - "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="], "@radix-ui/react-use-previous": ["@radix-ui/react-use-previous@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ=="], @@ -579,24 +545,6 @@ "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], - "@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="], - - "@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="], - - "@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="], - - "@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="], - - "@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="], - - "@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="], - - "@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="], - - "@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="], - - "@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="], - "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -681,12 +629,16 @@ "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], - "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], - "cmdk": ["cmdk@1.1.1", "", { "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", "@radix-ui/react-dialog": "^1.1.6", "@radix-ui/react-id": "^1.1.0", "@radix-ui/react-primitive": "^2.0.2" }, "peerDependencies": { "react": "^18 || ^19 || ^19.0.0-rc", "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], @@ -699,38 +651,16 @@ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], - - "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], - - "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], - - "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], - - "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], - - "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], - - "d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], - - "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], - - "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], - - "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], - - "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], - "date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], - "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], - "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="], + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], @@ -741,16 +671,8 @@ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], - "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], - "dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], - "embla-carousel": ["embla-carousel@8.6.0", "", {}, "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA=="], - - "embla-carousel-react": ["embla-carousel-react@8.6.0", "", { "dependencies": { "embla-carousel": "8.6.0", "embla-carousel-reactive-utils": "8.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.1 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA=="], - - "embla-carousel-reactive-utils": ["embla-carousel-reactive-utils@8.6.0", "", { "peerDependencies": { "embla-carousel": "8.6.0" } }, "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], @@ -769,16 +691,14 @@ "esbuild": ["esbuild@0.27.2", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.2", "@esbuild/android-arm": "0.27.2", "@esbuild/android-arm64": "0.27.2", "@esbuild/android-x64": "0.27.2", "@esbuild/darwin-arm64": "0.27.2", "@esbuild/darwin-x64": "0.27.2", "@esbuild/freebsd-arm64": "0.27.2", "@esbuild/freebsd-x64": "0.27.2", "@esbuild/linux-arm": "0.27.2", "@esbuild/linux-arm64": "0.27.2", "@esbuild/linux-ia32": "0.27.2", "@esbuild/linux-loong64": "0.27.2", "@esbuild/linux-mips64el": "0.27.2", "@esbuild/linux-ppc64": "0.27.2", "@esbuild/linux-riscv64": "0.27.2", "@esbuild/linux-s390x": "0.27.2", "@esbuild/linux-x64": "0.27.2", "@esbuild/netbsd-arm64": "0.27.2", "@esbuild/netbsd-x64": "0.27.2", "@esbuild/openbsd-arm64": "0.27.2", "@esbuild/openbsd-x64": "0.27.2", "@esbuild/openharmony-arm64": "0.27.2", "@esbuild/sunos-x64": "0.27.2", "@esbuild/win32-arm64": "0.27.2", "@esbuild/win32-ia32": "0.27.2", "@esbuild/win32-x64": "0.27.2" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw=="], - "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], - "eventemitter3": ["eventemitter3@4.0.7", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="], + "estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], - "fast-equals": ["fast-equals@5.4.0", "", {}, "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw=="], - "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], @@ -799,13 +719,15 @@ "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="], "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], - "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], @@ -841,16 +763,16 @@ "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], - "input-otp": ["input-otp@1.4.2", "", { "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA=="], - - "internmap": ["internmap@2.0.3", "", {}, "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg=="], - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="], + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], @@ -887,11 +809,9 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], - "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="], "lucide-react": ["lucide-react@0.462.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" } }, "sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g=="], @@ -915,7 +835,7 @@ "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], - "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + "minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], @@ -963,12 +883,12 @@ "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="], + "oxfmt": ["oxfmt@0.26.0", "", { "dependencies": { "tinypool": "2.0.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.26.0", "@oxfmt/darwin-x64": "0.26.0", "@oxfmt/linux-arm64-gnu": "0.26.0", "@oxfmt/linux-arm64-musl": "0.26.0", "@oxfmt/linux-x64-gnu": "0.26.0", "@oxfmt/linux-x64-musl": "0.26.0", "@oxfmt/win32-arm64": "0.26.0", "@oxfmt/win32-x64": "0.26.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-UDD1wFNwfeorMm2ZY0xy1KRAAvJ5NjKBfbDmiMwGP7baEHTq65cYpC0aPP+BGHc8weXUbSZaK8MdGyvuRUvS4Q=="], "oxlint": ["oxlint@1.41.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.41.0", "@oxlint/darwin-x64": "1.41.0", "@oxlint/linux-arm64-gnu": "1.41.0", "@oxlint/linux-arm64-musl": "1.41.0", "@oxlint/linux-x64-gnu": "1.41.0", "@oxlint/linux-x64-musl": "1.41.0", "@oxlint/win32-arm64": "1.41.0", "@oxlint/win32-x64": "1.41.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.1" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-Dyaoup82uhgAgp5xLNt4dPdvl5eSJTIzqzL7DcKbkooUE4PDViWURIPlSUF8hu5a+sCnNIp/LlQMDsKoyaLTBA=="], @@ -979,6 +899,8 @@ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + "path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -997,21 +919,17 @@ "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], - "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "react-day-picker": ["react-day-picker@8.10.1", "", { "peerDependencies": { "date-fns": "^2.28.0 || ^3.0.0", "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA=="], - "react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], "react-hook-form": ["react-hook-form@7.71.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-9SUJKCGKo8HUSsCO+y0CtqkqI5nNuaDqTxyqPsZPqIwudpj4rCrAz/jZV+jn57bx5gtZKOh3neQu94DXMc+w5w=="], - "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="], + "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="], @@ -1023,20 +941,14 @@ "react-router-dom": ["react-router-dom@6.30.3", "", { "dependencies": { "@remix-run/router": "1.23.2", "react-router": "6.30.3" }, "peerDependencies": { "react": ">=16.8", "react-dom": ">=16.8" } }, "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag=="], - "react-smooth": ["react-smooth@4.0.4", "", { "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q=="], - "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], - "react-transition-group": ["react-transition-group@4.4.5", "", { "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", "loose-envify": "^1.4.0", "prop-types": "^15.6.2" }, "peerDependencies": { "react": ">=16.6.0", "react-dom": ">=16.6.0" } }, "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g=="], - "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], - "recharts": ["recharts@2.15.4", "", { "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", "lodash": "^4.17.21", "react-is": "^18.3.1", "react-smooth": "^4.0.4", "recharts-scale": "^0.4.4", "tiny-invariant": "^1.3.1", "victory-vendor": "^36.6.8" }, "peerDependencies": { "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw=="], - - "recharts-scale": ["recharts-scale@0.4.5", "", { "dependencies": { "decimal.js-light": "^2.4.1" } }, "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w=="], - "redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], @@ -1047,6 +959,8 @@ "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], + "rollup-plugin-visualizer": ["rollup-plugin-visualizer@6.0.5", "", { "dependencies": { "open": "^8.0.0", "picomatch": "^4.0.2", "source-map": "^0.7.4", "yargs": "^17.5.1" }, "peerDependencies": { "rolldown": "1.x || ^1.0.0-beta", "rollup": "2.x || 3.x || 4.x" }, "optionalPeers": ["rolldown", "rollup"], "bin": { "rollup-plugin-visualizer": "dist/bin/cli.js" } }, "sha512-9+HlNgKCVbJDs8tVtjQ43US12eqaiHyyiLMdBwQ7vSZPiHMysGNo2E88TAp1si5wx8NAoYriI2A5kuKfIakmJg=="], + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], @@ -1075,6 +989,8 @@ "sonner": ["sonner@1.7.4", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], @@ -1113,8 +1029,6 @@ "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], - "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], - "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], @@ -1165,10 +1079,6 @@ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], - "vaul": ["vaul@0.9.9", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0", "react-dom": "^16.8 || ^17.0 || ^18.0" } }, "sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ=="], - - "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], - "vite": ["rolldown-vite@7.3.1", "", { "dependencies": { "@oxc-project/runtime": "0.101.0", "fdir": "^6.5.0", "lightningcss": "^1.30.2", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rolldown": "1.0.0-beta.53", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "esbuild": "^0.27.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-LYzdNAjRHhF2yA4JUQm/QyARyi216N2rpJ0lJZb8E9FU2y5v6Vk+xq/U4XBOxMefpWixT5H3TslmAHm1rqIq2w=="], "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], @@ -1181,13 +1091,19 @@ "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], "ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], "zustand": ["zustand@5.0.10", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg=="], @@ -1197,12 +1113,6 @@ "@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-aspect-ratio/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - - "@radix-ui/react-avatar/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], - - "@radix-ui/react-avatar/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], @@ -1215,10 +1125,6 @@ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], - - "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], - "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], @@ -1245,18 +1151,20 @@ "@types/ws/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="], - "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "cacache/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "cacache/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], - "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "happy-dom/@types/node": ["@types/node@25.0.10", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg=="], "loose-envify/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + "make-fetch-happen/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], @@ -1271,28 +1179,38 @@ "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], - "pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], + "node-gyp/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], - "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], + "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.1", "", {}, "sha512-UTBjtTxVOhodhzFVp/ayITaTETRHPUPYZPXQe0WU0wOgxghMojXxYjOiPOauKIYNWJAWS2fd7gJgGQK8GU8vDA=="], "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + "tar/minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], "vite/rolldown": ["rolldown@1.0.0-beta.53", "", { "dependencies": { "@oxc-project/types": "=0.101.0", "@rolldown/pluginutils": "1.0.0-beta.53" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-arm64": "1.0.0-beta.53", "@rolldown/binding-darwin-x64": "1.0.0-beta.53", "@rolldown/binding-freebsd-x64": "1.0.0-beta.53", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.53", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.53", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.53", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.53", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.53", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.53", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.53" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-Qd9c2p0XKZdgT5AYd+KgAMggJ8ZmCs3JnS9PTMWkyUfteKlfmKtxJbWTHkVakxwXs1Ub7jrRYVeFeF7N0sQxyw=="], "vitest/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="], + "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "@dora/desktop/@vitejs/plugin-react-swc/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="], "@repo/style/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "@types/ws/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "cacache/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "happy-dom/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "node-gyp/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "rimraf/glob/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + "vite/rolldown/@oxc-project/types": ["@oxc-project/types@0.101.0", "", {}, "sha512-nuFhqlUzJX+gVIPPfuE6xurd4lST3mdcWOhyK/rZO0B9XWMKm79SuszIQEnSMmmDhq1DC8WWVYGVd+6F93o1gQ=="], "vite/rolldown/@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.53", "", { "os": "android", "cpu": "arm64" }, "sha512-Ok9V8o7o6YfSdTTYA/uHH30r3YtOxLD6G3wih/U9DO0ucBBFq8WPt/DslU53OgfteLRHITZny9N/qCUxMf9kjQ=="], diff --git a/package.json b/package.json index fe39219e..923530bc 100644 --- a/package.json +++ b/package.json @@ -31,15 +31,20 @@ "dependencies": { "@google/generative-ai": "^0.24.1", "@tauri-apps/plugin-dialog": "^2.6.0", + "glob": "^13.0.0", + "monaco-editor": "^0.55.1", "sqlite3": "^5.1.7", "zustand": "^5.0.10" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", "@testing-library/react": "^16.3.1", "@vitejs/plugin-react-swc": "^4.2.2", "@vitest/coverage-v8": "^4.0.17", "@vitest/ui": "^4.0.17", "happy-dom": "^20.3.1", + "react": "18.3.1", + "react-dom": "18.3.1", "turbo": "^2.3.0", "vitest": "^4.0.17" }, diff --git a/recap.md b/recap.md new file mode 100644 index 00000000..c9e3d9cb --- /dev/null +++ b/recap.md @@ -0,0 +1,41 @@ +# Debugging Recap + +## What I Have Done + +1. **Identified Failure**: The CI failed on `TypeScript Tests`, specifically in `docker-client.test.ts`. +2. **Reproduced Locally**: Confirmed the same 7 test failures locally using `bun run test:desktop`. +3. **Diagnosed Cause**: The error `Cannot read properties of undefined (reading 'stdout')` happens because the mocked `Command.execute()` returns `undefined` instead of the expected object `{ stdout: '', ... }`. +4. **Attempted Fixes**: + - Tried adding debug logs to `docker-client.ts` (confirmed `output` is undefined). + - Tried various `vi.mock` strategies in `docker-client.test.ts` (hoisted variables, factory functions). + - Tried creating a global `__mocks__` folder for `@tauri-apps/plugin-shell` (encountered import resolution errors). + - Tried hardcoding the mock return value (still failed, suggesting the mock isn't being applied correctly). + +## What Went Wrong + +- The mocking of `@tauri-apps/plugin-shell` is tricky because of how it exports `Command`. +- The test file structure was modified multiple times, potentially leaving it in an inconsistent state. +- Import resolution for manual mocks in `__mocks__` failed due to path/config issues. + +## Next Steps + +1. **Cleanup**: Remove the temporary `__mocks__` directory and revert `docker-client.ts` to its original state (remove debug logs). +2. **Reset Test File**: Restore `docker-client.test.ts` to a clean state but with a valid mock strategy. +3. **Implement Fix**: Use `vi.mock` with a factory that explicitly returns a `default` export or named export matching the library's structure. + - `@tauri-apps/plugin-shell` exports `Command` class. + - I will mock it as: + + ```typescript + vi.mock('@tauri-apps/plugin-shell', () => { + return { + Command: { + create: vi.fn(() => ({ + execute: vi.fn().mockResolvedValue({ stdout: '', stderr: '', code: 0 }), + // ... other methods + })) + } + } + }) + ``` + +4. **Verify**: Run the tests again to confirm they pass. diff --git a/tools/audit-cleanup.sh b/tools/audit-cleanup.sh new file mode 100755 index 00000000..75caeefd --- /dev/null +++ b/tools/audit-cleanup.sh @@ -0,0 +1,123 @@ +#!/bin/bash +set -e + +# ========================================== +# CONFIGURATION +# ========================================== +BACKUP_ROOT="$HOME/.dora/backups/$(date +%Y%m%d_%H%M%S)" +PROJECT_ROOT=$(pwd) +DRY_RUN=true + +# ========================================== +# UNUSED FILES DEFINITION +# ========================================== +# Define path relative to project root +FILES_TO_REMOVE=( + # Feature: Database Studio + "apps/desktop/src/features/database-studio/utils/get-column-icon.tsx" + + # Feature: Docker Manager + "apps/desktop/src/features/docker-manager/hooks/use-container-history.ts" + "apps/desktop/src/features/docker-manager/hooks/use-container-sort-filter.ts" + + # Feature: SQL Console + "apps/desktop/src/features/sql-console/components/query-sidebar.tsx" + "apps/desktop/src/features/sql-console/components/schema-browser.tsx" + + # Feature: Sidebar / General + "apps/desktop/src/features/sidebar/components/theme-panel.tsx" + "apps/desktop/src/features/sidebar/components/spotlight-trigger.tsx" + "apps/desktop/src/features/sidebar/components/nav-buttons.tsx" + + # Round 2: Comprehensive Scan Findings + "apps/desktop/src/hooks/use-github-release.ts" + "apps/desktop/src/hooks/use-horizontal-scroll.ts" + "apps/desktop/src/features/docker-manager/api/queries/use-container-sizes.ts" +) + +# ========================================== +# ARGUMENT PARSING +# ========================================== +for arg in "$@"; do + case $arg in + --execute) + DRY_RUN=false + shift + ;; + --force) + FORCE=true + shift + ;; + --help) + echo "Usage: ./tools/audit-cleanup.sh [--execute] [--force]" + echo " --execute Perform actual backup and deletion (default is DRY RUN)" + echo " --force Bypass Pre-flight Git checks" + exit 0 + ;; + esac +done + +# ========================================== +# PRE-FLIGHT CHECKS +# ========================================== +echo "๐Ÿ” Starting Project Audit Cleanup..." + +if [ "$DRY_RUN" = false ]; then + echo "โš ๏ธ EXECUTION MODE ENABLED" + + # Git Safety Check + if [ "$FORCE" = true ]; then + echo "โš ๏ธ Skipping Git Safety Check (--force used)" + elif ! git diff-index --quiet HEAD --; then + echo "โŒ Error: You have uncommitted changes." + echo " Please commit your changes before running cleanup." + exit 1 + fi + echo "โœ… Git is clean." + + # Reminder to push (optional, hard to enforce strictly if no upstream, but good practice) + echo "โ„น๏ธ Reminder: Ensure you have pushed your latest changes to origin." + echo " Press ENTER to continue or Ctrl+C to abort." + read -r +else + echo "โ„น๏ธ DRY RUN MODE (No changes will be made)" + echo " Use --execute to perform deletion." +fi + +# ========================================== +# EXECUTION LOOP +# ========================================== +echo "๐Ÿ“‹ Processing ${#FILES_TO_REMOVE[@]} files..." + +for FILE_PATH in "${FILES_TO_REMOVE[@]}"; do + FULL_PATH="$PROJECT_ROOT/$FILE_PATH" + + if [ ! -f "$FULL_PATH" ]; then + echo "โš ๏ธ File not found (already deleted?): $FILE_PATH" + continue + fi + + if [ "$DRY_RUN" = true ]; then + echo " [DRY-RUN] Would delete: $FILE_PATH" + else + # Backup Logic + BACKUP_DEST="$BACKUP_ROOT/$FILE_PATH" + mkdir -p "$(dirname "$BACKUP_DEST")" + cp "$FULL_PATH" "$BACKUP_DEST" + + # Delete Logic + rm "$FULL_PATH" + echo " โœ… Moved to backup & deleted: $FILE_PATH" + fi +done + +if [ "$DRY_RUN" = false ]; then + echo "โœจ Cleanup complete." + echo "๐Ÿ“ฆ Backups saved to: $BACKUP_ROOT" + + echo "๐Ÿ—๏ธ Verifying build..." + bun run build || { echo "โŒ Build failed! Restore from backup immediately."; exit 1; } + echo "โœ… Build passed!" +else + echo "๐Ÿ Dry run complete." +fi diff --git a/tools/clean-fix.sh b/tools/clean-fix.sh new file mode 100755 index 00000000..2ef37c7d --- /dev/null +++ b/tools/clean-fix.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -e + +# Define root directory +ROOT_DIR=$(pwd) + +echo "๐Ÿงน Cleaning up project from $ROOT_DIR..." + +# Remove root node_modules and .turbo +rm -rf "$ROOT_DIR/node_modules" "$ROOT_DIR/.turbo" + +# Find and remove items in subdirectories +find "$ROOT_DIR/apps" "$ROOT_DIR/packages" -type d \( -name "node_modules" -o -name ".next" -o -name ".turbo" -o -name "dist" -o -name ".vite" \) -prune -exec rm -rf {} + + +echo "โœจ Clean complete." + +echo "๐Ÿ“ฆ Installing dependencies..." +bun install + +echo "๐Ÿ—๏ธ Building project..." +bun run build + +echo "โœ… Done! Try running the dev server again." diff --git a/tools/cleanup-dependencies.sh b/tools/cleanup-dependencies.sh new file mode 100755 index 00000000..c1aa3b1e --- /dev/null +++ b/tools/cleanup-dependencies.sh @@ -0,0 +1,45 @@ +#!/bin/bash +set -e + +# Config +PROJECT_ROOT=$(pwd) +DESKTOP_DIR="$PROJECT_ROOT/apps/desktop" + +# Unused packages identified by audit +UNUSED_PACKAGES=( + "@radix-ui/react-accordion" + "@radix-ui/react-aspect-ratio" + "@radix-ui/react-avatar" + "@radix-ui/react-hover-card" + "@radix-ui/react-menubar" + "@radix-ui/react-navigation-menu" + "@radix-ui/react-progress" + "@radix-ui/react-radio-group" + "@radix-ui/react-toggle" + "@radix-ui/react-toggle-group" + "cmdk" + "embla-carousel-react" + "react-day-picker" + "vaul" + "zod" + "react-hook-form" + "recharts" + "input-otp" +) + +echo "๐Ÿ” Dependency Cleanup" +echo "Target: $DESKTOP_DIR" +echo "Removing ${#UNUSED_PACKAGES[@]} unused packages..." + +cd "$DESKTOP_DIR" + +# Construct removal command +CMD="bun remove" +for pkg in "${UNUSED_PACKAGES[@]}"; do + CMD="$CMD $pkg" +done + +echo "Running: $CMD" +$CMD + +echo "โœ… Cleanup complete. Please run 'bun run build' to verify." diff --git a/tools/scan-dependencies.ts b/tools/scan-dependencies.ts new file mode 100644 index 00000000..9d36be87 --- /dev/null +++ b/tools/scan-dependencies.ts @@ -0,0 +1,121 @@ +import { glob } from 'glob'; +import * as fs from 'fs'; +import * as path from 'path'; + +// Config +const ROOT_DIR = path.resolve(__dirname, '..'); +const PACKAGE_JSONS = [ + 'package.json', + 'apps/desktop/package.json', + 'packages/style/package.json' +]; + +// Helper to read file content safely +function readFile(p: string): string { + return fs.existsSync(p) ? fs.readFileSync(p, 'utf-8') : ''; +} + +async function scanPackage(pkgPath: string) { + const fullPkgPath = path.join(ROOT_DIR, pkgPath); + console.log(`\n๐Ÿ“ฆ Scanning: ${pkgPath}`); + + if (!fs.existsSync(fullPkgPath)) { + console.error(`Error: ${pkgPath} not found.`); + return; + } + + const pkg = JSON.parse(readFile(fullPkgPath)); + const deps = Object.keys(pkg.dependencies || {}); + const devDeps = Object.keys(pkg.devDependencies || {}); + const allDeps = [...deps, ...devDeps]; + + if (allDeps.length === 0) { + console.log(" No dependencies found."); + return; + } + + // Define search scope based on package location + const pkgDir = path.dirname(fullPkgPath); + + // Glob patterns to search for usage + // We look in src, lib, tools, scripts, and root config files if specific + const searchPatterns = [ + '**/*.{ts,tsx,js,jsx,mjs,cjs,vue,svelte}', + 'vite.config.*', + 'vitest.config.*', + 'tailwind.config.*', + 'tsconfig*.json', + '.eslintrc*', + '.prettierrc*' + ]; + + // Collect all source content + const files = glob.sync(searchPatterns, { + cwd: pkgDir, + ignore: ['**/node_modules/**', '**/dist/**', '**/.next/**', '**/.turbo/**'], + absolute: true, + dot: true + }); + + // Also check root config files if we are in a sub-package (monorepo context) + // Dependencies might be used in root configs but installed in workspace?? + // Usually opposite. Let's stick to local package scope + root config references? + // Actually, simply checking the package directory recursively is usually enough. + + let allContent = ''; + // Append package.json scripts to content (binaries usage) + if (pkg.scripts) { + allContent += JSON.stringify(pkg.scripts) + '\n'; + } + + // Read all files + console.log(` Scanning ${files.length} files for usage references...`); + for (const file of files) { + allContent += readFile(file) + '\n'; + } + + const unused: string[] = []; + + for (const dep of allDeps) { + // 1. Strict Import Check: "from 'dep'" or "require('dep')" + // We optimize by checking if the string literal exists. + // We check for: + // 'dep' + // "dep" + // 'dep/ + // "dep/ + + // Simple heuristic: Does the package name appear in the code? + // This is "Double Verify" strategy from previous steps. + // If "react" appears, it's used. + // False positive risk: comments. But better to keep than delete. + + const isTypes = dep.startsWith('@types/'); + const searchName = isTypes ? dep.replace('@types/', '') : dep; + + // For types, we check if the base name is used OR the type pkg name is used (e.g. tsconfig) + const isUsed = allContent.includes(dep) || (isTypes && allContent.includes(searchName)); + + if (!isUsed) { + // Special handling for known "magic" packages + // e.g. "typescript", "vite", these might be used implicitly by run commands not in scripts? + // "rimraf" -> often in scripts. checked above. + unused.push(dep); + } + } + + if (unused.length > 0) { + console.log(` โš ๏ธ Potentially Unused (${unused.length}):`); + unused.forEach(d => console.log(` - ${d}`)); + } else { + console.log(" โœ… All dependencies appear to be used."); + } +} + +async function main() { + for (const p of PACKAGE_JSONS) { + await scanPackage(p); + } +} + +main().catch(console.error); diff --git a/tools/scan-unused.ts b/tools/scan-unused.ts new file mode 100644 index 00000000..ed1e5a8d --- /dev/null +++ b/tools/scan-unused.ts @@ -0,0 +1,88 @@ +import { glob } from 'glob'; +import * as fs from 'fs'; +import * as path from 'path'; + +// Config +const TARGET_DIR = path.resolve(__dirname, '../apps/desktop/src'); +const SEARCH_ROOT = path.resolve(__dirname, '../apps/desktop/src'); // Scope for references +const EXCLUSIONS = [ + 'index.ts', 'index.tsx', '.test.', '.spec.', '.css', '.scss', + 'vite-env.d.ts', 'main.tsx', 'App.tsx' +]; + +async function main() { + console.log(`๐Ÿ”Ž Scanning for unused files in: ${TARGET_DIR}`); + + // 1. Get all files in src + const files = glob.sync('**/*.{ts,tsx}', { cwd: TARGET_DIR, absolute: true }); + console.log(`Found ${files.length} files in src.`); + + // 2. Read all files in the Search Root to find references + const allSourceFiles = glob.sync('**/*.{ts,tsx}', { cwd: SEARCH_ROOT, absolute: true }); + let allContent = ''; + + for (const file of allSourceFiles) { + allContent += fs.readFileSync(file, 'utf-8') + '\n'; + } + + // 3. Check each file + const unusedFiles: string[] = []; + + for (const file of files) { + const filename = path.basename(file); + const basenameNoExt = path.basename(file, path.extname(file)); + const relativePath = path.relative(TARGET_DIR, file); + + // Skip exclusions + if (EXCLUSIONS.some(ex => filename.includes(ex))) continue; + + // Skip Pages (Entry points) + if (relativePath.startsWith('pages/')) continue; + + // Skip specific config/entry files + if (filename === 'layout.tsx' || filename === 'page.tsx') continue; + + // Skip Features (Already audited, but re-scanning is fine - + // actually let's skip features if we want to focus on the rest, + // OR just scan everything. Let's scan everything for completeness.) + + // Check strict import references (simplified text search for basename) + // We search for "basename" to catch imports like: import { X } from './basename' + // This is safe-ish because if 'basename' is common word, we assume used. + // We want to find UNUSED, so if we don't find it, it's definitely unused. + + // Regex for import paths? No, simple text search is robust "double-verify". + // If the filename (no ext) appears in the codebase, we assume it MIGHT be used. + // We check if it appears MORE than once (once is the definition itself, if inside the checked dir? + // Wait, we concatenated ALL content. So the definition file content is in there. + + // Refined strategy: + // Search in all OTHER files. + + let isUsed = false; + for (const sourceFile of allSourceFiles) { + if (sourceFile === file) continue; // Don't check self + + const content = fs.readFileSync(sourceFile, 'utf-8'); + + // Check for strict import path parts or import names + // Simplest: Check if the filename (without extension) exists in content + if (content.includes(basenameNoExt)) { + isUsed = true; + break; + } + } + + if (!isUsed) { + // Fallback: Check Global (Project Root - e.g. for dynamic or cross-package) + // For now, let's stick to Desktop scope as per plan, but flag "Suspicious". + console.log(`[UNUSED?] ${relativePath}`); + unusedFiles.push(relativePath); // Store relative path for easier reading + } + } + + console.log(`\n๐Ÿ“‹ Potential Unused Files (${unusedFiles.length}):`); + unusedFiles.forEach(f => console.log(f)); +} + +main().catch(console.error);