diff --git a/.github/workflows/ci-mac.yml b/.github/workflows/ci-mac.yml index 8af7ce60..bf21dcb3 100644 --- a/.github/workflows/ci-mac.yml +++ b/.github/workflows/ci-mac.yml @@ -1,30 +1,30 @@ name: CI Mac on: - workflow_dispatch: + workflow_dispatch: jobs: - build-macos: - name: Build desktop (${{ matrix.runner }}) - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - runner: [macos-latest, macos-15-intel] - steps: - - name: Checkout - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + build-macos: + name: Build desktop (${{ matrix.runner }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + runner: [macos-latest, macos-15-intel] + steps: + - name: Checkout + uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 - - name: Setup Bun - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 - with: - bun-version: latest + - name: Setup Bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 + with: + bun-version: latest - - name: Setup Rust - uses: dtolnay/rust-toolchain@stable + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable - - name: Install dependencies - run: bun install + - name: Install dependencies + run: bun install - - name: Build desktop app - run: bun run desktop:build + - name: Build desktop app + run: bun run desktop:build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 434ff687..182473d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,5 @@ name: Release + on: push: tags: @@ -53,6 +54,15 @@ jobs: releaseDraft: true prerelease: false + - name: generate linux checksums + run: bun ../../tools/scripts/generate-checksums.ts --input-dir=src-tauri/target/release/bundle --output=src-tauri/target/release/bundle/checksums-linux.txt --extensions=.AppImage,.deb,.rpm + working-directory: ./apps/desktop + + - name: upload linux checksums + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ github.ref_name }} ./apps/desktop/src-tauri/target/release/bundle/checksums-linux.txt --clobber + release-windows: permissions: contents: write @@ -106,6 +116,15 @@ jobs: releaseDraft: true prerelease: false + - name: generate windows checksums + run: bun ../../tools/scripts/generate-checksums.ts --input-dir=src-tauri/target/release/bundle --output=src-tauri/target/release/bundle/checksums-windows.txt --extensions=.msi,.exe + working-directory: ./apps/desktop + + - name: upload windows checksums + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ github.ref_name }} ./apps/desktop/src-tauri/target/release/bundle/checksums-windows.txt --clobber + release-macos: permissions: contents: write diff --git a/.github/workflows/snap.yml b/.github/workflows/snap.yml new file mode 100644 index 00000000..9681e705 --- /dev/null +++ b/.github/workflows/snap.yml @@ -0,0 +1,86 @@ +name: Snap + +on: + release: + types: + - published + workflow_dispatch: + inputs: + tag: + description: Existing GitHub release tag like v0.0.102. Leave empty for artifact-only builds. + required: false + type: string + publish: + description: Publish the built snap to the Snap Store. + required: false + type: boolean + default: false + release_channel: + description: Snap Store channel to release to. + required: false + type: choice + default: stable + options: + - stable + - candidate + - beta + - edge + +permissions: + contents: write + +jobs: + snap: + runs-on: ubuntu-latest + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + steps: + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + + - name: Resolve release metadata + id: meta + shell: bash + run: | + if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then + TAG="${{ github.event.release.tag_name }}" + else + TAG="${{ inputs.tag }}" + fi + + VERSION="$(python -c "import json; print(json.load(open('package.json'))['version'])")" + if [[ -n "$TAG" ]]; then + VERSION="${TAG#v}" + fi + + CHANNEL="stable" + if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then + CHANNEL="${{ inputs.release_channel }}" + fi + + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "channel=$CHANNEL" >> "$GITHUB_OUTPUT" + echo "DORA_RELEASE_VERSION=$VERSION" >> "$GITHUB_ENV" + + - name: Build snap + uses: snapcore/action-build@v1 + id: build + + - name: Upload snap artifact + uses: actions/upload-artifact@v4 + with: + name: dora-snap-${{ steps.meta.outputs.version }} + path: ${{ steps.build.outputs.snap }} + + - name: Upload snap to GitHub release + if: ${{ steps.meta.outputs.tag != '' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ steps.meta.outputs.tag }} ${{ steps.build.outputs.snap }} --clobber + + - name: Publish snap to Snap Store + if: ${{ env.SNAPCRAFT_STORE_CREDENTIALS != '' && (github.event_name == 'release' || inputs.publish) }} + uses: snapcore/action-publish@v1 + with: + snap: ${{ steps.build.outputs.snap }} + release: ${{ steps.meta.outputs.channel }} diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml new file mode 100644 index 00000000..2741ebc3 --- /dev/null +++ b/.github/workflows/winget.yml @@ -0,0 +1,113 @@ +name: Winget + +on: + release: + types: + - published + workflow_dispatch: + inputs: + tag: + description: Existing GitHub release tag like v0.0.102. + required: true + type: string + submit_update: + description: Submit a wingetcreate update PR. + required: false + type: boolean + default: false + +permissions: + contents: write + +jobs: + generate-manifests: + runs-on: ubuntu-latest + outputs: + tag: ${{ steps.meta.outputs.tag }} + version: ${{ steps.meta.outputs.version }} + installer_url: ${{ steps.meta.outputs.installer_url }} + steps: + - uses: actions/checkout@0c366fd6a839edf440554fa01a7085ccba70ac98 + + - name: Setup bun + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 + + - name: Resolve release metadata + id: meta + shell: bash + run: | + if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then + TAG="${{ github.event.release.tag_name }}" + else + TAG="${{ inputs.tag }}" + fi + + VERSION="${TAG#v}" + INSTALLER_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/Dora_${VERSION}_x64_en-US.msi" + CHECKSUMS_URL="https://github.com/${GITHUB_REPOSITORY}/releases/download/${TAG}/checksums-windows.txt" + + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + echo "installer_url=$INSTALLER_URL" >> "$GITHUB_OUTPUT" + echo "checksums_url=$CHECKSUMS_URL" >> "$GITHUB_OUTPUT" + + - name: Download Windows checksums + run: curl --fail --location --output checksums-windows.txt "${{ steps.meta.outputs.checksums_url }}" + + - name: Generate Winget manifests + run: | + bun run release:winget -- \ + --version=${{ steps.meta.outputs.version }} \ + --installer-url=${{ steps.meta.outputs.installer_url }} \ + --checksums-file=checksums-windows.txt \ + --installer-file=Dora_${{ steps.meta.outputs.version }}_x64_en-US.msi + + - name: Archive Winget manifests + run: | + tar -czf winget-manifests-${{ steps.meta.outputs.version }}.tar.gz \ + -C packaging/winget/manifests/${{ steps.meta.outputs.version }} . + + - name: Upload Winget manifest artifact + uses: actions/upload-artifact@v4 + with: + name: winget-manifests-${{ steps.meta.outputs.version }} + path: winget-manifests-${{ steps.meta.outputs.version }}.tar.gz + + - name: Upload Winget manifests to GitHub release + if: ${{ github.event_name == 'release' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ steps.meta.outputs.tag }} winget-manifests-${{ steps.meta.outputs.version }}.tar.gz --clobber + + submit-update: + needs: generate-manifests + if: ${{ (github.event_name == 'release' && vars.WINGET_PACKAGE_READY == 'true') || (github.event_name == 'workflow_dispatch' && inputs.submit_update) }} + runs-on: windows-latest + env: + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_CREATE_GITHUB_TOKEN }} + steps: + - name: Skip when Winget token is not configured + if: ${{ env.WINGET_CREATE_GITHUB_TOKEN == '' }} + shell: pwsh + run: | + Write-Host "WINGET_CREATE_GITHUB_TOKEN is not configured. Skipping automated Winget submission." + exit 0 + + - name: Install WingetCreate dependencies + if: ${{ env.WINGET_CREATE_GITHUB_TOKEN != '' }} + shell: pwsh + run: | + Invoke-WebRequest https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx + Add-AppxPackage .\Microsoft.VCLibs.x64.14.00.Desktop.appx + Invoke-WebRequest https://aka.ms/wingetcreate/latest/msixbundle -OutFile wingetcreate.msixbundle + Add-AppxPackage .\wingetcreate.msixbundle + + - name: Submit Winget update + if: ${{ env.WINGET_CREATE_GITHUB_TOKEN != '' }} + shell: pwsh + run: | + wingetcreate update RemcoStoeten.Dora ` + -u "${{ needs.generate-manifests.outputs.installer_url }}" ` + -v "${{ needs.generate-manifests.outputs.version }}" ` + -t "${env:WINGET_CREATE_GITHUB_TOKEN}" ` + --submit diff --git a/.gitignore b/.gitignore index 43e0fe03..95537327 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,4 @@ apps/desktop/build_log.txt .env.local test1.db test2.db +target/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c4761e3..a8444da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,58 @@ # Changelog +## 0.0.102 - Snap Workflow Follow-up & Packaging Release Cleanup + +**Date:** 2026-04-05 + +**Highlights** + +- Fixed the Snap CI workflow so packaging builds run correctly on GitHub Actions. +- Switched the Snap build path to a direct Rust release build instead of an invalid Tauri bundle flag. +- Carries the packaging automation and VM lab work forward into a clean tag that matches branch head. + +## 0.0.101 - Packaging Automation, VM Lab & Desktop Iteration + +**Date:** 2026-04-05 + +**Highlights** + +- Added repo-native packaging helpers for Winget, AUR, Snap, release checksums, and release guidance. +- Added an Ubuntu host VM lab flow for provisioning Ubuntu, Arch, and Windows packaging test environments. +- Updated the in-app changelog and release surfaces for the new release milestone. +- Bundled the current desktop, docs, test, and workflow iteration work into the `0.0.101` line. + +## 0.1.0 - Project Foundation & Documentation Audit + +**Date:** 2026-04-04 + +**Highlights** + +- Established 0.1.0 version baseline across the monorepo. +- Completed full Homebrew installation documentation and README overhaul. +- Verified 115/115 tests passing for the new milestone. + +## 0.0.99 - Homebrew Support & CI Security Hardening + +**Date:** 2026-04-04 + +**Highlights** + +- Added the official Homebrew Tap at `remcostoeten/homebrew-dora` (`brew install dora`). +- Pinned all GitHub Actions to specific commit SHAs for improved supply chain security. +- Optimized the esbuild target to `esnext` to resolve CI transform errors. +- Stabilized the Rust toolchain reference in automated workflows. + +## 0.0.98 - Live Database Updates & Performance Refactor + +**Date:** 2026-04-04 + +**Highlights** + +- Implemented a backend-driven live database monitor manager. +- Eliminated inefficient frontend polling logic for faster performance. +- Added support for real-time data grid updates on external database changes. +- Enhanced documentation with an animated showcase of live performance. + ## 0.0.97 - Type Safety Recovery, Feature-State Alignment & Docker Manager Updates **Date:** 2026-02-20 diff --git a/README.md b/README.md index add5bc5d..64880b2b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
- Dora Logo + Dora Logo

Dora

A clean, fast desktop database studio for people who actually work in data.

@@ -12,7 +12,7 @@

- Dora App Demonstration + Dora App Demonstration

Dora is a cross-platform database studio built with Tauri, Rust, React, and Monaco. It is designed for a native-feeling desktop workflow: fast navigation, direct editing, real SQL execution, clean table tooling, and a UI that stays out of your way. @@ -80,7 +80,9 @@ Release bundles are configured for: GitHub Releases: https://github.com/remcostoeten/dora/releases -## Installation (macOS / Linux) +## Installation + +### Homebrew (macOS / Linux) You can install Dora via Homebrew using our custom tap: @@ -89,14 +91,67 @@ brew tap remcostoeten/dora brew install dora ``` +Or in a single command: + +```bash +brew install remcostoeten/dora/dora +``` + +### Linux Packages + +Dora releases also ship native Linux packages: + +- `.deb` for Debian / Ubuntu +- `.rpm` for Fedora / RHEL / openSUSE +- `.AppImage` for portable Linux installs + +Download the latest package from GitHub Releases: +https://github.com/remcostoeten/dora/releases/latest + +Examples: + +```bash +# Debian / Ubuntu +sudo apt install ./dora_*.deb +``` + +```bash +# Fedora / RHEL +sudo dnf install ./dora-*.rpm +``` + +```bash +# openSUSE +sudo zypper install ./dora-*.rpm +``` + +### Other Package Managers + +These are possible, but are not published yet: + +- Snap via Snapcraft +- APT repository metadata for Debian / Ubuntu +- YUM / DNF repository metadata for RPM-based distros +- Winget, Scoop, or Chocolatey for Windows +- AUR for Arch Linux + +At the moment, Homebrew is the only package-manager install path wired up end-to-end in this repo. The other supported install path is downloading the release artifact directly. + +Maintainer packaging docs: + +- `docs/distribution/winget.md` +- `docs/distribution/aur.md` +- `docs/distribution/snap.md` +- `docs/distribution/one-machine-playbook.md` + ## Database Support -| Database | Status | Notes | -| :-- | :--: | :-- | -| PostgreSQL | Supported | Full desktop path, including SSH tunneling and live external change monitoring | -| SQLite | Supported | Native desktop workflow | -| LibSQL / Turso | Supported | Local and remote flows | -| MySQL | Not shipped | Scaffolded in parts of the codebase, not exposed as a supported feature | +| Database | Status | Notes | +| :------------- | :---------: | :----------------------------------------------------------------------------- | +| PostgreSQL | Supported | Full desktop path, including SSH tunneling and live external change monitoring | +| SQLite | Supported | Native desktop workflow | +| LibSQL / Turso | Supported | Local and remote flows | +| MySQL | Not shipped | Scaffolded in parts of the codebase, not exposed as a supported feature | ## Live Updates diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 55157bae..ca0cbf75 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,82 @@ +# Release Notes - v0.0.102 + +**Date:** 2026-04-05 +**Tag:** `v0.0.102` + +## Summary + +Follow-up release that rolls the Snap CI fixes onto a clean tag after `v0.0.101`. + +## Changes + +- Fixed Snap GitHub Actions builds to run Snapcraft with the required privileges in destructive mode. +- Replaced the invalid `tauri build --bundles none` step with a direct Rust release build for Snap packaging. +- Keeps the packaging automation, release guidance, and VM lab work on the tagged branch head. + +# Release Notes - v0.0.101 + +**Date:** 2026-04-05 +**Tag:** `v0.0.101` + +## Summary + +Release that rolls packaging automation, VM-based packaging workflows, and the current desktop/docs/test iteration into `v0.0.101`. + +## Changes + +- Added release checksum generation for Windows and Linux assets. +- Added repo-native manifest/package generation for Winget and AUR. +- Added Snap packaging scaffolding and GitHub Actions workflow support. +- Added an interactive release guide and Ubuntu VM lab helper for one-machine packaging workflows. +- Refreshed changelog/release surfaces and carried forward the current desktop and documentation iteration work. + +# Release Notes - v0.1.0 + +**Date:** 2026-04-04 +**Tag:** `v0.1.0` + +## Summary + +Milestone release establishing the 0.1.x codebase baseline, including comprehensive installation paths (Homebrew) and documentation overhaul. + +## Changes + +- Established 0.1.0 version baseline across the monorepo. +- Completed full Homebrew installation documentation in the README. +- Verified 115/115 tests passing for the new milestone. + +# Release Notes - v0.0.99 + +**Date:** 2026-04-04 +**Tag:** `v0.0.99` + +## Summary + +Launched official Homebrew support and hardened CI/CD security and stability. + +## Changes + +- Added the official Homebrew Tap at `remcostoeten/homebrew-dora` (`brew install dora`). +- Pinned all GitHub Actions to specific commit SHAs. +- Optimized the esbuild target to `esnext` to resolve CI transform errors. +- Stabilized the Rust toolchain reference in automated workflows. + +# Release Notes - v0.0.98 + +**Date:** 2026-04-04 +**Tag:** `v0.0.98` + +## Summary + +Performance-focused release introducing a backend-driven live database monitor manager. + +## Changes + +- Implemented a backend-driven live database monitor manager. +- Eliminated inefficient frontend polling logic for faster performance. +- Added support for real-time data grid updates on external database changes. +- Enhanced documentation with an animated showcase. + # Release Notes - v0.0.97 **Date:** 2026-02-20 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 184a958b..4137e721 100644 --- a/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts +++ b/__tests__/apps/desktop/features/drizzle-runner/lsp-patterns.test.ts @@ -13,7 +13,6 @@ import { getColumnMatch } from '../../../../../apps/desktop/src/features/drizzle-runner/utils/lsp-patterns' - describe('Drizzle LSP Patterns', () => { describe('getDbName', () => { it('detects db context', () => { diff --git a/__tests__/apps/desktop/src/features/connections/mysql-mapping.test.ts b/__tests__/apps/desktop/src/features/connections/mysql-mapping.test.ts index f5930833..860cbc54 100644 --- a/__tests__/apps/desktop/src/features/connections/mysql-mapping.test.ts +++ b/__tests__/apps/desktop/src/features/connections/mysql-mapping.test.ts @@ -24,4 +24,3 @@ describe('connections/api mapping (mysql)', function () { expect(true).toBe(false) }) }) - diff --git a/__tests__/apps/desktop/src/features/connections/mysql-validation.test.ts b/__tests__/apps/desktop/src/features/connections/mysql-validation.test.ts index e8988719..30dd5609 100644 --- a/__tests__/apps/desktop/src/features/connections/mysql-validation.test.ts +++ b/__tests__/apps/desktop/src/features/connections/mysql-validation.test.ts @@ -45,4 +45,3 @@ describe('connections validation (mysql)', function () { expect(result.success).toBe(false) }) }) - diff --git a/__tests__/apps/desktop/src/features/database-studio/hooks/use-live-monitor.test.ts b/__tests__/apps/desktop/src/features/database-studio/hooks/use-live-monitor.test.ts index b10c6b71..4d5c6d25 100644 --- a/__tests__/apps/desktop/src/features/database-studio/hooks/use-live-monitor.test.ts +++ b/__tests__/apps/desktop/src/features/database-studio/hooks/use-live-monitor.test.ts @@ -1,21 +1,13 @@ -import { renderHook, act, waitFor } from '@testing-library/react' +import { renderHook, act } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import { useLiveMonitor } from '@/features/database-studio/hooks/use-live-monitor' const mocks = vi.hoisted(function () { return { - listen: vi.fn(), startLiveMonitor: vi.fn(), stopLiveMonitor: vi.fn() } }) -vi.mock('@tauri-apps/api/event', function () { - return { - listen: mocks.listen - } -}) - vi.mock('@/lib/bindings', function () { return { commands: { @@ -30,12 +22,7 @@ describe('useLiveMonitor', function () { beforeEach(function () { backendListener = null - mocks.listen.mockImplementation(function (_eventName, handler) { - backendListener = handler - return Promise.resolve(function () { - backendListener = null - }) - }) + vi.resetModules() mocks.startLiveMonitor.mockResolvedValue({ status: 'ok', data: { @@ -45,23 +32,43 @@ describe('useLiveMonitor', function () { }) mocks.stopLiveMonitor.mockResolvedValue({ status: 'ok', data: null }) - Object.defineProperty(window, '__TAURI__', { - value: {}, + Object.defineProperty(window, '__TAURI_INTERNALS__', { + value: { + transformCallback(handler: (event: { payload: any }) => void) { + backendListener = handler + return 1 + }, + unregisterCallback: vi.fn(), + invoke: vi.fn(async function (command: string) { + if (command === 'plugin:event|listen') { + return 1 + } + if (command === 'plugin:event|unlisten') { + backendListener = null + return null + } + return null + }) + }, + configurable: true + }) + Object.defineProperty(window, '__TAURI_EVENT_PLUGIN_INTERNALS__', { + value: { + unregisterListener: vi.fn() + }, configurable: true }) - delete (window as unknown as Record).__TAURI_INTERNALS__ vi.useFakeTimers() }) afterEach(function () { vi.runOnlyPendingTimers() vi.useRealTimers() - delete (window as unknown as Record).__TAURI__ - delete (window as unknown as Record).__TAURI_INTERNALS__ vi.clearAllMocks() }) it('batches backend change events into a single refresh callback', async function () { + const { useLiveMonitor } = await import('@/features/database-studio/hooks/use-live-monitor') const onDataChanged = vi.fn() const { result } = renderHook(function () { @@ -82,10 +89,12 @@ describe('useLiveMonitor', function () { }) }) - await waitFor(function () { - expect(mocks.startLiveMonitor).toHaveBeenCalledTimes(1) + await act(async function () { + await Promise.resolve() }) + expect(mocks.startLiveMonitor).toHaveBeenCalledTimes(1) + await act(async function () { backendListener?.({ payload: { 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 ed6c9295..235062c9 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,6 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' - // Mock the docker-client module vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/api/docker-client', () => ({ checkDockerAvailability: vi.fn(), @@ -11,8 +10,8 @@ vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/api/docke describe('seedDatabase', () => { // We need to keep a reference to the dynamically imported module - let containerService: any; - let dockerClient: any; + let containerService: any + let dockerClient: any beforeEach(async () => { vi.resetModules() // Important: clear cache so isTauri is re-evaluated @@ -26,8 +25,10 @@ describe('seedDatabase', () => { }) // 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') + 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(() => { @@ -77,7 +78,10 @@ describe('seedDatabase', () => { exitCode: 1 }) - const result = await containerService.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 8f76c9fb..09e65b57 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 @@ -35,18 +35,19 @@ describe('docker-client', () => { 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() } + 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) + }) as any }) describe('checkDockerAvailability', () => { @@ -83,8 +84,7 @@ describe('docker-client', () => { describe('listContainers', () => { it('should list and parse containers correctly including Env', async () => { 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 }) 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 6e0d5768..7967cc6d 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 @@ -10,32 +10,48 @@ vi.mock('@/components/ui/use-toast', () => ({ useToast: () => ({ toast: vi.fn() }) })) -vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/container-list', () => ({ - ContainerList: ({ containers, isLoading }: any) => ( -
- {isLoading ? 'Loading containers...' : `Containers: ${containers?.length || 0}`} -
- ) -})) - -vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/container-details-panel', () => ({ - ContainerDetailsPanel: () =>
-})) +vi.mock( + '../../../../../../../apps/desktop/src/features/docker-manager/components/container-list', + () => ({ + ContainerList: ({ containers, isLoading }: any) => ( +
+ {isLoading ? 'Loading containers...' : `Containers: ${containers?.length || 0}`} +
+ ) + }) +) -vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/create-container-dialog', () => ({ - CreateContainerDialog: ({ open }: any) => (open ?
: null) -})) +vi.mock( + '../../../../../../../apps/desktop/src/features/docker-manager/components/container-details-panel', + () => ({ + ContainerDetailsPanel: () =>
+ }) +) -vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/components/sandbox-indicator', () => ({ - SandboxIndicator: () =>
-})) +vi.mock( + '../../../../../../../apps/desktop/src/features/docker-manager/components/create-container-dialog', + () => ({ + CreateContainerDialog: ({ open }: any) => + open ?
: null + }) +) -vi.mock('../../../../../../../apps/desktop/src/features/docker-manager/api/mutations/use-container-actions', () => ({ - useContainerActions: () => ({ - mutate: vi.fn(), - isPending: false +vi.mock( + '../../../../../../../apps/desktop/src/features/docker-manager/components/sandbox-indicator', + () => ({ + SandboxIndicator: () =>
}) -})) +) + +vi.mock( + '../../../../../../../apps/desktop/src/features/docker-manager/api/mutations/use-container-actions', + () => ({ + useContainerActions: () => ({ + mutate: vi.fn(), + isPending: false + }) + }) +) describe('DockerView', () => { const mockCreateContainer = { @@ -52,12 +68,8 @@ describe('DockerView', () => { ) }) - const renderWithProviders = (component: any) => { - return render( - - {component} - - ) + function renderWithProviders(component: any) { + return render({component}) } it('shows loading state when checking docker availability', () => { diff --git a/__tests__/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.test.ts b/__tests__/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.test.ts index d2eb004d..48a972a5 100644 --- a/__tests__/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.test.ts +++ b/__tests__/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.test.ts @@ -1,127 +1,127 @@ import { describe, it, expect } from 'vitest' import { - levenshtein, - similarity, - findClosestMatch, - getSuggestions, - createDrizzleTypoDetector, - createPrismaTypoDetector, - createSqlTypoDetector + levenshtein, + similarity, + findClosestMatch, + getSuggestions, + createDrizzleTypoDetector, + createPrismaTypoDetector, + createSqlTypoDetector } from '../../../../../../../apps/desktop/src/features/drizzle-runner/utils/fuzzy-match' describe('Fuzzy Matching Utilities', () => { - describe('levenshtein', () => { - it('calculates distance correctly', () => { - expect(levenshtein('kitten', 'sitting')).toBe(3) - expect(levenshtein('book', 'back')).toBe(2) - expect(levenshtein('test', 'test')).toBe(0) - expect(levenshtein('', 'abc')).toBe(3) - expect(levenshtein('abc', '')).toBe(3) - }) - - it('is case insensitive', () => { - expect(levenshtein('Test', 'test')).toBe(0) - expect(levenshtein('Kitten', 'sitting')).toBe(3) - }) - }) - - describe('similarity', () => { - it('calculates similarity score 0-1', () => { - expect(similarity('test', 'test')).toBe(1) - expect(similarity('abc', 'def')).toBe(0) - expect(similarity('hook', 'book')).toBe(0.75) // 1 diff / 4 length = 0.25 dist -> 0.75 sim - }) - }) - - describe('findClosestMatch', () => { - const candidates = ['users', 'products', 'orders', 'order_items'] - - it('finds exact match', () => { - const match = findClosestMatch('users', candidates) - expect(match?.value).toBe('users') - expect(match?.distance).toBe(0) - }) - - it('finds close match', () => { - const match = findClosestMatch('usr', candidates) - expect(match?.value).toBe('users') - }) - - it('respects threshold', () => { - const match = findClosestMatch('xzq', candidates, 2) - expect(match).toBeNull() - }) - }) - - describe('getSuggestions', () => { - const candidates = ['apple', 'apply', 'apricot', 'banana'] - - it('returns ranked suggestions', () => { - const suggestions = getSuggestions('appl', candidates) - expect(suggestions).toHaveLength(2) - expect(suggestions[0].value).toBe('apple') // dist 1 - expect(suggestions[1].value).toBe('apply') // dist 1 - }) - }) + describe('levenshtein', () => { + it('calculates distance correctly', () => { + expect(levenshtein('kitten', 'sitting')).toBe(3) + expect(levenshtein('book', 'back')).toBe(2) + expect(levenshtein('test', 'test')).toBe(0) + expect(levenshtein('', 'abc')).toBe(3) + expect(levenshtein('abc', '')).toBe(3) + }) + + it('is case insensitive', () => { + expect(levenshtein('Test', 'test')).toBe(0) + expect(levenshtein('Kitten', 'sitting')).toBe(3) + }) + }) + + describe('similarity', () => { + it('calculates similarity score 0-1', () => { + expect(similarity('test', 'test')).toBe(1) + expect(similarity('abc', 'def')).toBe(0) + expect(similarity('hook', 'book')).toBe(0.75) // 1 diff / 4 length = 0.25 dist -> 0.75 sim + }) + }) + + describe('findClosestMatch', () => { + const candidates = ['users', 'products', 'orders', 'order_items'] + + it('finds exact match', () => { + const match = findClosestMatch('users', candidates) + expect(match?.value).toBe('users') + expect(match?.distance).toBe(0) + }) + + it('finds close match', () => { + const match = findClosestMatch('usr', candidates) + expect(match?.value).toBe('users') + }) + + it('respects threshold', () => { + const match = findClosestMatch('xzq', candidates, 2) + expect(match).toBeNull() + }) + }) + + describe('getSuggestions', () => { + const candidates = ['apple', 'apply', 'apricot', 'banana'] + + it('returns ranked suggestions', () => { + const suggestions = getSuggestions('appl', candidates) + expect(suggestions).toHaveLength(2) + expect(suggestions[0].value).toBe('apple') // dist 1 + expect(suggestions[1].value).toBe('apply') // dist 1 + }) + }) }) describe('Typo Detectors', () => { - const mockSchema = [ - { name: 'users', columns: [{ name: 'id' }, { name: 'email' }] }, - { name: 'posts', columns: [{ name: 'id' }, { name: 'userId' }, { name: 'title' }] } - ] - - describe('Drizzle Detector', () => { - const detect = createDrizzleTypoDetector(mockSchema) - - it('ignores reserved words', () => { - const typos = detect('db.select().from(users)') - expect(typos).toHaveLength(0) - }) - - it('detects table name typo', () => { - const typos = detect('db.select().from(usrs)') - expect(typos).toHaveLength(1) - expect(typos[0].word).toBe('usrs') - expect(typos[0].suggestion).toBe('users') - }) - - it('detects column typo in dot notation', () => { - const typos = detect('eq(users.emai, "test")') - expect(typos).toHaveLength(1) - expect(typos[0].word).toBe('users.emai') - expect(typos[0].suggestion).toBe('users.email') - }) - }) - - describe('Prisma Detector', () => { - const detect = createPrismaTypoDetector(mockSchema) - - it('ignores prisma keywords', () => { - const typos = detect('prisma.users.findMany({ where: { id: 1 } })') - expect(typos).toHaveLength(0) - }) - - it('detects model typo', () => { - // Testing simple model name which is the common case in many contexts - const typos = detect('const x = usrs') - expect(typos).toHaveLength(1) - expect(typos[0].suggestion).toBe('users') - }) - }) - - describe('SQL Detector', () => { - const detect = createSqlTypoDetector(mockSchema) - - it('ignores SQL keywords case-insensitive', () => { - const typos = detect('SELECT * FROM users WHERE id = 1') - expect(typos).toHaveLength(0) - }) - - it('detects typo in SQL', () => { - const typos = detect('SELECT * FROM usrs') - expect(typos).toHaveLength(1) - expect(typos[0].suggestion).toBe('users') - }) - }) + const mockSchema = [ + { name: 'users', columns: [{ name: 'id' }, { name: 'email' }] }, + { name: 'posts', columns: [{ name: 'id' }, { name: 'userId' }, { name: 'title' }] } + ] + + describe('Drizzle Detector', () => { + const detect = createDrizzleTypoDetector(mockSchema) + + it('ignores reserved words', () => { + const typos = detect('db.select().from(users)') + expect(typos).toHaveLength(0) + }) + + it('detects table name typo', () => { + const typos = detect('db.select().from(usrs)') + expect(typos).toHaveLength(1) + expect(typos[0].word).toBe('usrs') + expect(typos[0].suggestion).toBe('users') + }) + + it('detects column typo in dot notation', () => { + const typos = detect('eq(users.emai, "test")') + expect(typos).toHaveLength(1) + expect(typos[0].word).toBe('users.emai') + expect(typos[0].suggestion).toBe('users.email') + }) + }) + + describe('Prisma Detector', () => { + const detect = createPrismaTypoDetector(mockSchema) + + it('ignores prisma keywords', () => { + const typos = detect('prisma.users.findMany({ where: { id: 1 } })') + expect(typos).toHaveLength(0) + }) + + it('detects model typo', () => { + // Testing simple model name which is the common case in many contexts + const typos = detect('const x = usrs') + expect(typos).toHaveLength(1) + expect(typos[0].suggestion).toBe('users') + }) + }) + + describe('SQL Detector', () => { + const detect = createSqlTypoDetector(mockSchema) + + it('ignores SQL keywords case-insensitive', () => { + const typos = detect('SELECT * FROM users WHERE id = 1') + expect(typos).toHaveLength(0) + }) + + it('detects typo in SQL', () => { + const typos = detect('SELECT * FROM usrs') + expect(typos).toHaveLength(1) + expect(typos[0].suggestion).toBe('users') + }) + }) }) diff --git a/__tests__/apps/desktop/src/features/sql-console/components/sql-results.test.tsx b/__tests__/apps/desktop/src/features/sql-console/components/sql-results.test.tsx index d034261c..e2d65c67 100644 --- a/__tests__/apps/desktop/src/features/sql-console/components/sql-results.test.tsx +++ b/__tests__/apps/desktop/src/features/sql-console/components/sql-results.test.tsx @@ -61,7 +61,15 @@ describe('SqlResults', function () { fireEvent.doubleClick(screen.getByText('Bob')) - const input = screen.getByDisplayValue('Bob') + const input = screen + .getAllByDisplayValue('Bob') + .find((element) => element.getAttribute('placeholder') !== 'Filter results...') + + expect(input).toBeTruthy() + if (!input) { + throw new Error('Expected inline editor input to be rendered') + } + fireEvent.change(input, { target: { value: 'Bobby' } }) fireEvent.keyDown(input, { key: 'Enter' }) diff --git a/__tests__/apps/desktop/src/shared/ui/number-input.test.tsx b/__tests__/apps/desktop/src/shared/ui/number-input.test.tsx index 45ef7015..31131f5f 100644 --- a/__tests__/apps/desktop/src/shared/ui/number-input.test.tsx +++ b/__tests__/apps/desktop/src/shared/ui/number-input.test.tsx @@ -3,43 +3,43 @@ import { describe, it, expect, vi } from 'vitest' import { NumberInput } from '@/shared/ui/number-input' describe('NumberInput', () => { - it('renders correctly with initial value', () => { - render( { }} />) - const input = screen.getByRole('spinbutton') as HTMLInputElement - expect(input.value).toBe('10') - }) - - it('increments value on up button click', () => { - const handleChange = vi.fn() - render() - - const incrementBtn = screen.getByLabelText('Increase value') - fireEvent.click(incrementBtn) - - // Since we are mocking the change event dispatch in JSDOM, we check if onChange was called - // Note: dispatching manual events in React testing can be tricky. - // The component triggers a native change event. - expect(handleChange).toHaveBeenCalled() - }) - - it('decrements value on down button click', () => { - const handleChange = vi.fn() - render() - - const decrementBtn = screen.getByLabelText('Decrease value') - fireEvent.click(decrementBtn) - - expect(handleChange).toHaveBeenCalled() - }) - - it('respects min and max', () => { - // We test disability of buttons logic here mainly - const { rerender } = render( { }} />) - const incrementBtn = screen.getByLabelText('Increase value') - expect(incrementBtn).toBeDisabled() - - rerender( { }} />) - const decrementBtn = screen.getByLabelText('Decrease value') - expect(decrementBtn).toBeDisabled() - }) + it('renders correctly with initial value', () => { + render( {}} />) + const input = screen.getByRole('spinbutton') as HTMLInputElement + expect(input.value).toBe('10') + }) + + it('increments value on up button click', () => { + const handleChange = vi.fn() + render() + + const incrementBtn = screen.getByLabelText('Increase value') + fireEvent.click(incrementBtn) + + // Since we are mocking the change event dispatch in JSDOM, we check if onChange was called + // Note: dispatching manual events in React testing can be tricky. + // The component triggers a native change event. + expect(handleChange).toHaveBeenCalled() + }) + + it('decrements value on down button click', () => { + const handleChange = vi.fn() + render() + + const decrementBtn = screen.getByLabelText('Decrease value') + fireEvent.click(decrementBtn) + + expect(handleChange).toHaveBeenCalled() + }) + + it('respects min and max', () => { + // We test disability of buttons logic here mainly + const { rerender } = render( {}} />) + const incrementBtn = screen.getByLabelText('Increase value') + expect(incrementBtn).toBeDisabled() + + rerender( {}} />) + const decrementBtn = screen.getByLabelText('Decrease value') + expect(decrementBtn).toBeDisabled() + }) }) diff --git a/__tests__/apps/desktop/tauri-invoke-contract.test.ts b/__tests__/apps/desktop/tauri-invoke-contract.test.ts index 1a5ec277..9df339bb 100644 --- a/__tests__/apps/desktop/tauri-invoke-contract.test.ts +++ b/__tests__/apps/desktop/tauri-invoke-contract.test.ts @@ -15,7 +15,12 @@ function listSourceFiles(dirPath: string): string[] { const files: string[] = [] for (const entry of entries) { - if (entry === 'node_modules' || entry === 'dist' || entry === 'target' || entry === 'vendor') { + if ( + entry === 'node_modules' || + entry === 'dist' || + entry === 'target' || + entry === 'vendor' + ) { continue } diff --git a/__tests__/apps/desktop/tests/settings-restoration.test.ts b/__tests__/apps/desktop/tests/settings-restoration.test.ts index e74f2db1..8c9ef5bf 100644 --- a/__tests__/apps/desktop/tests/settings-restoration.test.ts +++ b/__tests__/apps/desktop/tests/settings-restoration.test.ts @@ -24,14 +24,14 @@ function useSettingsRestorationTest(initialSettings = mockSettings) { ) function updateSetting(key: string, value: any) { - setSettings((prev) => ({ ...prev, [key]: value })) - mockUpdateSetting(key, value) - } + setSettings((prev) => ({ ...prev, [key]: value })) + mockUpdateSetting(key, value) + } function updateSettingsFn(updates: any) { - setSettings((prev) => ({ ...prev, ...updates })) - mockUpdateSettings(updates) - } + setSettings((prev) => ({ ...prev, ...updates })) + mockUpdateSettings(updates) + } // Simulate saving last connection logic useEffect(() => { diff --git a/__tests__/apps/desktop/unit/accessibility-utilities.test.ts b/__tests__/apps/desktop/unit/accessibility-utilities.test.ts index 660ceae4..bc58d0fa 100644 --- a/__tests__/apps/desktop/unit/accessibility-utilities.test.ts +++ b/__tests__/apps/desktop/unit/accessibility-utilities.test.ts @@ -1,174 +1,174 @@ import { describe, it, expect } from 'vitest' describe('Accessibility Utilities', function () { - describe('Focus Key Generation', function () { - it('should generate unique focus keys from row and column indices', function () { - function getFocusKey(row: number, col: number): string { - return `cell-${row}-${col}` - } - expect(getFocusKey(0, 0)).toBe('cell-0-0') - expect(getFocusKey(5, 10)).toBe('cell-5-10') - }) - }) - - describe('Keyboard Navigation', function () { - it('should calculate next cell in grid navigation', function () { - function getNextCell( - currentRow: number, - currentCol: number, - direction: 'up' | 'down' | 'left' | 'right', - maxRow: number, - maxCol: number - ) { - let nextRow = currentRow - let nextCol = currentCol - - switch (direction) { - case 'up': - nextRow = Math.max(0, currentRow - 1) - break - case 'down': - nextRow = Math.min(maxRow, currentRow + 1) - break - case 'left': - nextCol = Math.max(0, currentCol - 1) - break - case 'right': - nextCol = Math.min(maxCol, currentCol + 1) - break - } - - return { row: nextRow, col: nextCol } - } - - expect(getNextCell(5, 5, 'up', 10, 10)).toEqual({ row: 4, col: 5 }) - expect(getNextCell(5, 5, 'down', 10, 10)).toEqual({ row: 6, col: 5 }) - expect(getNextCell(5, 5, 'left', 10, 10)).toEqual({ row: 5, col: 4 }) - expect(getNextCell(5, 5, 'right', 10, 10)).toEqual({ row: 5, col: 6 }) - }) - - it('should clamp navigation at boundaries', function () { - function getNextCell( - currentRow: number, - currentCol: number, - direction: 'up' | 'down' | 'left' | 'right', - maxRow: number, - maxCol: number - ) { - let nextRow = currentRow - let nextCol = currentCol - - switch (direction) { - case 'up': - nextRow = Math.max(0, currentRow - 1) - break - case 'down': - nextRow = Math.min(maxRow, currentRow + 1) - break - case 'left': - nextCol = Math.max(0, currentCol - 1) - break - case 'right': - nextCol = Math.min(maxCol, currentCol + 1) - break - } - - return { row: nextRow, col: nextCol } - } - - expect(getNextCell(0, 0, 'up', 10, 10)).toEqual({ row: 0, col: 0 }) - expect(getNextCell(0, 0, 'left', 10, 10)).toEqual({ row: 0, col: 0 }) - expect(getNextCell(10, 10, 'down', 10, 10)).toEqual({ row: 10, col: 10 }) - expect(getNextCell(10, 10, 'right', 10, 10)).toEqual({ row: 10, col: 10 }) - }) - }) - - describe('Tab Navigation', function () { - it('should wrap tab navigation to next row', function () { - function getNextTabCell( - currentRow: number, - currentCol: number, - maxRow: number, - maxCol: number, - shiftKey: boolean - ) { - if (shiftKey) { - if (currentCol > 0) { - return { row: currentRow, col: currentCol - 1 } - } else if (currentRow > 0) { - return { row: currentRow - 1, col: maxCol } - } - return { row: 0, col: 0 } - } else { - if (currentCol < maxCol) { - return { row: currentRow, col: currentCol + 1 } - } else if (currentRow < maxRow) { - return { row: currentRow + 1, col: 0 } - } - return { row: maxRow, col: maxCol } - } - } - - expect(getNextTabCell(0, 5, 10, 5, false)).toEqual({ row: 1, col: 0 }) - expect(getNextTabCell(1, 0, 10, 5, true)).toEqual({ row: 0, col: 5 }) - }) - }) + describe('Focus Key Generation', function () { + it('should generate unique focus keys from row and column indices', function () { + function getFocusKey(row: number, col: number): string { + return `cell-${row}-${col}` + } + expect(getFocusKey(0, 0)).toBe('cell-0-0') + expect(getFocusKey(5, 10)).toBe('cell-5-10') + }) + }) + + describe('Keyboard Navigation', function () { + it('should calculate next cell in grid navigation', function () { + function getNextCell( + currentRow: number, + currentCol: number, + direction: 'up' | 'down' | 'left' | 'right', + maxRow: number, + maxCol: number + ) { + let nextRow = currentRow + let nextCol = currentCol + + switch (direction) { + case 'up': + nextRow = Math.max(0, currentRow - 1) + break + case 'down': + nextRow = Math.min(maxRow, currentRow + 1) + break + case 'left': + nextCol = Math.max(0, currentCol - 1) + break + case 'right': + nextCol = Math.min(maxCol, currentCol + 1) + break + } + + return { row: nextRow, col: nextCol } + } + + expect(getNextCell(5, 5, 'up', 10, 10)).toEqual({ row: 4, col: 5 }) + expect(getNextCell(5, 5, 'down', 10, 10)).toEqual({ row: 6, col: 5 }) + expect(getNextCell(5, 5, 'left', 10, 10)).toEqual({ row: 5, col: 4 }) + expect(getNextCell(5, 5, 'right', 10, 10)).toEqual({ row: 5, col: 6 }) + }) + + it('should clamp navigation at boundaries', function () { + function getNextCell( + currentRow: number, + currentCol: number, + direction: 'up' | 'down' | 'left' | 'right', + maxRow: number, + maxCol: number + ) { + let nextRow = currentRow + let nextCol = currentCol + + switch (direction) { + case 'up': + nextRow = Math.max(0, currentRow - 1) + break + case 'down': + nextRow = Math.min(maxRow, currentRow + 1) + break + case 'left': + nextCol = Math.max(0, currentCol - 1) + break + case 'right': + nextCol = Math.min(maxCol, currentCol + 1) + break + } + + return { row: nextRow, col: nextCol } + } + + expect(getNextCell(0, 0, 'up', 10, 10)).toEqual({ row: 0, col: 0 }) + expect(getNextCell(0, 0, 'left', 10, 10)).toEqual({ row: 0, col: 0 }) + expect(getNextCell(10, 10, 'down', 10, 10)).toEqual({ row: 10, col: 10 }) + expect(getNextCell(10, 10, 'right', 10, 10)).toEqual({ row: 10, col: 10 }) + }) + }) + + describe('Tab Navigation', function () { + it('should wrap tab navigation to next row', function () { + function getNextTabCell( + currentRow: number, + currentCol: number, + maxRow: number, + maxCol: number, + shiftKey: boolean + ) { + if (shiftKey) { + if (currentCol > 0) { + return { row: currentRow, col: currentCol - 1 } + } else if (currentRow > 0) { + return { row: currentRow - 1, col: maxCol } + } + return { row: 0, col: 0 } + } else { + if (currentCol < maxCol) { + return { row: currentRow, col: currentCol + 1 } + } else if (currentRow < maxRow) { + return { row: currentRow + 1, col: 0 } + } + return { row: maxRow, col: maxCol } + } + } + + expect(getNextTabCell(0, 5, 10, 5, false)).toEqual({ row: 1, col: 0 }) + expect(getNextTabCell(1, 0, 10, 5, true)).toEqual({ row: 0, col: 5 }) + }) + }) }) describe('Feature Gating', function () { - it('should log feature_gated events', function () { - const logs: string[] = [] - function logFeatureGated(feature: string) { - logs.push(`[feature_gated] ${feature}`) - } - - logFeatureGated('Import CSV') - logFeatureGated('Create Table UI') - - expect(logs).toContain('[feature_gated] Import CSV') - expect(logs).toContain('[feature_gated] Create Table UI') - }) - - it('should generate correct tooltip text for disabled features', function () { - function getTooltipText(feature: string, disabled: boolean): string { - return disabled ? `${feature} (Coming Soon)` : feature - } - - expect(getTooltipText('Import CSV', true)).toBe('Import CSV (Coming Soon)') - expect(getTooltipText('SQL Console', false)).toBe('SQL Console') - }) + it('should log feature_gated events', function () { + const logs: string[] = [] + function logFeatureGated(feature: string) { + logs.push(`[feature_gated] ${feature}`) + } + + logFeatureGated('Import CSV') + logFeatureGated('Create Table UI') + + expect(logs).toContain('[feature_gated] Import CSV') + expect(logs).toContain('[feature_gated] Create Table UI') + }) + + it('should generate correct tooltip text for disabled features', function () { + function getTooltipText(feature: string, disabled: boolean): string { + return disabled ? `${feature} (Coming Soon)` : feature + } + + expect(getTooltipText('Import CSV', true)).toBe('Import CSV (Coming Soon)') + expect(getTooltipText('SQL Console', false)).toBe('SQL Console') + }) }) describe('Loading State Utilities', function () { - it('should determine loading skeleton rows based on viewport', function () { - function getSkeletonRows(viewportHeight: number, rowHeight: number = 40): number { - return Math.max(5, Math.ceil(viewportHeight / rowHeight)) - } - - expect(getSkeletonRows(400)).toBe(10) - expect(getSkeletonRows(100)).toBe(5) - }) + it('should determine loading skeleton rows based on viewport', function () { + function getSkeletonRows(viewportHeight: number, rowHeight: number = 40): number { + return Math.max(5, Math.ceil(viewportHeight / rowHeight)) + } + + expect(getSkeletonRows(400)).toBe(10) + expect(getSkeletonRows(100)).toBe(5) + }) }) describe('Error State Formatting', function () { - it('should format error messages for display', function () { - function formatError(error: unknown): string { - if (error instanceof Error) return error.message - if (typeof error === 'string') return error - return 'An unknown error occurred' - } - - expect(formatError(new Error('Connection failed'))).toBe('Connection failed') - expect(formatError('Something went wrong')).toBe('Something went wrong') - expect(formatError({ code: 500 })).toBe('An unknown error occurred') - }) - - it('should provide retry action text', function () { - function getRetryText(hasRetryAction: boolean): string { - return hasRetryAction ? 'Try again' : '' - } - - expect(getRetryText(true)).toBe('Try again') - expect(getRetryText(false)).toBe('') - }) + it('should format error messages for display', function () { + function formatError(error: unknown): string { + if (error instanceof Error) return error.message + if (typeof error === 'string') return error + return 'An unknown error occurred' + } + + expect(formatError(new Error('Connection failed'))).toBe('Connection failed') + expect(formatError('Something went wrong')).toBe('Something went wrong') + expect(formatError({ code: 500 })).toBe('An unknown error occurred') + }) + + it('should provide retry action text', function () { + function getRetryText(hasRetryAction: boolean): string { + return hasRetryAction ? 'Try again' : '' + } + + expect(getRetryText(true)).toBe('Try again') + expect(getRetryText(false)).toBe('') + }) }) diff --git a/__tests__/apps/desktop/unit/database-studio.test.ts b/__tests__/apps/desktop/unit/database-studio.test.ts index f333b480..629a9ad9 100644 --- a/__tests__/apps/desktop/unit/database-studio.test.ts +++ b/__tests__/apps/desktop/unit/database-studio.test.ts @@ -1,165 +1,186 @@ import { describe, it, expect } from 'vitest' describe('Export Utilities', function () { - describe('CSV export formatting', function () { - it('should format single value correctly', function () { - const value = 'Hello World' - const escaped = value.includes(',') || value.includes('"') || value.includes('\n') - ? `"${value.replace(/"/g, '""')}"` - : value - expect(escaped).toBe('Hello World') - }) - - it('should escape values containing commas', function () { - const value = 'Hello, World' - const escaped = value.includes(',') || value.includes('"') || value.includes('\n') - ? `"${value.replace(/"/g, '""')}"` - : value - expect(escaped).toBe('"Hello, World"') - }) - - it('should escape values containing double quotes', function () { - const value = 'He said "Hello"' - const escaped = value.includes(',') || value.includes('"') || value.includes('\n') - ? `"${value.replace(/"/g, '""')}"` - : value - expect(escaped).toBe('"He said ""Hello"""') - }) - - it('should escape values containing newlines', function () { - const value = 'Line1\nLine2' - const escaped = value.includes(',') || value.includes('"') || value.includes('\n') - ? `"${value.replace(/"/g, '""')}"` - : value - expect(escaped).toBe('"Line1\nLine2"') - }) - - it('should handle null values', function () { - const value = null - const escaped = value === null ? '' : String(value) - expect(escaped).toBe('') - }) - }) - - describe('SQL INSERT formatting', function () { - it('should escape single quotes in string values', function () { - const value = "O'Brien" - const escaped = `'${String(value).replace(/'/g, "''")}'` - expect(escaped).toBe("'O''Brien'") - }) - - it('should format numbers without quotes', function () { - const value = 42 - const formatted = typeof value === 'number' ? String(value) : `'${String(value)}'` - expect(formatted).toBe('42') - }) - - it('should format NULL correctly', function () { - const value = null - const formatted = value === null ? 'NULL' : String(value) - expect(formatted).toBe('NULL') - }) - - it('should generate valid INSERT statement', function () { - const tableName = 'users' - const columns = ['id', 'name', 'email'] - const values = [1, "John", "john@example.com"] - - function formatValue(v: unknown): string { - if (v === null) return 'NULL' - if (typeof v === 'number') return String(v) - return `'${String(v).replace(/'/g, "''")}'` - } - - const insert = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${values.map(formatValue).join(', ')});` - expect(insert).toBe("INSERT INTO users (id, name, email) VALUES (1, 'John', 'john@example.com');") - }) - }) + describe('CSV export formatting', function () { + it('should format single value correctly', function () { + const value = 'Hello World' + const escaped = + value.includes(',') || value.includes('"') || value.includes('\n') + ? `"${value.replace(/"/g, '""')}"` + : value + expect(escaped).toBe('Hello World') + }) + + it('should escape values containing commas', function () { + const value = 'Hello, World' + const escaped = + value.includes(',') || value.includes('"') || value.includes('\n') + ? `"${value.replace(/"/g, '""')}"` + : value + expect(escaped).toBe('"Hello, World"') + }) + + it('should escape values containing double quotes', function () { + const value = 'He said "Hello"' + const escaped = + value.includes(',') || value.includes('"') || value.includes('\n') + ? `"${value.replace(/"/g, '""')}"` + : value + expect(escaped).toBe('"He said ""Hello"""') + }) + + it('should escape values containing newlines', function () { + const value = 'Line1\nLine2' + const escaped = + value.includes(',') || value.includes('"') || value.includes('\n') + ? `"${value.replace(/"/g, '""')}"` + : value + expect(escaped).toBe('"Line1\nLine2"') + }) + + it('should handle null values', function () { + const value = null + const escaped = value === null ? '' : String(value) + expect(escaped).toBe('') + }) + }) + + describe('SQL INSERT formatting', function () { + it('should escape single quotes in string values', function () { + const value = "O'Brien" + const escaped = `'${String(value).replace(/'/g, "''")}'` + expect(escaped).toBe("'O''Brien'") + }) + + it('should format numbers without quotes', function () { + const value = 42 + const formatted = typeof value === 'number' ? String(value) : `'${String(value)}'` + expect(formatted).toBe('42') + }) + + it('should format NULL correctly', function () { + const value = null + const formatted = value === null ? 'NULL' : String(value) + expect(formatted).toBe('NULL') + }) + + it('should generate valid INSERT statement', function () { + const tableName = 'users' + const columns = ['id', 'name', 'email'] + const values = [1, 'John', 'john@example.com'] + + function formatValue(v: unknown): string { + if (v === null) return 'NULL' + if (typeof v === 'number') return String(v) + return `'${String(v).replace(/'/g, "''")}'` + } + + const insert = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${values.map(formatValue).join(', ')});` + expect(insert).toBe( + "INSERT INTO users (id, name, email) VALUES (1, 'John', 'john@example.com');" + ) + }) + }) }) describe('Cell Selection Utilities', function () { - function getCellKey(row: number, col: number): string { - return `${row}:${col}` - } - - function getCellsInRectangle(start: { row: number; col: number }, end: { row: number; col: number }): Set { - const minRow = Math.min(start.row, end.row) - const maxRow = Math.max(start.row, end.row) - const minCol = Math.min(start.col, end.col) - const maxCol = Math.max(start.col, end.col) - - const cells = new Set() - for (let r = minRow; r <= maxRow; r++) { - for (let c = minCol; c <= maxCol; c++) { - cells.add(getCellKey(r, c)) - } - } - return cells - } - - it('should generate correct cell key', function () { - expect(getCellKey(0, 0)).toBe('0:0') - expect(getCellKey(5, 10)).toBe('5:10') - }) - - it('should select single cell', function () { - const cells = getCellsInRectangle({ row: 2, col: 3 }, { row: 2, col: 3 }) - expect(cells.size).toBe(1) - expect(cells.has('2:3')).toBe(true) - }) - - it('should select rectangular range', function () { - const cells = getCellsInRectangle({ row: 0, col: 0 }, { row: 2, col: 2 }) - expect(cells.size).toBe(9) - expect(cells.has('0:0')).toBe(true) - expect(cells.has('1:1')).toBe(true) - expect(cells.has('2:2')).toBe(true) - }) - - it('should handle inverted selection (end before start)', function () { - const cells = getCellsInRectangle({ row: 2, col: 2 }, { row: 0, col: 0 }) - expect(cells.size).toBe(9) - }) + function getCellKey(row: number, col: number): string { + return `${row}:${col}` + } + + function getCellsInRectangle( + start: { row: number; col: number }, + end: { row: number; col: number } + ): Set { + const minRow = Math.min(start.row, end.row) + const maxRow = Math.max(start.row, end.row) + const minCol = Math.min(start.col, end.col) + const maxCol = Math.max(start.col, end.col) + + const cells = new Set() + for (let r = minRow; r <= maxRow; r++) { + for (let c = minCol; c <= maxCol; c++) { + cells.add(getCellKey(r, c)) + } + } + return cells + } + + it('should generate correct cell key', function () { + expect(getCellKey(0, 0)).toBe('0:0') + expect(getCellKey(5, 10)).toBe('5:10') + }) + + it('should select single cell', function () { + const cells = getCellsInRectangle({ row: 2, col: 3 }, { row: 2, col: 3 }) + expect(cells.size).toBe(1) + expect(cells.has('2:3')).toBe(true) + }) + + it('should select rectangular range', function () { + const cells = getCellsInRectangle({ row: 0, col: 0 }, { row: 2, col: 2 }) + expect(cells.size).toBe(9) + expect(cells.has('0:0')).toBe(true) + expect(cells.has('1:1')).toBe(true) + expect(cells.has('2:2')).toBe(true) + }) + + it('should handle inverted selection (end before start)', function () { + const cells = getCellsInRectangle({ row: 2, col: 2 }, { row: 0, col: 0 }) + expect(cells.size).toBe(9) + }) }) describe('Clipboard Formatting', function () { - it('should format cells as tab-separated values', function () { - const selectedCells = [ - { row: 0, col: 0, value: 'A1' }, - { row: 0, col: 1, value: 'B1' }, - { row: 1, col: 0, value: 'A2' }, - { row: 1, col: 1, value: 'B2' } - ] - - const minRow = 0 - const maxRow = 1 - const rowData: string[][] = [] - - for (let r = minRow; r <= maxRow; r++) { - const rowCells = selectedCells.filter(function (c) { return c.row === r }) - rowCells.sort(function (a, b) { return a.col - b.col }) - rowData.push(rowCells.map(function (c) { return String(c.value) })) - } - - const clipboardText = rowData.map(function (r) { return r.join('\t') }).join('\n') - expect(clipboardText).toBe('A1\tB1\nA2\tB2') - }) - - it('should handle null values as empty strings', function () { - const value = null - const text = value === null || value === undefined ? '' : String(value) - expect(text).toBe('') - }) - - it('should parse tab-separated clipboard data', function () { - const clipboardText = 'A1\tB1\nA2\tB2' - const rows = clipboardText.split('\n').map(function (line) { - return line.split('\t') - }) - - expect(rows).toEqual([ - ['A1', 'B1'], - ['A2', 'B2'] - ]) - }) + it('should format cells as tab-separated values', function () { + const selectedCells = [ + { row: 0, col: 0, value: 'A1' }, + { row: 0, col: 1, value: 'B1' }, + { row: 1, col: 0, value: 'A2' }, + { row: 1, col: 1, value: 'B2' } + ] + + const minRow = 0 + const maxRow = 1 + const rowData: string[][] = [] + + for (let r = minRow; r <= maxRow; r++) { + const rowCells = selectedCells.filter(function (c) { + return c.row === r + }) + rowCells.sort(function (a, b) { + return a.col - b.col + }) + rowData.push( + rowCells.map(function (c) { + return String(c.value) + }) + ) + } + + const clipboardText = rowData + .map(function (r) { + return r.join('\t') + }) + .join('\n') + expect(clipboardText).toBe('A1\tB1\nA2\tB2') + }) + + it('should handle null values as empty strings', function () { + const value = null + const text = value === null || value === undefined ? '' : String(value) + expect(text).toBe('') + }) + + it('should parse tab-separated clipboard data', function () { + const clipboardText = 'A1\tB1\nA2\tB2' + const rows = clipboardText.split('\n').map(function (line) { + return line.split('\t') + }) + + expect(rows).toEqual([ + ['A1', 'B1'], + ['A2', 'B2'] + ]) + }) }) diff --git a/__tests__/vitest.setup.ts b/__tests__/vitest.setup.ts index a9e5873b..5472f719 100644 --- a/__tests__/vitest.setup.ts +++ b/__tests__/vitest.setup.ts @@ -1,5 +1,6 @@ -import { cleanup } from "@testing-library/react"; -import { expect, afterEach } from "vitest"; +import { cleanup } from '@testing-library/react' +import '@testing-library/jest-dom/vitest' +import { expect, afterEach } from 'vitest' // Cleanup after each test afterEach(() => { diff --git a/apps/desktop/RELEASE_TASKS.md b/apps/desktop/RELEASE_TASKS.md index 868f6e95..1886eac8 100644 --- a/apps/desktop/RELEASE_TASKS.md +++ b/apps/desktop/RELEASE_TASKS.md @@ -23,18 +23,18 @@ This document outlines the critical features and improvements required to make D **Specification:** 1. **UI Update:** - - Add an "SSH Tunnel" toggle or tab in the `Postgres` form section. - - Fields needed: - - `SSH Host` (string, required if enabled) - - `SSH Port` (number, default 22) + - Add an "SSH Tunnel" toggle or tab in the `Postgres` form section. + - Fields needed: + - `SSH Host` (string, required if enabled) + - `SSH Port` (number, default 22) - `SSH Username` (string, required) - `Authentication Method`: "Password" or "Key File". - `SSH Password / Passphrase` (password input). - `Private Key Path` (file picker). 2. **Implementation:** - - Update `formData` state to include `sshConfig`. - - Map fields to the `SshConfig` type defined in `bindings.ts`. - - Pass this populated object in `handleTestConnection` and persist it through `onSave`. + - Update `formData` state to include `sshConfig`. + - Map fields to the `SshConfig` type defined in `bindings.ts`. + - Pass this populated object in `handleTestConnection` and persist it through `onSave`. ### 2. Schema Management (DDL) diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json deleted file mode 100644 index e32f39c9..00000000 --- a/apps/desktop/package-lock.json +++ /dev/null @@ -1,6676 +0,0 @@ -{ - "name": "@dora/desktop", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@dora/desktop", - "version": "0.0.1", - "dependencies": { - "@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.11", - "@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", - "@radix-ui/react-slider": "^1.3.5", - "@radix-ui/react-slot": "^1.2.3", - "@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.8.0", - "@tauri-apps/plugin-dialog": "^2.0.0", - "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", - "input-otp": "^1.4.2", - "lucide-react": "^0.462.0", - "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" - }, - "devDependencies": { - "@eslint/js": "^9.32.0", - "@tailwindcss/typography": "^0.5.16", - "@tauri-apps/cli": "^2.8.0", - "@types/node": "^22.16.5", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react-swc": "^3.11.0", - "autoprefixer": "^10.4.21", - "eslint": "^9.32.0", - "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.4.20", - "globals": "^15.15.0", - "postcss": "^8.5.6", - "tailwindcss": "^3.4.17", - "typescript": "^5.8.3", - "typescript-eslint": "^8.38.0", - "vite": "^6.0.0" - } - }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "license": "MIT", - "dependencies": { - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "license": "MIT", - "dependencies": { - "@floating-ui/core": "^1.7.3", - "@floating-ui/utils": "^0.2.10" - } - }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@floating-ui/utils": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", - "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "license": "MIT" - }, - "node_modules/@hookform/resolvers": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", - "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", - "license": "MIT", - "peerDependencies": { - "react-hook-form": "^7.0.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@monaco-editor/loader": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz", - "integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==", - "license": "MIT", - "dependencies": { - "state-local": "^1.0.6" - } - }, - "node_modules/@monaco-editor/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz", - "integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==", - "license": "MIT", - "dependencies": { - "@monaco-editor/loader": "^1.5.0" - }, - "peerDependencies": { - "monaco-editor": ">= 0.25.0 < 1", - "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" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@radix-ui/number": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", - "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", - "license": "MIT" - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.15.tgz", - "integrity": "sha512-oTVLkEw5GpdRe29BqJ0LSDFWI3qu0vR1M0mUkOQWDIUnY/QIkLpgDMWuKxP94c2NAC2LGcgVhG1ImF3jkZ5wXw==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-aspect-ratio": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-aspect-ratio/-/react-aspect-ratio-1.1.8.tgz", - "integrity": "sha512-5nZrJTF7gH+e0nZS7/QxFz6tJV4VimhQb1avEgtsJxvvIp5JilL+c58HICsKzPxghdwaDt48hEfPM1au4zGy+w==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-aspect-ratio/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", - "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-avatar/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-checkbox": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", - "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@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-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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context-menu": { - "version": "2.2.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", - "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-menu": "2.1.16", - "@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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "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-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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", - "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-menu": "2.1.16", - "@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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", - "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menubar": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menubar/-/react-menubar-1.1.16.tgz", - "integrity": "sha512-EB1FktTz5xRRi2Er974AUQZWg2yVBb1yjip38/lgwtCVRd3a+maUoGHN/xs9Yv8SY8QwbSEb+YrxGadVWbEutA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-navigation-menu": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", - "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz", - "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", - "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-scroll-area": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", - "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", - "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@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-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.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", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slider": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.3.6.tgz", - "integrity": "sha512-JPYb1GuM1bxfjMRlNLE+BcmBC8onfCi60Blk7OBqi2MLTFdS+8401U4uFjnwkOr49BLmXxLC6JHkvAsx5OJvHw==", - "license": "MIT", - "dependencies": { - "@radix-ui/number": "1.1.1", - "@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-primitive": "2.1.3", - "@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-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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-switch": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", - "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", - "license": "MIT", - "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-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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "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" - }, - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toast": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", - "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", - "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-toggle-group": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", - "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", - "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-is-hydrated": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", - "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", - "license": "MIT", - "dependencies": { - "use-sync-external-store": "^1.5.0" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-visually-hidden": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", - "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", - "license": "MIT", - "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" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, - "node_modules/@remix-run/router": { - "version": "1.23.1", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.1.tgz", - "integrity": "sha512-vDbaOzF7yT2Qs4vO6XV1MHcJv+3dgR1sT+l3B8xxOVhUC336prMvqrvsLL/9Dnw2xr6Qhz4J0dmS0llNAbnUmQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz", - "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz", - "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz", - "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz", - "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz", - "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz", - "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz", - "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz", - "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz", - "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz", - "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz", - "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz", - "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz", - "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz", - "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz", - "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz", - "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz", - "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz", - "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz", - "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz", - "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz", - "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz", - "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@swc/core": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.8.tgz", - "integrity": "sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3", - "@swc/types": "^0.1.25" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/swc" - }, - "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.8", - "@swc/core-darwin-x64": "1.15.8", - "@swc/core-linux-arm-gnueabihf": "1.15.8", - "@swc/core-linux-arm64-gnu": "1.15.8", - "@swc/core-linux-arm64-musl": "1.15.8", - "@swc/core-linux-x64-gnu": "1.15.8", - "@swc/core-linux-x64-musl": "1.15.8", - "@swc/core-win32-arm64-msvc": "1.15.8", - "@swc/core-win32-ia32-msvc": "1.15.8", - "@swc/core-win32-x64-msvc": "1.15.8" - }, - "peerDependencies": { - "@swc/helpers": ">=0.5.17" - }, - "peerDependenciesMeta": { - "@swc/helpers": { - "optional": true - } - } - }, - "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.8.tgz", - "integrity": "sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-darwin-x64": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.8.tgz", - "integrity": "sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.8.tgz", - "integrity": "sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.8.tgz", - "integrity": "sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.8.tgz", - "integrity": "sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.8.tgz", - "integrity": "sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.8.tgz", - "integrity": "sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.8.tgz", - "integrity": "sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.8.tgz", - "integrity": "sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.8", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.8.tgz", - "integrity": "sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 AND MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=10" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@swc/counter": "^0.1.3" - } - }, - "node_modules/@tailwindcss/typography": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", - "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "6.0.10" - }, - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" - } - }, - "node_modules/@tanstack/query-core": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.16.tgz", - "integrity": "sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.90.16", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.16.tgz", - "integrity": "sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.90.16" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@tauri-apps/api": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.9.1.tgz", - "integrity": "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw==", - "license": "Apache-2.0 OR MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - } - }, - "node_modules/@tauri-apps/cli": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.9.6.tgz", - "integrity": "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw==", - "dev": true, - "license": "Apache-2.0 OR MIT", - "bin": { - "tauri": "tauri.js" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/tauri" - }, - "optionalDependencies": { - "@tauri-apps/cli-darwin-arm64": "2.9.6", - "@tauri-apps/cli-darwin-x64": "2.9.6", - "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", - "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", - "@tauri-apps/cli-linux-arm64-musl": "2.9.6", - "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", - "@tauri-apps/cli-linux-x64-gnu": "2.9.6", - "@tauri-apps/cli-linux-x64-musl": "2.9.6", - "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", - "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", - "@tauri-apps/cli-win32-x64-msvc": "2.9.6" - } - }, - "node_modules/@tauri-apps/cli-darwin-arm64": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.9.6.tgz", - "integrity": "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-darwin-x64": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.9.6.tgz", - "integrity": "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.9.6.tgz", - "integrity": "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-gnu": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.9.6.tgz", - "integrity": "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-arm64-musl": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.9.6.tgz", - "integrity": "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.9.6.tgz", - "integrity": "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-gnu": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.9.6.tgz", - "integrity": "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-linux-x64-musl": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.9.6.tgz", - "integrity": "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-arm64-msvc": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.9.6.tgz", - "integrity": "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-ia32-msvc": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.9.6.tgz", - "integrity": "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/cli-win32-x64-msvc": { - "version": "2.9.6", - "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.9.6.tgz", - "integrity": "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "Apache-2.0 OR MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tauri-apps/plugin-dialog": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.4.2.tgz", - "integrity": "sha512-lNIn5CZuw8WZOn8zHzmFmDSzg5zfohWoa3mdULP0YFh/VogVdMVWZPcWSHlydsiJhRQYaTNSYKN7RmZKE2lCYQ==", - "license": "MIT OR Apache-2.0", - "dependencies": { - "@tauri-apps/api": "^2.8.0" - } - }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", - "license": "MIT", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", - "license": "MIT", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", - "license": "MIT", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.19.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", - "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "devOptional": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, - "license": "MIT", - "peer": true, - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", - "integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/type-utils": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.51.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", - "integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", - "integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.51.0", - "@typescript-eslint/types": "^8.51.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", - "integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", - "integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", - "integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/utils": "8.51.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", - "integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", - "integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.51.0", - "@typescript-eslint/tsconfig-utils": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/visitor-keys": "8.51.0", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", - "integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.51.0", - "@typescript-eslint/types": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", - "integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.51.0", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitejs/plugin-react-swc": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", - "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-beta.27", - "@swc/core": "^1.12.11" - }, - "peerDependencies": { - "vite": "^4 || ^5 || ^6 || ^7" - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", - "license": "MIT" - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.23", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", - "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.11", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", - "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase-css": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001762", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", - "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cmdk": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", - "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", - "license": "MIT", - "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" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssesc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", - "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", - "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "license": "ISC", - "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" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", - "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", - "license": "ISC", - "dependencies": { - "d3-path": "^3.1.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", - "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/date-fns": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", - "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kossnocorp" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, - "node_modules/didyoumean": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", - "license": "Apache-2.0" - }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", - "license": "MIT" - }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, - "node_modules/dompurify": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", - "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "dev": true, - "license": "ISC" - }, - "node_modules/embla-carousel": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz", - "integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==", - "license": "MIT", - "peer": true - }, - "node_modules/embla-carousel-react": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/embla-carousel-react/-/embla-carousel-react-8.6.0.tgz", - "integrity": "sha512-0/PjqU7geVmo6F734pmPqpyHqiM99olvyecY7zdweCw+6tKEXnrE90pBiBbMMU8s5tICemzpQ3hi5EpxzGW+JA==", - "license": "MIT", - "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" - } - }, - "node_modules/embla-carousel-reactive-utils": { - "version": "8.6.0", - "resolved": "https://registry.npmjs.org/embla-carousel-reactive-utils/-/embla-carousel-reactive-utils-8.6.0.tgz", - "integrity": "sha512-fMVUDUEx0/uIEDM0Mz3dHznDhfX+znCCDCeIophYb1QGVM7YThSWX+wz11zlYwWFOr74b4QLGg0hrGPJeG2s4A==", - "license": "MIT", - "peerDependencies": { - "embla-carousel": "8.6.0" - } - }, - "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-equals": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", - "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true, - "license": "ISC" - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/input-otp": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", - "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", - "license": "MIT", - "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" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "license": "MIT", - "peer": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lucide-react": { - "version": "0.462.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.462.0.tgz", - "integrity": "sha512-NTL7EbAao9IFtuSivSZgrAh4fZd09Lr+6MTkqIxuHaH2nnYiYIzXPo06cOxHg9wKLdj6LL8TByG4qpePqwgx/g==", - "license": "ISC", - "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" - } - }, - "node_modules/marked": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", - "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/monaco-editor": { - "version": "0.55.1", - "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz", - "integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==", - "license": "MIT", - "peer": true, - "dependencies": { - "dompurify": "3.2.7", - "marked": "14.0.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/next-themes": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.3.0.tgz", - "integrity": "sha512-/QHIrsYpd6Kfk7xakK4svpDI5mmXP0gfvCoJdGpZQ2TOrQZmsW0QxjaiLn8wbIKjtm4BTSqLoix4lxYYOnLJ/w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" - } - }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-import": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.0.0", - "read-cache": "^1.0.0", - "resolve": "^1.1.7" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.0.0" - } - }, - "node_modules/postcss-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "camelcase-css": "^2.0.1" - }, - "engines": { - "node": "^12 || ^14 || >= 16" - }, - "peerDependencies": { - "postcss": "^8.4.21" - } - }, - "node_modules/postcss-load-config": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "lilconfig": "^3.1.1" - }, - "engines": { - "node": ">= 18" - }, - "peerDependencies": { - "jiti": ">=1.21.0", - "postcss": ">=8.0.9", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - }, - "postcss": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/postcss-nested": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.1.1" - }, - "engines": { - "node": ">=12.0" - }, - "peerDependencies": { - "postcss": "^8.2.14" - } - }, - "node_modules/postcss-nested/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-day-picker": { - "version": "8.10.1", - "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-8.10.1.tgz", - "integrity": "sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==", - "license": "MIT", - "funding": { - "type": "individual", - "url": "https://github.com/sponsors/gpbl" - }, - "peerDependencies": { - "date-fns": "^2.28.0 || ^3.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/react-hook-form": { - "version": "7.69.0", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.69.0.tgz", - "integrity": "sha512-yt6ZGME9f4F6WHwevrvpAjh42HMvocuSnSIHUGycBqXIJdhqGSPQzTpGF+1NLREk/58IdPxEMfPcFCjlMhclGw==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-hook-form" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17 || ^18 || ^19" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "license": "MIT" - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-resizable-panels": { - "version": "2.1.9", - "resolved": "https://registry.npmjs.org/react-resizable-panels/-/react-resizable-panels-2.1.9.tgz", - "integrity": "sha512-z77+X08YDIrgAes4jl8xhnUu1LNIRp4+E7cv4xHmLOxxUPO/ML7PSrE813b90vj7xvQ1lcf7g2uA9GeMZonjhQ==", - "license": "MIT", - "peerDependencies": { - "react": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/react-router": { - "version": "6.30.2", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.2.tgz", - "integrity": "sha512-H2Bm38Zu1bm8KUE5NVWRMzuIyAV8p/JrOaBJAwVmp37AXG72+CZJlEBw6pdn9i5TBgLMhNDgijS4ZlblpHyWTA==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.30.2", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.2.tgz", - "integrity": "sha512-l2OwHn3UUnEVUqc6/1VMmR1cvZryZ3j3NzapC2eUXO1dB0sYp5mvwdjiXhpUbRb21eFow3qSxpP8Yv6oAU824Q==", - "license": "MIT", - "dependencies": { - "@remix-run/router": "1.23.1", - "react-router": "6.30.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, - "node_modules/react-smooth": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", - "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", - "license": "MIT", - "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" - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "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" - } - }, - "node_modules/read-cache": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", - "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", - "license": "MIT", - "dependencies": { - "pify": "^2.3.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/recharts": { - "version": "2.15.4", - "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", - "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", - "license": "MIT", - "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" - }, - "engines": { - "node": ">=14" - }, - "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" - } - }, - "node_modules/recharts-scale": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", - "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", - "license": "MIT", - "dependencies": { - "decimal.js-light": "^2.4.1" - } - }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rollup": { - "version": "4.54.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz", - "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.54.0", - "@rollup/rollup-android-arm64": "4.54.0", - "@rollup/rollup-darwin-arm64": "4.54.0", - "@rollup/rollup-darwin-x64": "4.54.0", - "@rollup/rollup-freebsd-arm64": "4.54.0", - "@rollup/rollup-freebsd-x64": "4.54.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.54.0", - "@rollup/rollup-linux-arm-musleabihf": "4.54.0", - "@rollup/rollup-linux-arm64-gnu": "4.54.0", - "@rollup/rollup-linux-arm64-musl": "4.54.0", - "@rollup/rollup-linux-loong64-gnu": "4.54.0", - "@rollup/rollup-linux-ppc64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-gnu": "4.54.0", - "@rollup/rollup-linux-riscv64-musl": "4.54.0", - "@rollup/rollup-linux-s390x-gnu": "4.54.0", - "@rollup/rollup-linux-x64-gnu": "4.54.0", - "@rollup/rollup-linux-x64-musl": "4.54.0", - "@rollup/rollup-openharmony-arm64": "4.54.0", - "@rollup/rollup-win32-arm64-msvc": "4.54.0", - "@rollup/rollup-win32-ia32-msvc": "4.54.0", - "@rollup/rollup-win32-x64-gnu": "4.54.0", - "@rollup/rollup-win32-x64-msvc": "4.54.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sonner": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.4.tgz", - "integrity": "sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==", - "license": "MIT", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/state-local": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz", - "integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==", - "license": "MIT" - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "tinyglobby": "^0.2.11", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, - "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "@alloc/quick-lru": "^5.2.0", - "arg": "^5.0.2", - "chokidar": "^3.6.0", - "didyoumean": "^1.2.2", - "dlv": "^1.1.3", - "fast-glob": "^3.3.2", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "jiti": "^1.21.7", - "lilconfig": "^3.1.3", - "micromatch": "^4.0.8", - "normalize-path": "^3.0.0", - "object-hash": "^3.0.0", - "picocolors": "^1.1.1", - "postcss": "^8.4.47", - "postcss-import": "^15.1.0", - "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", - "postcss-nested": "^6.2.0", - "postcss-selector-parser": "^6.1.2", - "resolve": "^1.22.8", - "sucrase": "^3.35.0" - }, - "bin": { - "tailwind": "lib/cli.js", - "tailwindcss": "lib/cli.js" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", - "license": "MIT", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" - } - }, - "node_modules/tailwindcss/node_modules/postcss-selector-parser": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", - "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "license": "MIT" - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/ts-api-utils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", - "integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "license": "Apache-2.0" - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.51.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.51.0.tgz", - "integrity": "sha512-jh8ZuM5oEh2PSdyQG9YAEM1TCGuWenLSuSUhf/irbVUNW9O5FhbFVONviN2TgMTBnUmyHv7E56rYnfLZK6TkiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.51.0", - "@typescript-eslint/parser": "8.51.0", - "@typescript-eslint/typescript-estree": "8.51.0", - "@typescript-eslint/utils": "8.51.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", - "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/vaul": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/vaul/-/vaul-0.9.9.tgz", - "integrity": "sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.1" - }, - "peerDependencies": { - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0" - } - }, - "node_modules/victory-vendor": { - "version": "36.9.2", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", - "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", - "license": "MIT AND ISC", - "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" - } - }, - "node_modules/vite": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/apps/desktop/package.json b/apps/desktop/package.json index ee5f58f3..7c8c3ecf 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -1,87 +1,87 @@ { - "name": "@dora/desktop", - "version": "0.0.99", - "private": true, - "type": "module", - "scripts": { - "dev": "echo 'RECORD TOOL!!!' && bun vite --port 1420 --strictPort", - "build": "echo 'RECORD TOOL!!!' && vite build", - "build:dev": "vite build --mode development", - "lint": "bun run --cwd ../.. lint", - "preview": "vite preview", - "tauri": "tauri", - "tauri:dev": "echo 'RECORD TOOL!!!' && node -e \"require('child_process').execSync(process.platform==='win32'?'bun run tauri:win':'bun x tauri dev',{stdio:'inherit'})\"", - "tauri:win": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\tauri-dev-win.ps1", - "test": "vitest run -c ../../vitest.config.ts", - "test:watch": "vitest -c ../../vitest.config.ts", - "test:ui": "vitest --ui -c ../../vitest.config.ts", - "test:coverage": "vitest run --coverage -c ../../vitest.config.ts" - }, - "dependencies": { - "@faker-js/faker": "^10.2.0", - "@hookform/resolvers": "^3.10.0", - "@monaco-editor/react": "^4.6.0", - "@radix-ui/react-alert-dialog": "^1.1.14", - "@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-label": "^2.1.7", - "@radix-ui/react-popover": "^1.1.14", - "@radix-ui/react-scroll-area": "^1.2.9", - "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-separator": "^1.1.7", - "@radix-ui/react-slider": "^1.3.5", - "@radix-ui/react-slot": "^1.2.3", - "@radix-ui/react-switch": "^1.2.5", - "@radix-ui/react-tabs": "^1.1.12", - "@radix-ui/react-toast": "^1.2.14", - "@radix-ui/react-tooltip": "^1.2.7", - "@tanstack/react-query": "^5.83.0", - "@tauri-apps/api": "^2.9.1", - "@tauri-apps/plugin-dialog": "^2.6.0", - "@tauri-apps/plugin-fs": "^2.4.5", - "@tauri-apps/plugin-shell": "^2.3.4", - "@vercel/speed-insights": "^1.2.0", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^3.6.0", - "framer-motion": "^12.29.0", - "lucide-react": "^0.462.0", - "monaco-vim": "^0.4.4", - "next-themes": "^0.3.0", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-resizable-panels": "^2.1.9", - "react-router-dom": "^6.30.1", - "sonner": "^1.7.4", - "tailwind-merge": "^2.6.0", - "tailwindcss-animate": "^1.0.7", - "zod": "^4.3.6", - "zustand": "^5.0.10" - }, - "devDependencies": { - "@tailwindcss/postcss": "^4.1.18", - "@tailwindcss/typography": "^0.5.16", - "@tauri-apps/cli": "^2.9.6", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^22.16.5", - "@types/react": "^18.3.23", - "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react-swc": "^3.11.0", - "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", - "vitest": "^4.0.17" - }, - "overrides": { - "vite": "npm:rolldown-vite@latest" - } + "name": "@dora/desktop", + "version": "0.0.102", + "private": true, + "type": "module", + "scripts": { + "dev": "echo 'RECORD TOOL!!!' && bun vite --port 1420 --strictPort", + "build": "echo 'RECORD TOOL!!!' && vite build", + "build:dev": "vite build --mode development", + "lint": "bun run --cwd ../.. lint", + "preview": "vite preview", + "tauri": "tauri", + "tauri:dev": "echo 'RECORD TOOL!!!' && node -e \"require('child_process').execSync(process.platform==='win32'?'bun run tauri:win':'bun x tauri dev',{stdio:'inherit'})\"", + "tauri:win": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\tauri-dev-win.ps1", + "test": "vitest run -c ../../vitest.config.ts", + "test:watch": "vitest -c ../../vitest.config.ts", + "test:ui": "vitest --ui -c ../../vitest.config.ts", + "test:coverage": "vitest run --coverage -c ../../vitest.config.ts" + }, + "dependencies": { + "@faker-js/faker": "^10.2.0", + "@hookform/resolvers": "^3.10.0", + "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-alert-dialog": "^1.1.14", + "@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-label": "^2.1.7", + "@radix-ui/react-popover": "^1.1.14", + "@radix-ui/react-scroll-area": "^1.2.9", + "@radix-ui/react-select": "^2.2.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slider": "^1.3.5", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-switch": "^1.2.5", + "@radix-ui/react-tabs": "^1.1.12", + "@radix-ui/react-toast": "^1.2.14", + "@radix-ui/react-tooltip": "^1.2.7", + "@tanstack/react-query": "^5.83.0", + "@tauri-apps/api": "^2.9.1", + "@tauri-apps/plugin-dialog": "^2.6.0", + "@tauri-apps/plugin-fs": "^2.4.5", + "@tauri-apps/plugin-shell": "^2.3.4", + "@vercel/speed-insights": "^1.2.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^3.6.0", + "framer-motion": "^12.29.0", + "lucide-react": "^0.462.0", + "monaco-vim": "^0.4.4", + "next-themes": "^0.3.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-resizable-panels": "^2.1.9", + "react-router-dom": "^6.30.1", + "sonner": "^1.7.4", + "tailwind-merge": "^2.6.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^4.3.6", + "zustand": "^5.0.10" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "@tailwindcss/typography": "^0.5.16", + "@tauri-apps/cli": "^2.9.6", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^22.16.5", + "@types/react": "^18.3.23", + "@types/react-dom": "^18.3.7", + "@vitejs/plugin-react-swc": "^3.11.0", + "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", + "vitest": "^4.0.17" + }, + "overrides": { + "vite": "npm:rolldown-vite@latest" + } } diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml index 3f5606a5..9fbbe1ab 100644 --- a/apps/desktop/src-tauri/Cargo.toml +++ b/apps/desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "app" -version = "1.0.0" +version = "0.0.102" description = "" authors = ["Remco Stoeten"] license = "GPLv3" @@ -36,7 +36,15 @@ rustls-native-certs = "0.8.1" webpki-roots = "1.0.2" tokio-postgres-rustls = "0.13.0" dirs = "6.0.0" -rusqlite = { package = "libsql-rusqlite", version = "0.9.29", default-features = false, features = ["column_decltype", "functions", "hooks", "limits", "modern_sqlite", "trace", "libsql-experimental"] } +rusqlite = { package = "libsql-rusqlite", version = "0.9.29", default-features = false, features = [ + "column_decltype", + "functions", + "hooks", + "limits", + "modern_sqlite", + "trace", + "libsql-experimental" +] } chrono = { version = "0.4", features = ["serde"] } futures-util = "0.3.31" rustls-pemfile = "2.2.0" diff --git a/apps/desktop/src-tauri/splash.html b/apps/desktop/src-tauri/splash.html index 626ccf3f..89568200 100644 --- a/apps/desktop/src-tauri/splash.html +++ b/apps/desktop/src-tauri/splash.html @@ -40,7 +40,11 @@ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif; background: radial-gradient(1100px 520px at 20% -12%, var(--accent-soft), transparent 60%), - radial-gradient(900px 520px at 100% 100%, rgba(255, 255, 255, 0.03), transparent 68%), + radial-gradient( + 900px 520px at 100% 100%, + rgba(255, 255, 255, 0.03), + transparent 68% + ), linear-gradient(155deg, var(--bg), var(--bg-2)); color: var(--text); display: grid; @@ -142,7 +146,11 @@ display: grid; place-items: center; background: - radial-gradient(120% 120% at 20% 10%, rgba(255, 255, 255, 0.15), transparent 48%), + radial-gradient( + 120% 120% at 20% 10%, + rgba(255, 255, 255, 0.15), + transparent 48% + ), linear-gradient(165deg, var(--panel), var(--panel-2)); border: 1px solid var(--border); box-shadow: @@ -351,7 +359,7 @@

Dora

diff --git a/apps/desktop/src-tauri/splash_option.html b/apps/desktop/src-tauri/splash_option.html index a4735e47..367b6d9d 100644 --- a/apps/desktop/src-tauri/splash_option.html +++ b/apps/desktop/src-tauri/splash_option.html @@ -43,7 +43,11 @@ font-family: 'Inter', 'Segoe UI', system-ui, sans-serif; background: radial-gradient(1200px 520px at 14% -10%, var(--accent-soft), transparent 55%), - radial-gradient(900px 520px at 100% 100%, rgba(255, 255, 255, 0.03), transparent 64%), + radial-gradient( + 900px 520px at 100% 100%, + rgba(255, 255, 255, 0.03), + transparent 64% + ), linear-gradient(160deg, var(--bg) 0%, var(--bg-2) 100%); color: var(--text); user-select: none; @@ -130,7 +134,11 @@ padding: 7px; border-radius: 22px; border: 1px solid rgba(255, 255, 255, 0.12); - background: linear-gradient(160deg, rgba(255, 255, 255, 0.08), rgba(255, 255, 255, 0.01)); + background: linear-gradient( + 160deg, + rgba(255, 255, 255, 0.08), + rgba(255, 255, 255, 0.01) + ); box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.05) inset, 0 16px 40px -24px var(--glow); @@ -397,7 +405,9 @@

Dora

-

Loading your database workspace, tabs, and connection context.

+

+ Loading your database workspace, tabs, and connection context. +

diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 27d87b72..021ed7a0 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Dora", - "version": "0.0.97", + "version": "0.0.102", "identifier": "com.Dora.dev", "build": { "frontendDist": "../dist", @@ -31,14 +31,7 @@ }, "bundle": { "active": true, - "targets": [ - "deb", - "rpm", - "appimage", - "nsis", - "msi", - "dmg" - ], + "targets": ["deb", "rpm", "appimage", "nsis", "msi", "dmg"], "shortDescription": "A modern database client", "longDescription": "Dora is a modern, fast, and efficient database client for PostgreSQL, SQLite, and more.", "category": "Development", diff --git a/apps/desktop/src-tauri/vendor/libsql-sys/.cargo_vcs_info.json b/apps/desktop/src-tauri/vendor/libsql-sys/.cargo_vcs_info.json index e455d611..8beb3b4e 100644 --- a/apps/desktop/src-tauri/vendor/libsql-sys/.cargo_vcs_info.json +++ b/apps/desktop/src-tauri/vendor/libsql-sys/.cargo_vcs_info.json @@ -1,6 +1,6 @@ { - "git": { - "sha1": "6702c3da3ea5c8a348217b3db03acea73a5081b6" - }, - "path_in_vcs": "libsql-sys" -} \ No newline at end of file + "git": { + "sha1": "6702c3da3ea5c8a348217b3db03acea73a5081b6" + }, + "path_in_vcs": "libsql-sys" +} diff --git a/apps/desktop/src-tauri/vendor/libsql-sys/Cargo.toml b/apps/desktop/src-tauri/vendor/libsql-sys/Cargo.toml index f02f5512..adc78622 100644 --- a/apps/desktop/src-tauri/vendor/libsql-sys/Cargo.toml +++ b/apps/desktop/src-tauri/vendor/libsql-sys/Cargo.toml @@ -22,13 +22,7 @@ autotests = false autobenches = false description = "Native bindings to libSQL" readme = false -keywords = [ - "libsql", - "sqlite", - "ffi", - "bindings", - "database", -] +keywords = ["libsql", "sqlite", "ffi", "bindings", "database"] categories = ["external-ffi-bindings"] license = "MIT" repository = "https://github.com/tursodatabase/libsql" @@ -66,15 +60,15 @@ version = "1.18.0" [dependencies.rusqlite] version = "0.9.29" features = [ - "bundled", - "libsql-experimental", - "column_decltype", - "load_extension", - "modern_sqlite", - "functions", - "limits", - "hooks", - "trace", + "bundled", + "libsql-experimental", + "column_decltype", + "load_extension", + "modern_sqlite", + "functions", + "limits", + "hooks", + "trace" ] optional = true default-features = false diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index fa7be5e6..398c74dd 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,67 +1,61 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query" -import { BrowserRouter, Routes, Route } from "react-router-dom" -import { DemoBanner } from "@/components/demo-banner" -import { Toaster as Sonner } from "@/components/ui/sonner" -import { Toaster } from "@/components/ui/toaster" -import { DataProvider } from "@/core/data-provider" -import { PendingEditsProvider } from "@/core/pending-edits" -import { RecordingProvider, RecordingOverlay } from "@/core/recording" -import { SettingsProvider, useSettings } from "@/core/settings" -import { QueryHistoryProvider } from "@/features/sql-console/stores/query-history-store" -import { ThemeSync } from "@/features/sidebar/components/theme-sync" -import Index from "./pages/Index" -import NotFound from "./pages/NotFound" -import { Analytics } from "@vercel/analytics/react" +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { BrowserRouter, Routes, Route } from 'react-router-dom' +import { DemoBanner } from '@/components/demo-banner' +import { Toaster as Sonner } from '@/components/ui/sonner' +import { Toaster } from '@/components/ui/toaster' +import { DataProvider } from '@/core/data-provider' +import { PendingEditsProvider } from '@/core/pending-edits' +import { RecordingProvider, RecordingOverlay } from '@/core/recording' +import { SettingsProvider, useSettings } from '@/core/settings' +import { QueryHistoryProvider } from '@/features/sql-console/stores/query-history-store' +import { ThemeSync } from '@/features/sidebar/components/theme-sync' +import Index from './pages/Index' +import NotFound from './pages/NotFound' +import { Analytics } from '@vercel/analytics/react' const queryClient = new QueryClient() function GlobalToaster() { - const { settings } = useSettings() - if (settings.showToasts === false) return null - return ( - <> - - - - ) + const { settings } = useSettings() + if (settings.showToasts === false) return null + return ( + <> + + + + ) } function App() { - return ( - - - - - - - - -
- -
- - - - - } - /> - } - /> - - -
-
-
-
-
-
-
-
- ) + return ( + + + + + + + + +
+ +
+ + + + + } /> + } /> + + +
+
+
+
+
+
+
+
+ ) } export default App diff --git a/apps/desktop/src/components/demo-banner.tsx b/apps/desktop/src/components/demo-banner.tsx index b579ef69..7ccba67b 100644 --- a/apps/desktop/src/components/demo-banner.tsx +++ b/apps/desktop/src/components/demo-banner.tsx @@ -1,63 +1,63 @@ -import { X, Download } from "lucide-react" -import { useEffect, useState, useCallback } from "react" -import { useRecording } from "@/core/recording" -import { siteConfig } from "@/config/site" +import { X, Download } from 'lucide-react' +import { useEffect, useState, useCallback } from 'react' +import { useRecording } from '@/core/recording' +import { siteConfig } from '@/config/site' type Props = { - githubUrl?: string - onClose?: () => void - defaultVisible?: boolean + githubUrl?: string + onClose?: () => void + defaultVisible?: boolean } export function DemoBanner({ - githubUrl = siteConfig.links.releases, - onClose, - defaultVisible = true + githubUrl = siteConfig.links.releases, + onClose, + defaultVisible = true }: Props) { - const { shouldHide } = useRecording() - const [os, setOs] = useState("") - const [isVisible, setIsVisible] = useState(defaultVisible) - const [isDemo, setIsDemo] = useState(false) + const { shouldHide } = useRecording() + const [os, setOs] = useState('') + const [isVisible, setIsVisible] = useState(defaultVisible) + const [isDemo, setIsDemo] = useState(false) - useEffect(function detectDemoAndOs() { - const isTauri = typeof window !== "undefined" && "__TAURI__" in window - const isTauriProtocol = window.location.protocol === "tauri:" - const isWebDemo = - !isTauri && - !isTauriProtocol && - (import.meta.env.MODE === "demo" || - window.location.hostname.includes("demo") || - import.meta.env.VITE_IS_WEB === "true") + useEffect(function detectDemoAndOs() { + const isTauri = typeof window !== 'undefined' && '__TAURI__' in window + const isTauriProtocol = window.location.protocol === 'tauri:' + const isWebDemo = + !isTauri && + !isTauriProtocol && + (import.meta.env.MODE === 'demo' || + window.location.hostname.includes('demo') || + import.meta.env.VITE_IS_WEB === 'true') - setIsDemo(isWebDemo) + setIsDemo(isWebDemo) - if (isWebDemo) { - const userAgent = window.navigator.userAgent.toLowerCase() - if (userAgent.indexOf("win") !== -1) { - setOs("Windows") - } else if (userAgent.indexOf("mac") !== -1) { - setOs("macOS") - } else if (userAgent.indexOf("linux") !== -1) { - setOs("Linux") - } - } - }, []) + if (isWebDemo) { + const userAgent = window.navigator.userAgent.toLowerCase() + if (userAgent.indexOf('win') !== -1) { + setOs('Windows') + } else if (userAgent.indexOf('mac') !== -1) { + setOs('macOS') + } else if (userAgent.indexOf('linux') !== -1) { + setOs('Linux') + } + } + }, []) - const handleClose = useCallback( - function onCloseBanner() { - setIsVisible(false) - if (onClose) { - onClose() - } - }, - [onClose] - ) + const handleClose = useCallback( + function onCloseBanner() { + setIsVisible(false) + if (onClose) { + onClose() + } + }, + [onClose] + ) - if (!isVisible || !isDemo || shouldHide("hideDemoBanner")) return null + if (!isVisible || !isDemo || shouldHide('hideDemoBanner')) return null - return ( -
-
- - You're viewing the demo. - - -
+ ' + aria-label='Close banner' + > + + +
- - - - {os ? `Download for ${os}` : "Download"} - - -
- ) + + + + {os ? `Download for ${os}` : 'Download'} + + +
+ ) } diff --git a/apps/desktop/src/components/window-controls.tsx b/apps/desktop/src/components/window-controls.tsx index 2709986c..6a88d3c0 100644 --- a/apps/desktop/src/components/window-controls.tsx +++ b/apps/desktop/src/components/window-controls.tsx @@ -28,7 +28,7 @@ export function WindowControls({ className }: Props) { const appWindow = getCurrentWindow() const maximized = await appWindow.isMaximized() setIsMaximized(maximized) - } catch { } + } catch {} } checkMaximized() diff --git a/apps/desktop/src/core/data-generation/generator.ts b/apps/desktop/src/core/data-generation/generator.ts index 685d637a..bf5edd9c 100644 --- a/apps/desktop/src/core/data-generation/generator.ts +++ b/apps/desktop/src/core/data-generation/generator.ts @@ -1,37 +1,37 @@ import { guessFakerFunction } from './schema-analyzer' export type TableColumn = { - name: string - type: string - isNullable?: boolean - isPrimaryKey?: boolean + name: string + type: string + isNullable?: boolean + isPrimaryKey?: boolean } export type GeneratedRow = Record export function generateData(columns: TableColumn[], count: number): GeneratedRow[] { - const generatorMap = new Map any>() + const generatorMap = new Map any>() - // Pre-calculate generators for each column - columns.forEach(col => { - if (col.isPrimaryKey) return // Skip direct PK generation (often auto-increment or handled by DB, unless UUID) + // Pre-calculate generators for each column + columns.forEach((col) => { + if (col.isPrimaryKey) return // Skip direct PK generation (often auto-increment or handled by DB, unless UUID) - // Wait, for UUID PKs we MIGHT want to generate them if the DB doesn't auto-gen. - // For simplicity now, let's generate values for everything except likely auto-increments (generically named 'id' with type int) - // But for safety, let's create a map for all non-serial columns. + // Wait, for UUID PKs we MIGHT want to generate them if the DB doesn't auto-gen. + // For simplicity now, let's generate values for everything except likely auto-increments (generically named 'id' with type int) + // But for safety, let's create a map for all non-serial columns. - generatorMap.set(col.name, guessFakerFunction(col.name, col.type)) - }) + generatorMap.set(col.name, guessFakerFunction(col.name, col.type)) + }) - const rows: GeneratedRow[] = [] + const rows: GeneratedRow[] = [] - for (let i = 0; i < count; i++) { - const row: GeneratedRow = {} - generatorMap.forEach((fn, colName) => { - row[colName] = fn() - }) - rows.push(row) - } + for (let i = 0; i < count; i++) { + const row: GeneratedRow = {} + generatorMap.forEach((fn, colName) => { + row[colName] = fn() + }) + rows.push(row) + } - return rows + return rows } diff --git a/apps/desktop/src/core/data-generation/schema-analyzer.ts b/apps/desktop/src/core/data-generation/schema-analyzer.ts index b1b6f8ea..e685a1fb 100644 --- a/apps/desktop/src/core/data-generation/schema-analyzer.ts +++ b/apps/desktop/src/core/data-generation/schema-analyzer.ts @@ -1,44 +1,55 @@ import { faker } from '@faker-js/faker' -type ColumnType = 'string' | 'number' | 'integer' | 'boolean' | 'date' | 'timestamp' | 'uuid' | 'json' +type ColumnType = + | 'string' + | 'number' + | 'integer' + | 'boolean' + | 'date' + | 'timestamp' + | 'uuid' + | 'json' export function guessFakerFunction(columnName: string, columnType: string): () => any { - const name = columnName.toLowerCase() - const type = columnType.toLowerCase() + const name = columnName.toLowerCase() + const type = columnType.toLowerCase() - // 1. By exact name match (Highest priority) - if (name === 'email') return faker.internet.email - if (name === 'username') return faker.internet.username - if (name === 'first_name' || name === 'firstname') return faker.person.firstName - if (name === 'last_name' || name === 'lastname') return faker.person.lastName - if (name === 'full_name' || name === 'fullname' || name === 'name') return faker.person.fullName - if (name === 'phone' || name === 'phone_number') return faker.phone.number - if (name === 'address') return faker.location.streetAddress - if (name === 'city') return faker.location.city - if (name === 'zip' || name === 'zipcode' || name === 'postal_code') return faker.location.zipCode - if (name === 'country') return faker.location.country - if (name === 'company') return faker.company.name - if (name === 'description' || name === 'bio') return () => faker.lorem.sentences(2) - if (name === 'avatar' || name === 'image' || name === 'photo') return faker.image.avatar - if (name === 'url' || name === 'website') return faker.internet.url - if (name === 'password') return faker.internet.password + // 1. By exact name match (Highest priority) + if (name === 'email') return faker.internet.email + if (name === 'username') return faker.internet.username + if (name === 'first_name' || name === 'firstname') return faker.person.firstName + if (name === 'last_name' || name === 'lastname') return faker.person.lastName + if (name === 'full_name' || name === 'fullname' || name === 'name') return faker.person.fullName + if (name === 'phone' || name === 'phone_number') return faker.phone.number + if (name === 'address') return faker.location.streetAddress + if (name === 'city') return faker.location.city + if (name === 'zip' || name === 'zipcode' || name === 'postal_code') + return faker.location.zipCode + if (name === 'country') return faker.location.country + if (name === 'company') return faker.company.name + if (name === 'description' || name === 'bio') return () => faker.lorem.sentences(2) + if (name === 'avatar' || name === 'image' || name === 'photo') return faker.image.avatar + if (name === 'url' || name === 'website') return faker.internet.url + if (name === 'password') return faker.internet.password - // 2. By partial name match - if (name.includes('uuid') || name.includes('id')) return faker.string.uuid - if (name.includes('email')) return faker.internet.email - if (name.includes('name')) return faker.person.fullName - if (name.includes('price') || name.includes('amount') || name.includes('cost')) return () => Number(faker.commerce.price()) - if (name.includes('date') || name.includes('at')) return faker.date.past - if (name.includes('is_') || name.includes('has_')) return faker.datatype.boolean + // 2. By partial name match + if (name.includes('uuid') || name.includes('id')) return faker.string.uuid + if (name.includes('email')) return faker.internet.email + if (name.includes('name')) return faker.person.fullName + if (name.includes('price') || name.includes('amount') || name.includes('cost')) + return () => Number(faker.commerce.price()) + if (name.includes('date') || name.includes('at')) return faker.date.past + if (name.includes('is_') || name.includes('has_')) return faker.datatype.boolean - // 3. By Type - if (type.includes('int')) return () => faker.number.int({ min: 1, max: 1000 }) - if (type.includes('decimal') || type.includes('numeric') || type.includes('float')) return () => faker.number.float({ min: 0, max: 1000, fractionDigits: 2 }) - if (type.includes('bool')) return faker.datatype.boolean - if (type.includes('date') || type.includes('time')) return faker.date.past - if (type.includes('uuid')) return faker.string.uuid - if (type.includes('json')) return () => JSON.stringify({ foo: 'bar', baz: 123 }) + // 3. By Type + if (type.includes('int')) return () => faker.number.int({ min: 1, max: 1000 }) + if (type.includes('decimal') || type.includes('numeric') || type.includes('float')) + return () => faker.number.float({ min: 0, max: 1000, fractionDigits: 2 }) + if (type.includes('bool')) return faker.datatype.boolean + if (type.includes('date') || type.includes('time')) return faker.date.past + if (type.includes('uuid')) return faker.string.uuid + if (type.includes('json')) return () => JSON.stringify({ foo: 'bar', baz: 123 }) - // 4. Fallback - return faker.lorem.word + // 4. Fallback + return faker.lorem.word } diff --git a/apps/desktop/src/core/data-generation/sql-to-drizzle.ts b/apps/desktop/src/core/data-generation/sql-to-drizzle.ts index d16ea61a..30e1c880 100644 --- a/apps/desktop/src/core/data-generation/sql-to-drizzle.ts +++ b/apps/desktop/src/core/data-generation/sql-to-drizzle.ts @@ -1,209 +1,225 @@ import type { DatabaseSchema, TableInfo, ColumnInfo } from '@/lib/bindings' type DrizzleColumnType = - | 'integer' - | 'text' - | 'varchar' - | 'boolean' - | 'timestamp' - | 'serial' - | 'bigint' - | 'real' - | 'doublePrecision' - | 'json' - | 'jsonb' - | 'uuid' - | 'date' - | 'time' + | 'integer' + | 'text' + | 'varchar' + | 'boolean' + | 'timestamp' + | 'serial' + | 'bigint' + | 'real' + | 'doublePrecision' + | 'json' + | 'jsonb' + | 'uuid' + | 'date' + | 'time' function mapSqlTypeToDrizzle(sqlType: string): { type: DrizzleColumnType; args?: string } { - const normalized = sqlType.toLowerCase().trim() - - if (normalized.includes('serial')) { - return { type: 'serial' } - } - if (normalized.includes('bigint') || normalized.includes('int8')) { - return { type: 'bigint', args: "{ mode: 'number' }" } - } - if (normalized.includes('int') || normalized.includes('integer')) { - return { type: 'integer' } - } - if (normalized.includes('real') || normalized.includes('float4')) { - return { type: 'real' } - } - if (normalized.includes('double') || normalized.includes('float8') || normalized.includes('numeric') || normalized.includes('decimal')) { - return { type: 'doublePrecision' } - } - if (normalized.includes('bool')) { - return { type: 'boolean' } - } - if (normalized.includes('timestamp')) { - return { type: 'timestamp' } - } - if (normalized.includes('date')) { - return { type: 'date' } - } - if (normalized.includes('time')) { - return { type: 'time' } - } - if (normalized.includes('uuid')) { - return { type: 'uuid' } - } - if (normalized.includes('jsonb')) { - return { type: 'jsonb' } - } - if (normalized.includes('json')) { - return { type: 'json' } - } - if (normalized.includes('varchar') || normalized.includes('character varying')) { - const match = normalized.match(/\((\d+)\)/) - if (match) { - return { type: 'varchar', args: `{ length: ${match[1]} }` } - } - return { type: 'varchar', args: '{ length: 255 }' } - } - if (normalized.includes('char')) { - return { type: 'varchar', args: '{ length: 255 }' } - } - - return { type: 'text' } + const normalized = sqlType.toLowerCase().trim() + + if (normalized.includes('serial')) { + return { type: 'serial' } + } + if (normalized.includes('bigint') || normalized.includes('int8')) { + return { type: 'bigint', args: "{ mode: 'number' }" } + } + if (normalized.includes('int') || normalized.includes('integer')) { + return { type: 'integer' } + } + if (normalized.includes('real') || normalized.includes('float4')) { + return { type: 'real' } + } + if ( + normalized.includes('double') || + normalized.includes('float8') || + normalized.includes('numeric') || + normalized.includes('decimal') + ) { + return { type: 'doublePrecision' } + } + if (normalized.includes('bool')) { + return { type: 'boolean' } + } + if (normalized.includes('timestamp')) { + return { type: 'timestamp' } + } + if (normalized.includes('date')) { + return { type: 'date' } + } + if (normalized.includes('time')) { + return { type: 'time' } + } + if (normalized.includes('uuid')) { + return { type: 'uuid' } + } + if (normalized.includes('jsonb')) { + return { type: 'jsonb' } + } + if (normalized.includes('json')) { + return { type: 'json' } + } + if (normalized.includes('varchar') || normalized.includes('character varying')) { + const match = normalized.match(/\((\d+)\)/) + if (match) { + return { type: 'varchar', args: `{ length: ${match[1]} }` } + } + return { type: 'varchar', args: '{ length: 255 }' } + } + if (normalized.includes('char')) { + return { type: 'varchar', args: '{ length: 255 }' } + } + + return { type: 'text' } } function toSnakeCase(str: string): string { - return str - .replace(/([A-Z])/g, '_$1') - .toLowerCase() - .replace(/^_/, '') - .replace(/-/g, '_') + return str + .replace(/([A-Z])/g, '_$1') + .toLowerCase() + .replace(/^_/, '') + .replace(/-/g, '_') } function toCamelCase(str: string): string { - return str - .split('_') - .map(function (word, index) { - if (index === 0) return word.toLowerCase() - return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() - }) - .join('') + return str + .split('_') + .map(function (word, index) { + if (index === 0) return word.toLowerCase() + return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() + }) + .join('') } function generateColumnDefinition(col: ColumnInfo, tableName: string): string { - const { type, args } = mapSqlTypeToDrizzle(col.data_type) - const columnName = col.name - - let definition = `\t${toCamelCase(columnName)}: ${type}('${columnName}'` - - if (args) { - definition += `, ${args}` - } - - definition += ')' - - if (col.is_primary_key) { - definition += '.primaryKey()' - } - - if (!col.is_nullable && !col.is_primary_key) { - definition += '.notNull()' - } - - if (col.default_value) { - const defaultVal = col.default_value - if (defaultVal.toLowerCase().includes('now()') || defaultVal.toLowerCase().includes('current_timestamp')) { - definition += '.defaultNow()' - } else if (defaultVal.toLowerCase() === 'true' || defaultVal.toLowerCase() === 'false') { - definition += `.default(${defaultVal.toLowerCase()})` - } else if (!isNaN(Number(defaultVal))) { - definition += `.default(${defaultVal})` - } - } - - return definition + const { type, args } = mapSqlTypeToDrizzle(col.data_type) + const columnName = col.name + + let definition = `\t${toCamelCase(columnName)}: ${type}('${columnName}'` + + if (args) { + definition += `, ${args}` + } + + definition += ')' + + if (col.is_primary_key) { + definition += '.primaryKey()' + } + + if (!col.is_nullable && !col.is_primary_key) { + definition += '.notNull()' + } + + if (col.default_value) { + const defaultVal = col.default_value + if ( + defaultVal.toLowerCase().includes('now()') || + defaultVal.toLowerCase().includes('current_timestamp') + ) { + definition += '.defaultNow()' + } else if (defaultVal.toLowerCase() === 'true' || defaultVal.toLowerCase() === 'false') { + definition += `.default(${defaultVal.toLowerCase()})` + } else if (!isNaN(Number(defaultVal))) { + definition += `.default(${defaultVal})` + } + } + + return definition } function generateTableDefinition(table: TableInfo): string { - const tableName = table.name - const variableName = toCamelCase(tableName) + const tableName = table.name + const variableName = toCamelCase(tableName) - const columns = table.columns.map(function (col) { - return generateColumnDefinition(col, tableName) - }) + const columns = table.columns.map(function (col) { + return generateColumnDefinition(col, tableName) + }) - return `export const ${variableName} = pgTable('${tableName}', { + return `export const ${variableName} = pgTable('${tableName}', { ${columns.join(',\n')} })` } export function convertSchemaToDrizzle(schema: DatabaseSchema): string { - const header = `import { pgTable, serial, text, varchar, integer, boolean, timestamp, bigint, real, doublePrecision, json, jsonb, uuid, date, time } from 'drizzle-orm/pg-core'` + const header = `import { pgTable, serial, text, varchar, integer, boolean, timestamp, bigint, real, doublePrecision, json, jsonb, uuid, date, time } from 'drizzle-orm/pg-core'` - const tableDefinitions = schema.tables.map(function (table) { - return generateTableDefinition(table) - }) + const tableDefinitions = schema.tables.map(function (table) { + return generateTableDefinition(table) + }) - return `${header}\n\n${tableDefinitions.join('\n\n')}\n` + return `${header}\n\n${tableDefinitions.join('\n\n')}\n` } export function convertDDLToDrizzle(ddl: string, schema?: DatabaseSchema): string { - if (schema) { - return convertSchemaToDrizzle(schema) - } - - const header = `import { pgTable, serial, text, varchar, integer, boolean, timestamp, bigint, real, doublePrecision, json, jsonb, uuid, date, time } from 'drizzle-orm/pg-core'` - - const createTableRegex = /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?\s*\(([\s\S]*?)\);/gi - const tables: string[] = [] - - let match - while ((match = createTableRegex.exec(ddl)) !== null) { - const tableName = match[1] - const columnsBlock = match[2] - const variableName = toCamelCase(tableName) - - const columnLines = columnsBlock - .split(',') - .map(function (line) { return line.trim() }) - .filter(function (line) { - return line.length > 0 && - !line.toUpperCase().startsWith('PRIMARY KEY') && - !line.toUpperCase().startsWith('FOREIGN KEY') && - !line.toUpperCase().startsWith('CONSTRAINT') && - !line.toUpperCase().startsWith('UNIQUE') - }) - - const columns: string[] = [] - for (const line of columnLines) { - const parts = line.match(/["']?(\w+)["']?\s+(\w+(?:\([^)]*\))?)/i) - if (parts) { - const colName = parts[1] - const colType = parts[2] - const { type, args } = mapSqlTypeToDrizzle(colType) - - let colDef = `\t${toCamelCase(colName)}: ${type}('${colName}'` - if (args) { - colDef += `, ${args}` - } - colDef += ')' - - if (line.toUpperCase().includes('PRIMARY KEY')) { - colDef += '.primaryKey()' - } - if (line.toUpperCase().includes('NOT NULL') && !line.toUpperCase().includes('PRIMARY KEY')) { - colDef += '.notNull()' - } - - columns.push(colDef) - } - } - - tables.push(`export const ${variableName} = pgTable('${tableName}', { + if (schema) { + return convertSchemaToDrizzle(schema) + } + + const header = `import { pgTable, serial, text, varchar, integer, boolean, timestamp, bigint, real, doublePrecision, json, jsonb, uuid, date, time } from 'drizzle-orm/pg-core'` + + const createTableRegex = + /CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?["']?(\w+)["']?\s*\(([\s\S]*?)\);/gi + const tables: string[] = [] + + let match + while ((match = createTableRegex.exec(ddl)) !== null) { + const tableName = match[1] + const columnsBlock = match[2] + const variableName = toCamelCase(tableName) + + const columnLines = columnsBlock + .split(',') + .map(function (line) { + return line.trim() + }) + .filter(function (line) { + return ( + line.length > 0 && + !line.toUpperCase().startsWith('PRIMARY KEY') && + !line.toUpperCase().startsWith('FOREIGN KEY') && + !line.toUpperCase().startsWith('CONSTRAINT') && + !line.toUpperCase().startsWith('UNIQUE') + ) + }) + + const columns: string[] = [] + for (const line of columnLines) { + const parts = line.match(/["']?(\w+)["']?\s+(\w+(?:\([^)]*\))?)/i) + if (parts) { + const colName = parts[1] + const colType = parts[2] + const { type, args } = mapSqlTypeToDrizzle(colType) + + let colDef = `\t${toCamelCase(colName)}: ${type}('${colName}'` + if (args) { + colDef += `, ${args}` + } + colDef += ')' + + if (line.toUpperCase().includes('PRIMARY KEY')) { + colDef += '.primaryKey()' + } + if ( + line.toUpperCase().includes('NOT NULL') && + !line.toUpperCase().includes('PRIMARY KEY') + ) { + colDef += '.notNull()' + } + + columns.push(colDef) + } + } + + tables.push(`export const ${variableName} = pgTable('${tableName}', { ${columns.join(',\n')} })`) - } + } - if (tables.length === 0) { - return `// Could not parse DDL. Please provide a valid SQL schema.\n// Original DDL:\n// ${ddl.split('\n').join('\n// ')}` - } + if (tables.length === 0) { + return `// Could not parse DDL. Please provide a valid SQL schema.\n// Original DDL:\n// ${ddl.split('\n').join('\n// ')}` + } - return `${header}\n\n${tables.join('\n\n')}\n` + return `${header}\n\n${tables.join('\n\n')}\n` } diff --git a/apps/desktop/src/core/data-provider/adapters/mock.ts b/apps/desktop/src/core/data-provider/adapters/mock.ts index 9938623b..3c7eacde 100644 --- a/apps/desktop/src/core/data-provider/adapters/mock.ts +++ b/apps/desktop/src/core/data-provider/adapters/mock.ts @@ -52,7 +52,7 @@ function saveToStorage() { return } localStorage.setItem(STORAGE_KEY, JSON.stringify(store)) - } catch { } + } catch {} } function initializeStore() { @@ -306,16 +306,16 @@ CREATE TABLE posts ( const columns: ColumnDefinition[] = tableInfo ? tableInfo.columns.map(function (c) { - return { - name: c.name, - type: c.data_type, - nullable: c.is_nullable, - primaryKey: c.is_primary_key || false - } - }) + return { + name: c.name, + type: c.data_type, + nullable: c.is_nullable, + primaryKey: c.is_primary_key || false + } + }) : Object.keys(paged[0] || {}).map(function (k) { - return { name: k, type: 'unknown', nullable: true, primaryKey: k === 'id' } - }) + return { name: k, type: 'unknown', nullable: true, primaryKey: k === 'id' } + }) return ok({ columns, @@ -536,8 +536,8 @@ CREATE TABLE posts ( await randomDelay() const filtered = connectionId ? store.scripts.filter(function (s) { - return s.connection_id === connectionId - }) + return s.connection_id === connectionId + }) : store.scripts return ok(filtered) }, @@ -600,7 +600,6 @@ CREATE TABLE posts ( return ok(undefined) }, - async deleteScript(id: number): Promise> { await randomDelay() store.scripts = store.scripts.filter(function (s) { @@ -633,12 +632,11 @@ CREATE TABLE posts ( } } - export function resetMockStore() { try { if (typeof localStorage !== 'undefined') { localStorage.removeItem(STORAGE_KEY) } - } catch { } + } catch {} initializeStore() } diff --git a/apps/desktop/src/core/data-provider/adapters/tauri.ts b/apps/desktop/src/core/data-provider/adapters/tauri.ts index 6eba9adf..7296f33d 100644 --- a/apps/desktop/src/core/data-provider/adapters/tauri.ts +++ b/apps/desktop/src/core/data-provider/adapters/tauri.ts @@ -429,7 +429,6 @@ export function createTauriAdapter(): DataAdapter { return err(formatError(result.error)) }, - async updateScript( id: number, name: string, @@ -455,7 +454,6 @@ export function createTauriAdapter(): DataAdapter { return err(formatError(result.error)) }, - async deleteScript(id: number): Promise> { const result = await commands.deleteScript(id) if (result.status === 'ok') { @@ -499,7 +497,6 @@ export function createTauriAdapter(): DataAdapter { } } - function operatorToSql(op: string): string { const map: Record = { eq: '=', diff --git a/apps/desktop/src/core/data-provider/hooks.ts b/apps/desktop/src/core/data-provider/hooks.ts index d22bef14..eb0d8ab7 100644 --- a/apps/desktop/src/core/data-provider/hooks.ts +++ b/apps/desktop/src/core/data-provider/hooks.ts @@ -206,27 +206,34 @@ export function useDataMutation() { }, onMutate: async (newEdit) => { // Cancel any outgoing refetches (so they don't overwrite our optimistic update) - await queryClient.cancelQueries({ queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] }) + await queryClient.cancelQueries({ + queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] + }) // Snapshot the previous value - const previousTableData = queryClient.getQueriesData({ queryKey: ['tableData', newEdit.connectionId, newEdit.tableName] }) + 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 + 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 row + }) + } } - }) + ) // Return a context object with the snapshotted value return { previousTableData } diff --git a/apps/desktop/src/core/data-provider/types.ts b/apps/desktop/src/core/data-provider/types.ts index 1376cca3..05b90983 100644 --- a/apps/desktop/src/core/data-provider/types.ts +++ b/apps/desktop/src/core/data-provider/types.ts @@ -13,18 +13,17 @@ import { DatabaseInfo, SavedQuery, SnippetFolder - } from '@/lib/bindings' export type AdapterResult = | { - ok: true - data: T - } + ok: true + data: T + } | { - ok: false - error: string - } + ok: false + error: string + } export function getAdapterError(result: AdapterResult): string { return 'error' in result ? result.error : 'Unknown adapter error' @@ -125,15 +124,11 @@ export type DataAdapter = { // Snippet Folder Management getSnippetFolders(): Promise> - createSnippetFolder( - name: string, - parentId?: number | null - ): Promise> + createSnippetFolder(name: string, parentId?: number | null): Promise> updateSnippetFolder(id: number, name: string): Promise> deleteSnippetFolder(id: number): Promise> } - export type DataProviderContextValue = { adapter: DataAdapter isTauri: boolean diff --git a/apps/desktop/src/core/recording/recording-overlay.tsx b/apps/desktop/src/core/recording/recording-overlay.tsx index 12522d0c..f15d73c5 100644 --- a/apps/desktop/src/core/recording/recording-overlay.tsx +++ b/apps/desktop/src/core/recording/recording-overlay.tsx @@ -24,13 +24,7 @@ function clamp(value: number, min: number, max: number) { return Math.max(min, Math.min(max, value)) } -function cubicBezier( - t: number, - p0: number, - p1: number, - p2: number, - p3: number -): number { +function cubicBezier(t: number, p0: number, p1: number, p2: number, p3: number): number { const oneMinusT = 1 - t return ( oneMinusT * oneMinusT * oneMinusT * p0 + @@ -80,43 +74,40 @@ export function RecordingOverlay() { return true }, []) - const animateSegment = useCallback( - async function animateSegment( - start: { x: number; y: number }, - end: { x: number; y: number }, - token: number - ) { - const dx = end.x - start.x - const dy = end.y - start.y - const distance = Math.hypot(dx, dy) - const durationMs = clamp(distance * 2.4, 300, 1100) - const angle = Math.atan2(dy, dx) - const arc = clamp(distance * 0.18, 18, 120) * (Math.random() > 0.5 ? 1 : -1) - const c1x = start.x + dx * 0.34 + Math.cos(angle + Math.PI / 2) * arc - const c1y = start.y + dy * 0.34 + Math.sin(angle + Math.PI / 2) * arc - const c2x = start.x + dx * 0.68 + Math.cos(angle - Math.PI / 2) * arc * 0.65 - const c2y = start.y + dy * 0.68 + Math.sin(angle - Math.PI / 2) * arc * 0.65 - const startTs = performance.now() - - while (true) { - await new Promise(function nextFrame(resolve) { - requestAnimationFrame(function () { - resolve() - }) - }) - if (token !== runTokenRef.current) return false - if (pausedRef.current) continue - const t = clamp((performance.now() - startTs) / durationMs, 0, 1) - const easedT = easeInOutCubic(t) - setCursor({ - x: cubicBezier(easedT, start.x, c1x, c2x, end.x), - y: cubicBezier(easedT, start.y, c1y, c2y, end.y) + const animateSegment = useCallback(async function animateSegment( + start: { x: number; y: number }, + end: { x: number; y: number }, + token: number + ) { + const dx = end.x - start.x + const dy = end.y - start.y + const distance = Math.hypot(dx, dy) + const durationMs = clamp(distance * 2.4, 300, 1100) + const angle = Math.atan2(dy, dx) + const arc = clamp(distance * 0.18, 18, 120) * (Math.random() > 0.5 ? 1 : -1) + const c1x = start.x + dx * 0.34 + Math.cos(angle + Math.PI / 2) * arc + const c1y = start.y + dy * 0.34 + Math.sin(angle + Math.PI / 2) * arc + const c2x = start.x + dx * 0.68 + Math.cos(angle - Math.PI / 2) * arc * 0.65 + const c2y = start.y + dy * 0.68 + Math.sin(angle - Math.PI / 2) * arc * 0.65 + const startTs = performance.now() + + while (true) { + await new Promise(function nextFrame(resolve) { + requestAnimationFrame(function () { + resolve() }) - if (t >= 1) return true - } - }, - [] - ) + }) + if (token !== runTokenRef.current) return false + if (pausedRef.current) continue + const t = clamp((performance.now() - startTs) / durationMs, 0, 1) + const easedT = easeInOutCubic(t) + setCursor({ + x: cubicBezier(easedT, start.x, c1x, c2x, end.x), + y: cubicBezier(easedT, start.y, c1y, c2y, end.y) + }) + if (t >= 1) return true + } + }, []) const dispatchClickAtPoint = useCallback(function dispatchClickAtPoint(x: number, y: number) { const target = document.elementFromPoint(x, y) @@ -136,35 +127,38 @@ export function RecordingOverlay() { target.dispatchEvent(new MouseEvent('click', eventInit)) }, []) - const runPlayback = useCallback(async function runPlayback() { - if (waypoints.length === 0) return + const runPlayback = useCallback( + async function runPlayback() { + if (waypoints.length === 0) return - const token = runTokenRef.current + 1 - runTokenRef.current = token - pausedRef.current = false - setPlaybackState('playing') + const token = runTokenRef.current + 1 + runTokenRef.current = token + pausedRef.current = false + setPlaybackState('playing') - let current = cursor ?? { - x: window.innerWidth / 2, - y: window.innerHeight / 2 - } - setCursor(current) - - for (const waypoint of waypoints) { - const waitingCompleted = await waitWithPause(waypoint.delayMs, token) - if (!waitingCompleted) return - const movementCompleted = await animateSegment(current, waypoint, token) - if (!movementCompleted) return - current = waypoint - setCursor({ x: waypoint.x, y: waypoint.y }) - dispatchClickAtPoint(waypoint.x, waypoint.y) - } + let current = cursor ?? { + x: window.innerWidth / 2, + y: window.innerHeight / 2 + } + setCursor(current) + + for (const waypoint of waypoints) { + const waitingCompleted = await waitWithPause(waypoint.delayMs, token) + if (!waitingCompleted) return + const movementCompleted = await animateSegment(current, waypoint, token) + if (!movementCompleted) return + current = waypoint + setCursor({ x: waypoint.x, y: waypoint.y }) + dispatchClickAtPoint(waypoint.x, waypoint.y) + } - if (token === runTokenRef.current) { - pausedRef.current = false - setPlaybackState('idle') - } - }, [animateSegment, cursor, dispatchClickAtPoint, waitWithPause, waypoints]) + if (token === runTokenRef.current) { + pausedRef.current = false + setPlaybackState('idle') + } + }, + [animateSegment, cursor, dispatchClickAtPoint, waitWithPause, waypoints] + ) useEffect( function captureWaypointsOnClick() { @@ -212,9 +206,12 @@ export function RecordingOverlay() { } }, []) - const waypointPath = useMemo(function path() { - return waypoints.map((point) => `${point.x},${point.y}`).join(' ') - }, [waypoints]) + const waypointPath = useMemo( + function path() { + return waypoints.map((point) => `${point.x},${point.y}`).join(' ') + }, + [waypoints] + ) function handleRecordToggle() { if (playbackState === 'recording') { @@ -324,13 +321,17 @@ export function RecordingOverlay() {

Demo Autopilot

-

{playbackState}

+

+ {playbackState} +

) diff --git a/apps/desktop/src/features/connections/components/connection-switcher.tsx b/apps/desktop/src/features/connections/components/connection-switcher.tsx index 7f42d682..2bcffaeb 100644 --- a/apps/desktop/src/features/connections/components/connection-switcher.tsx +++ b/apps/desktop/src/features/connections/components/connection-switcher.tsx @@ -188,105 +188,108 @@ export function ConnectionSwitcher({
{filteredConnections.length > 0 ? ( filteredConnections.map((connection) => ( - - - -
-
- {connection.status === 'error' ? ( - - ) : ( - - )} -
-
-
{connection.name}
-
- Created {formatQuickDate(connection.createdAt)} + + + +
+
+ {connection.status === 'error' ? ( + + ) : ( + + )}
-
- Last used {formatQuickDate(connection.lastConnectedAt)} +
+
+ {connection.name} +
+
+ Created {formatQuickDate(connection.createdAt)} +
+
+ Last used{' '} + {formatQuickDate(connection.lastConnectedAt)} +
+
+
+ {connection.id === activeConnectionId && ( + + )} + {onEditConnection && ( + + )} + {onDeleteConnection && ( + + )} +
-
- {connection.id === activeConnectionId && ( - - )} - {onEditConnection && ( - - )} - {onDeleteConnection && ( - - )} - -
-
-
-
+ + - onViewConnection?.(connection.id)} - className='gap-2 cursor-pointer' - > - - View Details - - onEditConnection?.(connection.id)} - className='gap-2 cursor-pointer' - > - - Edit Connection - - + onViewConnection?.(connection.id)} + className='gap-2 cursor-pointer' + > + + View Details + + onEditConnection?.(connection.id)} + className='gap-2 cursor-pointer' + > + + Edit Connection + + { event.preventDefault() @@ -302,7 +305,9 @@ export function ConnectionSwitcher({ )) ) : (
- {connections.length > 0 ? 'No matching connections' : 'No connections found'} + {connections.length > 0 + ? 'No matching connections' + : 'No connections found'}
)}
diff --git a/apps/desktop/src/features/connections/validation.ts b/apps/desktop/src/features/connections/validation.ts index 68a891b3..5fc45c20 100644 --- a/apps/desktop/src/features/connections/validation.ts +++ b/apps/desktop/src/features/connections/validation.ts @@ -1,12 +1,12 @@ import { z } from 'zod' const baseConnectionSchema = z.object({ - name: z.string().min(1, 'Connection name is required').max(100, 'Name is too long'), + name: z.string().min(1, 'Connection name is required').max(100, 'Name is too long') }) export const sqliteConnectionSchema = baseConnectionSchema.extend({ type: z.literal('sqlite'), - url: z.string().min(1, 'Database file path is required'), + url: z.string().min(1, 'Database file path is required') }) export const libsqlConnectionSchema = baseConnectionSchema.extend({ @@ -15,59 +15,76 @@ export const libsqlConnectionSchema = baseConnectionSchema.extend({ .string() .min(1, 'Database URL is required') .refine( - (val) => val.startsWith('libsql://') || val.startsWith('https://') || val.startsWith('http://'), + (val) => + val.startsWith('libsql://') || + val.startsWith('https://') || + val.startsWith('http://'), 'URL must start with libsql://, https://, or http://' ), - authToken: z.string().optional(), + authToken: z.string().optional() }) -export const connectionStringSchema = baseConnectionSchema.extend({ - type: z.enum(['postgres', 'mysql']), +export const postgresConnectionStringSchema = baseConnectionSchema.extend({ + type: z.literal('postgres'), url: z .string() .min(1, 'Connection string is required') .refine( - (val) => - val.startsWith('postgres://') || - val.startsWith('postgresql://') || - val.startsWith('mysql://'), + (val) => val.startsWith('postgres://') || val.startsWith('postgresql://'), 'Invalid connection string format' - ), + ) +}) + +export const mysqlConnectionStringSchema = baseConnectionSchema.extend({ + type: z.literal('mysql'), + url: z + .string() + .min(1, 'Connection string is required') + .refine( + (val) => val.startsWith('mysql://'), + 'Invalid connection string format' + ) }) export const connectionFieldsSchema = baseConnectionSchema.extend({ type: z.enum(['postgres', 'mysql']), host: z.string().min(1, 'Host is required'), - port: z.number().int().min(1, 'Port must be positive').max(65535, 'Port must be less than 65536'), + port: z + .number() + .int() + .min(1, 'Port must be positive') + .max(65535, 'Port must be less than 65536'), user: z.string().min(1, 'Username is required'), password: z.string().optional(), database: z.string().min(1, 'Database name is required'), - ssl: z.boolean().optional(), + ssl: z.boolean().optional() }) -export const sshTunnelSchema = z.object({ - enabled: z.literal(true), - host: z.string().min(1, 'SSH host is required'), - port: z.number().int().min(1).max(65535).default(22), - username: z.string().min(1, 'SSH username is required'), - authMethod: z.enum(['password', 'keyfile']), - password: z.string().optional(), - privateKeyPath: z.string().optional(), -}).refine( - (data) => { - if (data.authMethod === 'password') { - return !!data.password - } - if (data.authMethod === 'keyfile') { - return !!data.privateKeyPath +export const sshTunnelSchema = z + .object({ + enabled: z.literal(true), + host: z.string().min(1, 'SSH host is required'), + port: z.number().int().min(1).max(65535).default(22), + username: z.string().min(1, 'SSH username is required'), + authMethod: z.enum(['password', 'keyfile']), + password: z.string().optional(), + privateKeyPath: z.string().optional() + }) + .refine( + (data) => { + if (data.authMethod === 'password') { + return !!data.password + } + if (data.authMethod === 'keyfile') { + return !!data.privateKeyPath + } + return true + }, + { + message: 'Password or private key is required based on auth method', + path: ['password'] } - return true - }, - { - message: 'Password or private key is required based on auth method', - path: ['password'], - } -) + ) export type ValidationResult = { success: boolean @@ -94,12 +111,20 @@ export function validateConnection( libsqlConnectionSchema.parse(formData) } else if (type === 'postgres' || type === 'mysql') { if (useConnectionString) { - connectionStringSchema.parse(formData) + if (type === 'postgres') { + postgresConnectionStringSchema.parse(formData) + } else { + mysqlConnectionStringSchema.parse(formData) + } } else { connectionFieldsSchema.parse(formData) } - if (type === 'postgres' && formData.sshConfig && typeof formData.sshConfig === 'object') { + if ( + type === 'postgres' && + formData.sshConfig && + typeof formData.sshConfig === 'object' + ) { const sshConfig = formData.sshConfig as Record if (sshConfig.enabled === true) { sshTunnelSchema.parse(sshConfig) @@ -114,7 +139,7 @@ export function validateConnection( return { success: false, error: firstError.message, - field: firstError.path[0] as string, + field: firstError.path[0] as string } } return { success: false, error: 'Validation failed' } diff --git a/apps/desktop/src/features/database-studio/components/bottom-status-bar.tsx b/apps/desktop/src/features/database-studio/components/bottom-status-bar.tsx index b800ee53..21268826 100644 --- a/apps/desktop/src/features/database-studio/components/bottom-status-bar.tsx +++ b/apps/desktop/src/features/database-studio/components/bottom-status-bar.tsx @@ -82,12 +82,16 @@ export function BottomStatusBar({ <>
- 0 ? 'bg-amber-400' : 'bg-emerald-400') - : 'bg-muted-foreground/40' - )} /> + 0 + ? 'bg-amber-400' + : 'bg-emerald-400' + : 'bg-muted-foreground/40' + )} + /> {liveMonitorEnabled ? ( liveMonitorError ? ( diff --git a/apps/desktop/src/features/database-studio/components/change-feed.tsx b/apps/desktop/src/features/database-studio/components/change-feed.tsx index bda049ab..3efea170 100644 --- a/apps/desktop/src/features/database-studio/components/change-feed.tsx +++ b/apps/desktop/src/features/database-studio/components/change-feed.tsx @@ -7,151 +7,154 @@ import { cn } from '@/shared/utils/cn' import type { ChangeEvent, ChangeType } from '../hooks/use-live-monitor' type TProps = { - events: ChangeEvent[] - unreadCount: number - onClear: () => void - onMarkRead: () => void + events: ChangeEvent[] + unreadCount: number + onClear: () => void + onMarkRead: () => void } const CHANGE_ICONS: Record = { - insert: ArrowDownRight, - update: Pencil, - delete: Trash2 + insert: ArrowDownRight, + update: Pencil, + delete: Trash2 } const CHANGE_COLORS: Record = { - insert: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30', - update: 'bg-amber-500/15 text-amber-400 border-amber-500/30', - delete: 'bg-red-500/15 text-red-400 border-red-500/30' + insert: 'bg-emerald-500/15 text-emerald-400 border-emerald-500/30', + update: 'bg-amber-500/15 text-amber-400 border-amber-500/30', + delete: 'bg-red-500/15 text-red-400 border-red-500/30' } const CHANGE_DOT_COLORS: Record = { - insert: 'bg-emerald-400', - update: 'bg-amber-400', - delete: 'bg-red-400' + insert: 'bg-emerald-400', + update: 'bg-amber-400', + delete: 'bg-red-400' } function formatTimestamp(ts: number): string { - const date = new Date(ts) - const hours = date.getHours().toString().padStart(2, '0') - const minutes = date.getMinutes().toString().padStart(2, '0') - const seconds = date.getSeconds().toString().padStart(2, '0') - return `${hours}:${minutes}:${seconds}` + const date = new Date(ts) + const hours = date.getHours().toString().padStart(2, '0') + const minutes = date.getMinutes().toString().padStart(2, '0') + const seconds = date.getSeconds().toString().padStart(2, '0') + return `${hours}:${minutes}:${seconds}` } function formatRelativeTime(ts: number): string { - const diff = Date.now() - ts - if (diff < 60000) return 'just now' - if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago` - return `${Math.floor(diff / 3600000)}h ago` + const diff = Date.now() - ts + if (diff < 60000) return 'just now' + if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago` + return `${Math.floor(diff / 3600000)}h ago` } export function ChangeFeed({ events, unreadCount, onClear, onMarkRead }: TProps) { - const scrollRef = useRef(null) + const scrollRef = useRef(null) - useEffect(function scrollToTop() { - if (scrollRef.current && events.length > 0) { - scrollRef.current.scrollTop = 0 - } - }, [events.length]) + useEffect( + function scrollToTop() { + if (scrollRef.current && events.length > 0) { + scrollRef.current.scrollTop = 0 + } + }, + [events.length] + ) - return ( - - - - - -
-
- - Changes - {events.length > 0 && ( - - {events.length} - - )} -
- {events.length > 0 && ( - - )} -
+ return ( + + + + + +
+
+ + Changes + {events.length > 0 && ( + + {events.length} + + )} +
+ {events.length > 0 && ( + + )} +
-
- {events.length === 0 ? ( -
- - No changes detected - - Enable live monitor to start tracking - -
- ) : ( -
- {events.map(function (event) { - const Icon = CHANGE_ICONS[event.changeType] - return ( -
-
- -
-
-
- - - {event.tableName} - -
-

- {event.summary} -

- - {formatTimestamp(event.timestamp)} Ā· {formatRelativeTime(event.timestamp)} - -
-
- ) - })} -
- )} -
-
-
- ) +
+ {events.length === 0 ? ( +
+ + No changes detected + + Enable live monitor to start tracking + +
+ ) : ( +
+ {events.map(function (event) { + const Icon = CHANGE_ICONS[event.changeType] + return ( +
+
+ +
+
+
+ + + {event.tableName} + +
+

+ {event.summary} +

+ + {formatTimestamp(event.timestamp)} Ā·{' '} + {formatRelativeTime(event.timestamp)} + +
+
+ ) + })} +
+ )} +
+
+
+ ) } 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 eb5f74f5..0a8bcefd 100644 --- a/apps/desktop/src/features/database-studio/components/data-grid.tsx +++ b/apps/desktop/src/features/database-studio/components/data-grid.tsx @@ -253,16 +253,13 @@ export function DataGrid({ lastClickedRowRef.current = rowIndex } - useEffect( - function cleanupPendingFrame() { - return function () { - if (pendingNavFrameRef.current !== null) { - cancelAnimationFrame(pendingNavFrameRef.current) - } + useEffect(function cleanupPendingFrame() { + return function () { + if (pendingNavFrameRef.current !== null) { + cancelAnimationFrame(pendingNavFrameRef.current) } - }, - [] - ) + } + }, []) useEffect( function keepFocusedCellInView() { @@ -607,18 +604,34 @@ export function DataGrid({ cellsArray.sort(function (a, b) { return a.row === b.row ? a.col - b.col : a.row - b.row }) - const minRow = Math.min(...cellsArray.map(function (c) { return c.row })) - const maxRow = Math.max(...cellsArray.map(function (c) { return c.row })) + const minRow = Math.min( + ...cellsArray.map(function (c) { + return c.row + }) + ) + const maxRow = Math.max( + ...cellsArray.map(function (c) { + return c.row + }) + ) const rowData: string[][] = [] for (let r = minRow; r <= maxRow; r++) { - const rowCells = cellsArray.filter(function (c) { return c.row === r }) + const rowCells = cellsArray.filter(function (c) { + return c.row === r + }) const values = rowCells.map(function (cell) { const value = rows[cell.row][columns[cell.col].name] - return value === null || value === undefined ? '' : String(value) + return value === null || value === undefined + ? '' + : String(value) }) rowData.push(values) } - const clipboardText = rowData.map(function (r) { return r.join('\t') }).join('\n') + const clipboardText = rowData + .map(function (r) { + return r.join('\t') + }) + .join('\n') navigator.clipboard.writeText(clipboardText) } else if (focusedCell) { const value = rows[focusedCell.row][columns[focusedCell.col].name] @@ -630,23 +643,26 @@ export function DataGrid({ case 'v': if ((e.ctrlKey || e.metaKey) && focusedCell && onCellEdit) { e.preventDefault() - navigator.clipboard.readText().then(function (clipboardText) { - if (!clipboardText || !focusedCell) return - const pasteRows = clipboardText.split('\n').map(function (line) { - return line.split('\t') - }) - pasteRows.forEach(function (pasteRow, pasteRowIndex) { - const targetRow = focusedCell.row + pasteRowIndex - if (targetRow >= rows.length) return - pasteRow.forEach(function (pasteValue, pasteColIndex) { - const targetCol = focusedCell.col + pasteColIndex - if (targetCol >= columns.length) return - onCellEdit!(targetRow, columns[targetCol].name, pasteValue) + navigator.clipboard + .readText() + .then(function (clipboardText) { + if (!clipboardText || !focusedCell) return + const pasteRows = clipboardText.split('\n').map(function (line) { + return line.split('\t') + }) + pasteRows.forEach(function (pasteRow, pasteRowIndex) { + const targetRow = focusedCell.row + pasteRowIndex + if (targetRow >= rows.length) return + pasteRow.forEach(function (pasteValue, pasteColIndex) { + const targetCol = focusedCell.col + pasteColIndex + if (targetCol >= columns.length) return + onCellEdit!(targetRow, columns[targetCol].name, pasteValue) + }) }) }) - }).catch(function () { - // Clipboard read unavailable — no-op - }) + .catch(function () { + // Clipboard read unavailable — no-op + }) } break } @@ -726,7 +742,8 @@ export function DataGrid({

No columns found

- This table doesn't have any columns defined yet, or the schema couldn't be loaded. + This table doesn't have any columns defined yet, or the schema couldn't be + loaded.

@@ -965,9 +982,9 @@ export function DataGrid({ ) return ( - - + - - - - void - isPolling: boolean + config: LiveMonitorConfig + onConfigChange: (config: LiveMonitorConfig) => void + isPolling: boolean } const CHANGE_TYPE_LABELS: Record = { - insert: 'Inserts', - update: 'Updates', - delete: 'Deletes' + insert: 'Inserts', + update: 'Updates', + delete: 'Deletes' } const CHANGE_TYPE_COLORS: Record = { - insert: 'text-emerald-400', - update: 'text-amber-400', - delete: 'text-red-400' + insert: 'text-emerald-400', + update: 'text-amber-400', + delete: 'text-red-400' } function formatInterval(ms: number): string { - if (ms < 1000) return `${ms}ms` - return `${ms / 1000}s` + if (ms < 1000) return `${ms}ms` + return `${ms / 1000}s` } export function LiveMonitorPopover({ config, onConfigChange, isPolling }: TProps) { - function handleToggleEnabled(enabled: boolean) { - onConfigChange({ ...config, enabled }) - } + function handleToggleEnabled(enabled: boolean) { + onConfigChange({ ...config, enabled }) + } - function handleIntervalChange(value: number[]) { - onConfigChange({ ...config, intervalMs: value[0] }) - } + function handleIntervalChange(value: number[]) { + onConfigChange({ ...config, intervalMs: value[0] }) + } - function handleToggleChangeType(changeType: ChangeType, checked: boolean) { - const currentTypes = config.subscription.changeTypes - const nextTypes = checked - ? [...currentTypes, changeType] - : currentTypes.filter(function (t) { return t !== changeType }) + function handleToggleChangeType(changeType: ChangeType, checked: boolean) { + const currentTypes = config.subscription.changeTypes + const nextTypes = checked + ? [...currentTypes, changeType] + : currentTypes.filter(function (t) { + return t !== changeType + }) - if (nextTypes.length === 0) return + if (nextTypes.length === 0) return - onConfigChange({ - ...config, - subscription: { ...config.subscription, changeTypes: nextTypes } - }) - } + onConfigChange({ + ...config, + subscription: { ...config.subscription, changeTypes: nextTypes } + }) + } - return ( - - - - - -
-
-
- - Live Monitor -
- -
-
+ return ( + + + + + +
+
+
+ + Live Monitor +
+ +
+
-
-
-
- Poll interval - - {formatInterval(config.intervalMs)} - -
- -
- 2s - 30s -
-
+
+
+
+ Poll interval + + {formatInterval(config.intervalMs)} + +
+ +
+ 2s + 30s +
+
-
-
- - Notify me about -
-
- {(['insert', 'update', 'delete'] as ChangeType[]).map(function (changeType) { - return ( - - ) - })} -
-
-
- - - ) +
+
+ + Notify me about +
+
+ {(['insert', 'update', 'delete'] as ChangeType[]).map( + function (changeType) { + return ( + + ) + } + )} +
+
+
+
+
+ ) } diff --git a/apps/desktop/src/features/database-studio/components/row-context-menu.tsx b/apps/desktop/src/features/database-studio/components/row-context-menu.tsx index ea9a9e54..d91f2fe6 100644 --- a/apps/desktop/src/features/database-studio/components/row-context-menu.tsx +++ b/apps/desktop/src/features/database-studio/components/row-context-menu.tsx @@ -18,7 +18,12 @@ type Props = { rowIndex: number columns: ColumnDefinition[] tableName?: string - onAction?: (action: RowAction, row: Record, rowIndex: number, batchIndexes?: number[]) => void + onAction?: ( + action: RowAction, + row: Record, + rowIndex: number, + batchIndexes?: number[] + ) => void onOpenChange?: (open: boolean, rowIndex: number) => void selectedRows?: Set children: React.ReactNode @@ -120,7 +125,11 @@ export function RowContextMenu({ - {isBatch ? `Copy SQL INSERT (${batchCount} rows)` : 'Copy SQL INSERT'} + + {isBatch + ? `Copy SQL INSERT (${batchCount} rows)` + : 'Copy SQL INSERT'} + diff --git a/apps/desktop/src/features/database-studio/components/selection-action-bar.tsx b/apps/desktop/src/features/database-studio/components/selection-action-bar.tsx index 29d3ba09..dd3d997e 100644 --- a/apps/desktop/src/features/database-studio/components/selection-action-bar.tsx +++ b/apps/desktop/src/features/database-studio/components/selection-action-bar.tsx @@ -227,7 +227,10 @@ export const SelectionActionBar = forwardRef(function Sel )} {onExportCsv && ( - )} diff --git a/apps/desktop/src/features/database-studio/components/studio-toolbar.tsx b/apps/desktop/src/features/database-studio/components/studio-toolbar.tsx index 918762ca..43806725 100644 --- a/apps/desktop/src/features/database-studio/components/studio-toolbar.tsx +++ b/apps/desktop/src/features/database-studio/components/studio-toolbar.tsx @@ -185,7 +185,7 @@ export function StudioToolbar({ className={cn( 'h-7 px-2 text-xs gap-1.5 ml-1', (showFilters || filters.length > 0) && - 'text-sidebar-foreground bg-sidebar-accent' + 'text-sidebar-foreground bg-sidebar-accent' )} onClick={() => setShowFilters(!showFilters)} > @@ -245,7 +245,7 @@ export function StudioToolbar({ className={cn( 'h-7 px-2 text-xs gap-1.5', isDryEditMode && - 'bg-amber-500/20 text-amber-500 hover:bg-amber-500/30' + 'bg-amber-500/20 text-amber-500 hover:bg-amber-500/30' )} onClick={function () { onDryEditModeChange(!isDryEditMode) @@ -330,9 +330,7 @@ export function StudioToolbar({ - - Export JSON - + Export JSON {onExportCsv && ( Export CSV @@ -379,9 +377,9 @@ export function StudioToolbar({ { })} + onFiltersChange={onFiltersChange || (() => {})} columns={columns} /> -
+
) } diff --git a/apps/desktop/src/features/database-studio/data-seeder-dialog.tsx b/apps/desktop/src/features/database-studio/data-seeder-dialog.tsx index b7cba725..4668887a 100644 --- a/apps/desktop/src/features/database-studio/data-seeder-dialog.tsx +++ b/apps/desktop/src/features/database-studio/data-seeder-dialog.tsx @@ -1,12 +1,12 @@ import { useState } from 'react' import { generateData, TableColumn } from '@/core/data-generation/generator' import { - Dialog, - DialogContent, - DialogDescription, - DialogFooter, - DialogHeader, - DialogTitle + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle } from '@/shared/ui/dialog' import { Button } from '@/shared/ui/button' import { Input } from '@/shared/ui/input' @@ -16,116 +16,121 @@ import { Loader2, Sparkles } from 'lucide-react' import { useToast } from '@/components/ui/use-toast' type Props = { - open: boolean - onOpenChange: (open: boolean) => void - tableName: string - columns: TableColumn[] - onGenerate: (data: any[]) => Promise + open: boolean + onOpenChange: (open: boolean) => void + tableName: string + columns: TableColumn[] + onGenerate: (data: any[]) => Promise } export function DataSeederDialog({ open, onOpenChange, tableName, columns, onGenerate }: Props) { - const [rowCount, setRowCount] = useState(50) - const [previewData, setPreviewData] = useState([]) - const [isGenerating, setIsGenerating] = useState(false) - const { toast } = useToast() + const [rowCount, setRowCount] = useState(50) + const [previewData, setPreviewData] = useState([]) + const [isGenerating, setIsGenerating] = useState(false) + const { toast } = useToast() - function handlePreview() { - const data = generateData(columns, 5) - setPreviewData(data) - } + function handlePreview() { + const data = generateData(columns, 5) + setPreviewData(data) + } - async function handleGenerate() { - try { - setIsGenerating(true) - const data = generateData(columns, rowCount) - await onGenerate(data) - toast({ - title: 'Data Generated', - description: `Successfully generated ${rowCount} rows for ${tableName}` - }) - onOpenChange(false) - } catch (error) { - toast({ - title: 'Error', - description: 'Failed to insert generated data', - variant: 'destructive' - }) - } finally { - setIsGenerating(false) - } - } + async function handleGenerate() { + try { + setIsGenerating(true) + const data = generateData(columns, rowCount) + await onGenerate(data) + toast({ + title: 'Data Generated', + description: `Successfully generated ${rowCount} rows for ${tableName}` + }) + onOpenChange(false) + } catch (error) { + toast({ + title: 'Error', + description: 'Failed to insert generated data', + variant: 'destructive' + }) + } finally { + setIsGenerating(false) + } + } - return ( - - - - Smart Data Seeder - - Generate realistic mock data for {tableName} using AI-powered schema analysis. - - + return ( + + + + Smart Data Seeder + + Generate realistic mock data for{' '} + {tableName} using AI-powered + schema analysis. + + -
-
- - setRowCount(Number(e.target.value))} - className='col-span-3' - min={1} - max={1000} - /> -
+
+
+ + setRowCount(Number(e.target.value))} + className='col-span-3' + min={1} + max={1000} + /> +
-
- -
+
+ +
- {previewData.length > 0 && ( -
- - - - {columns.slice(0, 5).map((col) => ( - - {col.name} - - ))} - - - - {previewData.map((row, i) => ( - - {columns.slice(0, 5).map((col) => ( - - {String(row[col.name])} - - ))} - - ))} - -
-
- )} -
+ {previewData.length > 0 && ( +
+ + + + {columns.slice(0, 5).map((col) => ( + + {col.name} + + ))} + + + + {previewData.map((row, i) => ( + + {columns.slice(0, 5).map((col) => ( + + {String(row[col.name])} + + ))} + + ))} + +
+
+ )} +
- - - - -
-
- ) + + + + +
+
+ ) } diff --git a/apps/desktop/src/features/database-studio/database-studio.tsx b/apps/desktop/src/features/database-studio/database-studio.tsx index 17cae81e..14ecae5e 100644 --- a/apps/desktop/src/features/database-studio/database-studio.tsx +++ b/apps/desktop/src/features/database-studio/database-studio.tsx @@ -247,14 +247,17 @@ export function DatabaseStudio({ [rowsForActions.size] ) - useEffect(function cachePreviousTableDimensions() { - if (tableData && !isLoading) { - previousTableRef.current = { - columns: tableData.columns.length, - rows: Math.min(tableData.rows.length, 12) + useEffect( + function cachePreviousTableDimensions() { + if (tableData && !isLoading) { + previousTableRef.current = { + columns: tableData.columns.length, + rows: Math.min(tableData.rows.length, 12) + } } - } - }, [tableData, isLoading]) + }, + [tableData, isLoading] + ) const loadTableData = useCallback(async () => { if (!tableId || !activeConnectionId) { @@ -337,8 +340,8 @@ export function DatabaseStudio({ const shortcuts = useEffectiveShortcuts() const $ = useShortcut() - $.bind(shortcuts.focusToolbar.combo) - .on(function () { + $.bind(shortcuts.focusToolbar.combo).on( + function () { if (rowsForActions.size > 0 && toolbarRef.current) { toolbarRef.current.focus() // Optionally find the first button and focus it? @@ -346,24 +349,30 @@ export function DatabaseStudio({ // but usually we want to focus the first interactive element. const firstButton = toolbarRef.current.querySelector('button') if (firstButton) { - ; (firstButton as HTMLElement).focus() + ;(firstButton as HTMLElement).focus() } } - }, { description: shortcuts.focusToolbar.description }) + }, + { description: shortcuts.focusToolbar.description } + ) - $.bind(shortcuts.deleteRows.combo) - .on(function () { + $.bind(shortcuts.deleteRows.combo).on( + function () { if (rowsForActions.size > 0 && !isBulkActionLoading) { handleBulkDelete() } - }, { description: shortcuts.deleteRows.description }) + }, + { description: shortcuts.deleteRows.description } + ) - $.bind(shortcuts.deselect.combo) - .on(function () { + $.bind(shortcuts.deselect.combo).on( + function () { setSelectedRows(new Set()) setFocusedCell(null) setSelectedCells(new Set()) - }, { description: shortcuts.deselect.description }) + }, + { description: shortcuts.deselect.description } + ) // eslint-disable-next-line react-hooks/exhaustive-deps useEffect( @@ -382,44 +391,50 @@ export function DatabaseStudio({ onDataChanged: loadTableData }) - useEffect(function handleTableChange() { - if (!tableId || !activeConnectionId) return - setPagination({ limit: 50, offset: 0 }) - setSort(undefined) - setFilters([]) - initializedFromUrlRef.current = false - - const defaultCacheKey = buildTableCacheKey( - activeConnectionId, - tableId, - 50, - 0, - undefined, - [] - ) - const cached = tableDataCache.get(defaultCacheKey) + useEffect( + function handleTableChange() { + if (!tableId || !activeConnectionId) return + setPagination({ limit: 50, offset: 0 }) + setSort(undefined) + setFilters([]) + initializedFromUrlRef.current = false + + const defaultCacheKey = buildTableCacheKey( + activeConnectionId, + tableId, + 50, + 0, + undefined, + [] + ) + const cached = tableDataCache.get(defaultCacheKey) - if (cached) { - setTableData(cached.data) - setVisibleColumns(new Set(cached.visibleColumns)) - setIsTableTransitioning(false) - return - } + if (cached) { + setTableData(cached.data) + setVisibleColumns(new Set(cached.visibleColumns)) + setIsTableTransitioning(false) + return + } - setVisibleColumns(new Set()) - setIsTableTransitioning(true) - }, [tableId, activeConnectionId]) + setVisibleColumns(new Set()) + setIsTableTransitioning(true) + }, + [tableId, activeConnectionId] + ) - useEffect(function clearTransitionOnLoad() { - if (!isLoading && tableData) { - const timer = setTimeout(function () { - setIsTableTransitioning(false) - }, 50) - return function () { - clearTimeout(timer) + useEffect( + function clearTransitionOnLoad() { + if (!isLoading && tableData) { + const timer = setTimeout(function () { + setIsTableTransitioning(false) + }, 50) + return function () { + clearTimeout(timer) + } } - } - }, [isLoading, tableData]) + }, + [isLoading, tableData] + ) useEffect( function initializeFromUrl() { @@ -677,15 +692,7 @@ export function DatabaseStudio({ } } ) - }, [ - tableData, - activeConnectionId, - tableId, - tableName, - selectedRows, - deleteRows, - loadTableData - ]) + }, [tableData, activeConnectionId, tableId, tableName, selectedRows, deleteRows, loadTableData]) const handleBulkCopy = useCallback(() => { if (!tableData) return @@ -732,7 +739,15 @@ export function DatabaseStudio({ .finally(function () { setIsBulkActionLoading(false) }) - }, [tableData, activeConnectionId, tableId, tableName, rowsForActions, insertRow, loadTableData]) + }, [ + tableData, + activeConnectionId, + tableId, + tableName, + rowsForActions, + insertRow, + loadTableData + ]) const deleteRowIndexes = useCallback( function deleteRowIndexes(rowIndexes: number[]) { @@ -761,7 +776,8 @@ export function DatabaseStudio({ console.error('Failed to delete row(s):', error) toast({ title: 'Failed to delete rows', - description: error instanceof Error ? error.message : 'An error occurred', + description: + error instanceof Error ? error.message : 'An error occurred', variant: 'destructive' }) } @@ -877,7 +893,6 @@ export function DatabaseStudio({ }, [selectedRows.size]) const handleOpenSetNull = useCallback(() => setShowSetNullDialog(true), []) const handleOpenBulkEdit = useCallback(() => setShowBulkEditDialog(true), []) - const handleOpenDataSeeder = useCallback(() => setShowDataSeederDialog(true), []) const handleFilterAdd = useCallback(function (filter: FilterDescriptor) { setFilters(function (prev) { return [...prev, filter] @@ -1011,7 +1026,8 @@ export function DatabaseStudio({ console.error('Failed to update cell:', error) toast({ title: 'Failed to update cell', - description: error instanceof Error ? error.message : 'An error occurred', + description: + error instanceof Error ? error.message : 'An error occurred', variant: 'destructive' }) } @@ -1473,7 +1489,9 @@ export function DatabaseStudio({ function handleExportCsvAll() { if (!tableData || tableData.rows.length === 0) return - const headers = tableData.columns.map(function (col) { return col.name }) + const headers = tableData.columns.map(function (col) { + return col.name + }) const csvRows = [ headers.join(','), ...tableData.rows.map(function (row) { @@ -1623,7 +1641,11 @@ export function DatabaseStudio({ } } catch (error) { console.error('Failed to drop table:', error) - toast({ title: 'Failed to drop table', description: String(error), variant: 'destructive' }) + toast({ + title: 'Failed to drop table', + description: String(error), + variant: 'destructive' + }) } finally { setIsDdlLoading(false) } @@ -1865,12 +1887,7 @@ export function DatabaseStudio({ // Content view (default) return (
-
+
{selectionAnnouncement}
)} {!tableData && !isTableTransitioning && ( -
+
No data available
)} @@ -2072,7 +2096,9 @@ export function DatabaseStudio({ Are you absolutely sure? This action cannot be undone. This will permanently delete{' '} - {pendingSingleDeleteRow ? '1 row' : `${selectedRows.size} selected row${selectedRows.size !== 1 ? 's' : ''}`}{' '} + {pendingSingleDeleteRow + ? '1 row' + : `${selectedRows.size} selected row${selectedRows.size !== 1 ? 's' : ''}`}{' '} from the database. @@ -2086,8 +2112,11 @@ export function DatabaseStudio({ { connectionId: activeConnectionId, tableName: tableRefName, - primaryKeyColumn: pendingSingleDeleteRow.primaryKeyColumn, - primaryKeyValues: [pendingSingleDeleteRow.primaryKeyValue] + primaryKeyColumn: + pendingSingleDeleteRow.primaryKeyColumn, + primaryKeyValues: [ + pendingSingleDeleteRow.primaryKeyValue + ] }, { onSuccess: function onSingleDeleteSuccess() { @@ -2155,7 +2184,10 @@ export function DatabaseStudio({ console.error('Failed to bulk edit:', error) toast({ title: 'Failed to update rows', - description: error instanceof Error ? error.message : 'An error occurred', + description: + error instanceof Error + ? error.message + : 'An error occurred', variant: 'destructive' }) }) @@ -2209,7 +2241,10 @@ export function DatabaseStudio({ console.error('Failed to set null:', error) toast({ title: 'Failed to set NULL', - description: error instanceof Error ? error.message : 'An error occurred', + description: + error instanceof Error + ? error.message + : 'An error occurred', variant: 'destructive' }) }) diff --git a/apps/desktop/src/features/database-studio/hooks/use-live-monitor.ts b/apps/desktop/src/features/database-studio/hooks/use-live-monitor.ts index ea11be86..8332cdc9 100644 --- a/apps/desktop/src/features/database-studio/hooks/use-live-monitor.ts +++ b/apps/desktop/src/features/database-studio/hooks/use-live-monitor.ts @@ -72,8 +72,7 @@ const REFRESH_DEBOUNCE_MS = 150 function isTauriRuntime(): boolean { return ( - typeof window !== 'undefined' && - ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) ) } @@ -104,31 +103,37 @@ export function useLiveMonitor({ } }, []) - const scheduleDataRefresh = useCallback(function scheduleRefresh() { - clearRefreshTimer() - refreshTimerRef.current = setTimeout(function runRefresh() { - refreshTimerRef.current = null - onDataChangedRef.current() - }, REFRESH_DEBOUNCE_MS) - }, [clearRefreshTimer]) - - const stopMonitor = useCallback(async function stopCurrentMonitor() { - const monitorId = monitorIdRef.current - clearRefreshTimer() - if (!monitorId) { - setIsPolling(false) - return - } + const scheduleDataRefresh = useCallback( + function scheduleRefresh() { + clearRefreshTimer() + refreshTimerRef.current = setTimeout(function runRefresh() { + refreshTimerRef.current = null + onDataChangedRef.current() + }, REFRESH_DEBOUNCE_MS) + }, + [clearRefreshTimer] + ) - monitorIdRef.current = null - try { - await commands.stopLiveMonitor(monitorId) - } catch (error) { - console.error('[LiveMonitor] Failed to stop monitor:', error) - } finally { - setIsPolling(false) - } - }, [clearRefreshTimer]) + const stopMonitor = useCallback( + async function stopCurrentMonitor() { + const monitorId = monitorIdRef.current + clearRefreshTimer() + if (!monitorId) { + setIsPolling(false) + return + } + + monitorIdRef.current = null + try { + await commands.stopLiveMonitor(monitorId) + } catch (error) { + console.error('[LiveMonitor] Failed to stop monitor:', error) + } finally { + setIsPolling(false) + } + }, + [clearRefreshTimer] + ) useEffect( function subscribeToBackendEvents() { diff --git a/apps/desktop/src/features/docker-manager/api/container-service.ts b/apps/desktop/src/features/docker-manager/api/container-service.ts index 48aacb19..f0a32edd 100644 --- a/apps/desktop/src/features/docker-manager/api/container-service.ts +++ b/apps/desktop/src/features/docker-manager/api/container-service.ts @@ -31,8 +31,7 @@ import { import * as demoService from './demo-service' const isTauri = - typeof window !== 'undefined' && - ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) export async function createPostgresContainer( config: PostgresContainerConfig diff --git a/apps/desktop/src/features/docker-manager/api/demo-service.ts b/apps/desktop/src/features/docker-manager/api/demo-service.ts index 032f6a70..ca27bd1b 100644 --- a/apps/desktop/src/features/docker-manager/api/demo-service.ts +++ b/apps/desktop/src/features/docker-manager/api/demo-service.ts @@ -23,7 +23,13 @@ let demoContainers: DockerContainer[] = [ createdAt: Date.now() - 86400000 * 3, ports: [{ hostPort: 5433, containerPort: 5432, protocol: 'tcp' }], labels: { [MANAGED_LABEL_KEY]: MANAGED_LABEL_VALUE }, - volumes: [{ name: 'dora_analytics_db_data', mountPath: '/var/lib/postgresql/data', isEphemeral: false }], + volumes: [ + { + name: 'dora_analytics_db_data', + mountPath: '/var/lib/postgresql/data', + isEphemeral: false + } + ], env: ['POSTGRES_USER=analytics', 'POSTGRES_DB=analytics', 'POSTGRES_PASSWORD=***'] }, { @@ -37,7 +43,13 @@ let demoContainers: DockerContainer[] = [ createdAt: Date.now() - 86400000, ports: [{ hostPort: 5434, containerPort: 5432, protocol: 'tcp' }], labels: { [MANAGED_LABEL_KEY]: MANAGED_LABEL_VALUE }, - volumes: [{ name: 'dora_dev_postgres_data', mountPath: '/var/lib/postgresql/data', isEphemeral: false }], + volumes: [ + { + name: 'dora_dev_postgres_data', + mountPath: '/var/lib/postgresql/data', + isEphemeral: false + } + ], env: ['POSTGRES_USER=dev', 'POSTGRES_DB=devdb', 'POSTGRES_PASSWORD=***'] }, { @@ -129,9 +141,11 @@ export async function getContainers( export async function getContainer(containerId: string): Promise { await delay(100) - return demoContainers.find(function (c) { - return c.id === containerId - }) ?? null + return ( + demoContainers.find(function (c) { + return c.id === containerId + }) ?? null + ) } export async function createPostgresContainer( @@ -153,11 +167,13 @@ export async function createPostgresContainer( labels: { [MANAGED_LABEL_KEY]: MANAGED_LABEL_VALUE }, volumes: config.ephemeral ? [] - : [{ - name: config.volumeName || generateVolumeName(config.name), - mountPath: '/var/lib/postgresql/data', - isEphemeral: false - }], + : [ + { + name: config.volumeName || generateVolumeName(config.name), + mountPath: '/var/lib/postgresql/data', + isEphemeral: false + } + ], env: [ `POSTGRES_USER=${config.user}`, `POSTGRES_DB=${config.database}`, 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 c147db8f..a4fcedf8 100644 --- a/apps/desktop/src/features/docker-manager/api/docker-client.ts +++ b/apps/desktop/src/features/docker-manager/api/docker-client.ts @@ -62,7 +62,10 @@ export const deps = { export async function executeDockerCommand( args: string[] ): Promise<{ stdout: string; stderr: string; exitCode: number }> { - if (typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)) { + if ( + typeof window !== 'undefined' && + ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + ) { const Command = await deps.getCommand() const command = Command.create('docker', args) const output = await command.execute() @@ -297,7 +300,10 @@ export async function streamContainerLogs( onLog: (line: string) => void, onError: (error: string) => void ): Promise<() => void> { - if (typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)) { + if ( + typeof window !== 'undefined' && + ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + ) { const { Command } = await import('@tauri-apps/plugin-shell') const command = Command.create('docker', ['logs', '-f', '--tail', '100', containerId]) @@ -332,7 +338,10 @@ export async function openContainerTerminal( containerId: string, handlers: ContainerTerminalHandlers ): Promise { - if (typeof window !== 'undefined' && ('__TAURI__' in window || '__TAURI_INTERNALS__' in window)) { + if ( + typeof window !== 'undefined' && + ('__TAURI__' in window || '__TAURI_INTERNALS__' in window) + ) { const { Command } = await import('@tauri-apps/plugin-shell') const command = Command.create('docker', ['exec', '-i', containerId, 'sh']) @@ -450,9 +459,7 @@ export async function execCommand( } export async function getContainerSizes(): Promise { - const result = await executeDockerCommand([ - 'system', 'df', '-v', '--format', '{{json .}}' - ]) + const result = await executeDockerCommand(['system', 'df', '-v', '--format', '{{json .}}']) if (result.exitCode !== 0) { throw new Error(result.stderr || 'Failed to get container sizes') @@ -489,11 +496,11 @@ function parseSize(sizeStr: string): number { const unit = match[2].toUpperCase().replace('I', '') const multipliers: Record = { - 'B': 1, - 'KB': 1024, - 'MB': 1024 * 1024, - 'GB': 1024 * 1024 * 1024, - 'TB': 1024 * 1024 * 1024 * 1024 + B: 1, + KB: 1024, + MB: 1024 * 1024, + GB: 1024 * 1024 * 1024, + TB: 1024 * 1024 * 1024 * 1024 } return value * (multipliers[unit] || 1) diff --git a/apps/desktop/src/features/docker-manager/api/mutations/use-container-actions.ts b/apps/desktop/src/features/docker-manager/api/mutations/use-container-actions.ts index c426a52c..93963333 100644 --- a/apps/desktop/src/features/docker-manager/api/mutations/use-container-actions.ts +++ b/apps/desktop/src/features/docker-manager/api/mutations/use-container-actions.ts @@ -27,7 +27,9 @@ const ACTION_TO_EVENT: Record = { export function useContainerActions(options: UseContainerActionsOptions = {}) { const { onSuccess, onError } = options const queryClient = useQueryClient() - const addEvent = useDockerManagerStore(function (s) { return s.addEvent }) + const addEvent = useDockerManagerStore(function (s) { + return s.addEvent + }) return useMutation({ mutationFn: function (params) { @@ -71,7 +73,9 @@ type UseRemoveContainerOptions = { export function useRemoveContainer(options: UseRemoveContainerOptions = {}) { const { onSuccess, onError } = options const queryClient = useQueryClient() - const addEvent = useDockerManagerStore(function (s) { return s.addEvent }) + const addEvent = useDockerManagerStore(function (s) { + return s.addEvent + }) return useMutation({ mutationFn: function (params) { diff --git a/apps/desktop/src/features/docker-manager/api/mutations/use-create-container.ts b/apps/desktop/src/features/docker-manager/api/mutations/use-create-container.ts index 8b53f02b..412f4e64 100644 --- a/apps/desktop/src/features/docker-manager/api/mutations/use-create-container.ts +++ b/apps/desktop/src/features/docker-manager/api/mutations/use-create-container.ts @@ -12,7 +12,9 @@ type UseCreateContainerOptions = { export function useCreateContainer(options: UseCreateContainerOptions = {}) { const { onSuccess, onError, waitForHealth = true } = options const queryClient = useQueryClient() - const addEvent = useDockerManagerStore(function (s) { return s.addEvent }) + const addEvent = useDockerManagerStore(function (s) { + return s.addEvent + }) return useMutation({ mutationFn: async function (config) { diff --git a/apps/desktop/src/features/docker-manager/components/compose-export-dialog.tsx b/apps/desktop/src/features/docker-manager/components/compose-export-dialog.tsx index b4fecad9..f7beb012 100644 --- a/apps/desktop/src/features/docker-manager/components/compose-export-dialog.tsx +++ b/apps/desktop/src/features/docker-manager/components/compose-export-dialog.tsx @@ -22,9 +22,12 @@ type Props = { export function ComposeExportDialog({ container, open, onOpenChange }: Props) { const { toast } = useToast() - const yamlContent = useMemo(function () { - return generateDockerCompose(container) - }, [container]) + const yamlContent = useMemo( + function () { + return generateDockerCompose(container) + }, + [container] + ) async function handleCopy() { try { diff --git a/apps/desktop/src/features/docker-manager/components/connection-details.tsx b/apps/desktop/src/features/docker-manager/components/connection-details.tsx index 230e088e..0d49c995 100644 --- a/apps/desktop/src/features/docker-manager/components/connection-details.tsx +++ b/apps/desktop/src/features/docker-manager/components/connection-details.tsx @@ -3,10 +3,7 @@ import { useState } from 'react' import { Button } from '@/shared/ui/button' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/shared/ui/tabs' import type { DockerContainer } from '../types' -import { - buildConnectionEnvVars, - maskPassword -} from '../utilities/connection-string-builder' +import { buildConnectionEnvVars, maskPassword } from '../utilities/connection-string-builder' import { generateSnippet, SnippetLanguage } from '../utilities/connection-snippet-generator' import { SnippetHighlight } from './snippet-highlight' import { cn } from '@/shared/utils/cn' @@ -20,7 +17,7 @@ const LANGUAGE_CONFIG: Record e.startsWith('POSTGRES_USER='))?.split('=')[1] || 'postgres' - const database = container.env.find((e) => e.startsWith('POSTGRES_DB='))?.split('=')[1] || 'postgres' + const user = + container.env.find((e) => e.startsWith('POSTGRES_USER='))?.split('=')[1] || 'postgres' + const database = + container.env.find((e) => e.startsWith('POSTGRES_DB='))?.split('=')[1] || 'postgres' const envVars = buildConnectionEnvVars(host, port, user, password, database) const displayUrl = showPassword ? envVars.DATABASE_URL : maskPassword(envVars.DATABASE_URL) @@ -136,33 +135,39 @@ export function ConnectionDetails({ container, password }: Props) {
- {(Object.keys(LANGUAGE_CONFIG) as SnippetLanguage[]).map( - (lang) => { - const config = LANGUAGE_CONFIG[lang] - return ( - - ) - } - )} + {config.icon} + + {config.label} + + ) + })}
- +
) } -function ConnectionRow({ label, value, isLast }: { label: string; value: string; isLast?: boolean }) { +function ConnectionRow({ + label, + value, + isLast +}: { + label: string + value: string + isLast?: boolean +}) { return ( -
+
{label} {value}
diff --git a/apps/desktop/src/features/docker-manager/components/container-card.tsx b/apps/desktop/src/features/docker-manager/components/container-card.tsx index dea25ef3..9bbcf924 100644 --- a/apps/desktop/src/features/docker-manager/components/container-card.tsx +++ b/apps/desktop/src/features/docker-manager/components/container-card.tsx @@ -154,7 +154,10 @@ export function ContainerCard({ title='Stop' disabled={isActionPending} onClick={function (e) { - if (onStop) handleQuickAction(e, function () { onStop(container.id) }) + if (onStop) + handleQuickAction(e, function () { + onStop(container.id) + }) }} > @@ -165,7 +168,10 @@ export function ContainerCard({ disabled={isActionPending} className='text-emerald-500 hover:text-emerald-400 hover:bg-emerald-500/10' onClick={function (e) { - if (onStart) handleQuickAction(e, function () { onStart(container.id) }) + if (onStart) + handleQuickAction(e, function () { + onStart(container.id) + }) }} > @@ -176,16 +182,16 @@ export function ContainerCard({ title='Restart' disabled={isActionPending || !isRunning} onClick={function (e) { - if (onRestart) handleQuickAction(e, function () { onRestart(container.id) }) + if (onRestart) + handleQuickAction(e, function () { + onRestart(container.id) + }) }} > - + {copied ? ( ) : ( diff --git a/apps/desktop/src/features/docker-manager/components/container-details-panel.tsx b/apps/desktop/src/features/docker-manager/components/container-details-panel.tsx index ec7bc3c6..37230a91 100644 --- a/apps/desktop/src/features/docker-manager/components/container-details-panel.tsx +++ b/apps/desktop/src/features/docker-manager/components/container-details-panel.tsx @@ -1,4 +1,12 @@ -import { Play, Square, RotateCcw, Trash2, ExternalLink, FileCode, TerminalSquare } from 'lucide-react' +import { + Play, + Square, + RotateCcw, + Trash2, + ExternalLink, + FileCode, + TerminalSquare +} from 'lucide-react' import { Package } from 'lucide-react' import { useState } from 'react' import { Button } from '@/shared/ui/button' @@ -65,9 +73,7 @@ export function ContainerDetailsPanel({ const isRunning = container.state === 'running' const passwordEnv = container.env.find((e) => e.startsWith('POSTGRES_PASSWORD=')) - const password = passwordEnv - ? passwordEnv.split('=')[1] - : 'postgres' + const password = passwordEnv ? passwordEnv.split('=')[1] : 'postgres' function handleStart() { containerActions.mutate({ containerId: container.id, action: 'start' }) diff --git a/apps/desktop/src/features/docker-manager/components/container-terminal.tsx b/apps/desktop/src/features/docker-manager/components/container-terminal.tsx index 3ede8fa7..8eba0bcd 100644 --- a/apps/desktop/src/features/docker-manager/components/container-terminal.tsx +++ b/apps/desktop/src/features/docker-manager/components/container-terminal.tsx @@ -43,115 +43,127 @@ export function ContainerTerminal({ container, enabled }: Props) { }) }, []) - const disconnectSession = useCallback(async function (announce: boolean) { - const session = sessionRef.current - if (!session) { - return - } - - closingRef.current = true - connectingRef.current = false - sessionRef.current = null - - try { - await session.kill() - } catch { - // Session may already be closed. - } + const disconnectSession = useCallback( + async function (announce: boolean) { + const session = sessionRef.current + if (!session) { + return + } - setTerminalState('disconnected') - if (announce) { - appendOutput('\n[terminal disconnected]\n') - } - }, [appendOutput]) + closingRef.current = true + connectingRef.current = false + sessionRef.current = null - const connectSession = useCallback(async function () { - if (!enabled || !isRunning || sessionRef.current || connectingRef.current) { - return - } + try { + await session.kill() + } catch { + // Session may already be closed. + } - const connectId = connectCounterRef.current + 1 - connectCounterRef.current = connectId + setTerminalState('disconnected') + if (announce) { + appendOutput('\n[terminal disconnected]\n') + } + }, + [appendOutput] + ) - connectingRef.current = true - setTerminalState('connecting') - appendOutput(`[connecting to ${container.name}]\n`) + const connectSession = useCallback( + async function () { + if (!enabled || !isRunning || sessionRef.current || connectingRef.current) { + return + } - try { - const session = await openContainerTerminal(container.id, { - onOutput: function (chunk) { - if (connectCounterRef.current !== connectId) { - return - } - appendOutput(chunk) - }, - onError: function (error) { - if (connectCounterRef.current !== connectId) { - return - } - appendOutput(`\n[terminal error] ${error}\n`) - setTerminalState('error') - }, - onClose: function (code, signal) { - if (connectCounterRef.current !== connectId) { - return + const connectId = connectCounterRef.current + 1 + connectCounterRef.current = connectId + + connectingRef.current = true + setTerminalState('connecting') + appendOutput(`[connecting to ${container.name}]\n`) + + try { + const session = await openContainerTerminal(container.id, { + onOutput: function (chunk) { + if (connectCounterRef.current !== connectId) { + return + } + appendOutput(chunk) + }, + onError: function (error) { + if (connectCounterRef.current !== connectId) { + return + } + appendOutput(`\n[terminal error] ${error}\n`) + setTerminalState('error') + }, + onClose: function (code, signal) { + if (connectCounterRef.current !== connectId) { + return + } + + sessionRef.current = null + setTerminalState('disconnected') + connectingRef.current = false + + const closedByUser = closingRef.current + closingRef.current = false + + if (!closedByUser) { + appendOutput( + `\n[terminal exited${code !== null ? ` code=${code}` : ''}${signal !== null ? ` signal=${signal}` : ''}]\n` + ) + } } + }) - sessionRef.current = null - setTerminalState('disconnected') + if (connectCounterRef.current !== connectId) { + await session.kill().catch(function () { + return + }) connectingRef.current = false - - const closedByUser = closingRef.current - closingRef.current = false - - if (!closedByUser) { - appendOutput( - `\n[terminal exited${code !== null ? ` code=${code}` : ''}${signal !== null ? ` signal=${signal}` : ''}]\n` - ) - } + return } - }) - if (connectCounterRef.current !== connectId) { - await session.kill().catch(function () { - return - }) + sessionRef.current = session connectingRef.current = false - return + setTerminalState('connected') + appendOutput(`[connected to ${container.name}]\n`) + inputRef.current?.focus() + } catch (error) { + connectingRef.current = false + const message = error instanceof Error ? error.message : 'Failed to open terminal' + appendOutput(`\n[connection failed] ${message}\n`) + setTerminalState('error') } + }, + [appendOutput, container.id, container.name, enabled, isRunning] + ) - sessionRef.current = session - connectingRef.current = false - setTerminalState('connected') - appendOutput(`[connected to ${container.name}]\n`) - inputRef.current?.focus() - } catch (error) { - connectingRef.current = false - const message = error instanceof Error ? error.message : 'Failed to open terminal' - appendOutput(`\n[connection failed] ${message}\n`) - setTerminalState('error') - } - }, [appendOutput, container.id, container.name, enabled, isRunning]) - - useEffect(function () { - if (!enabled || !isRunning) { - void disconnectSession(false) - return - } + useEffect( + function () { + if (!enabled || !isRunning) { + void disconnectSession(false) + return + } - void connectSession() + void connectSession() - return function () { - void disconnectSession(false) - } - }, [connectSession, disconnectSession, enabled, isRunning]) + return function () { + void disconnectSession(false) + } + }, + [connectSession, disconnectSession, enabled, isRunning] + ) - useEffect(function () { - if (!outputRef.current) { - return - } - outputRef.current.scrollTop = outputRef.current.scrollHeight - }, [output]) + useEffect( + function () { + if (!outputRef.current) { + return + } + outputRef.current.scrollTop = outputRef.current.scrollHeight + }, + [output] + ) useEffect( function resetStateWhenContainerChanges() { @@ -268,14 +280,20 @@ export function ContainerTerminal({ container, enabled }: Props) {
-
@@ -356,11 +358,7 @@ export function CreateContainerDialog({ -
- +
)} @@ -554,15 +571,7 @@ export function DockerView({ onOpenInDataViewer }: Props) { ) } -function StatPill({ - icon, - label, - value -}: { - icon: ReactNode - label: string - value: number -}) { +function StatPill({ icon, label, value }: { icon: ReactNode; label: string; value: number }) { return (
{icon} diff --git a/apps/desktop/src/features/docker-manager/components/logs-viewer.tsx b/apps/desktop/src/features/docker-manager/components/logs-viewer.tsx index 9edfd4a9..1077dbb3 100644 --- a/apps/desktop/src/features/docker-manager/components/logs-viewer.tsx +++ b/apps/desktop/src/features/docker-manager/components/logs-viewer.tsx @@ -30,7 +30,9 @@ function tokenizeLine(line: string): LogToken[] { } // Match log level keywords - const levelMatch = remaining.match(/^(LOG|ERROR|FATAL|PANIC|WARNING|HINT|NOTICE|DEBUG|INFO|DETAIL|STATEMENT)(\s*:\s*)/) + const levelMatch = remaining.match( + /^(LOG|ERROR|FATAL|PANIC|WARNING|HINT|NOTICE|DEBUG|INFO|DETAIL|STATEMENT)(\s*:\s*)/ + ) if (levelMatch) { const level = levelMatch[1] let levelColor = 'text-blue-400' @@ -58,7 +60,10 @@ function tokenizeLine(line: string): LogToken[] { for (let i = 0; i < parts.length; i++) { const part = parts[i] if (!part) continue - if ((part.startsWith('"') && part.endsWith('"')) || (part.startsWith("'") && part.endsWith("'"))) { + if ( + (part.startsWith('"') && part.endsWith('"')) || + (part.startsWith("'") && part.endsWith("'")) + ) { tokens.push({ text: part, className: 'text-amber-300/80' }) } else if (/^\d+\.\d+%?$/.test(part) || /^\d{3,}$/.test(part)) { tokens.push({ text: part, className: 'text-purple-400/80' }) @@ -74,11 +79,14 @@ function tokenizeLine(line: string): LogToken[] { } function HighlightedLogs({ logs }: { logs: string }) { - const lines = useMemo(function () { - return logs.split('\n').map(function (line, i) { - return { key: i, tokens: tokenizeLine(line) } - }) - }, [logs]) + const lines = useMemo( + function () { + return logs.split('\n').map(function (line, i) { + return { key: i, tokens: tokenizeLine(line) } + }) + }, + [logs] + ) return ( <> diff --git a/apps/desktop/src/features/docker-manager/components/seed-view.tsx b/apps/desktop/src/features/docker-manager/components/seed-view.tsx index 6dea1f2b..7eeb7509 100644 --- a/apps/desktop/src/features/docker-manager/components/seed-view.tsx +++ b/apps/desktop/src/features/docker-manager/components/seed-view.tsx @@ -147,7 +147,9 @@ export function SeedView({ container }: Props) { Drag and drop or press Enter to browse

- Click anywhere to select a file + + Click anywhere to select a file + ) : ( <> diff --git a/apps/desktop/src/features/docker-manager/components/snippet-highlight.tsx b/apps/desktop/src/features/docker-manager/components/snippet-highlight.tsx index fc0d0015..dd9dd632 100644 --- a/apps/desktop/src/features/docker-manager/components/snippet-highlight.tsx +++ b/apps/desktop/src/features/docker-manager/components/snippet-highlight.tsx @@ -3,7 +3,17 @@ import type { SnippetLanguage } from '../utilities/connection-snippet-generator' type Token = { text: string - type: 'keyword' | 'string' | 'comment' | 'function' | 'number' | 'operator' | 'property' | 'variable' | 'flag' | 'plain' + type: + | 'keyword' + | 'string' + | 'comment' + | 'function' + | 'number' + | 'operator' + | 'property' + | 'variable' + | 'flag' + | 'plain' } const TOKEN_COLORS: Record = { @@ -16,7 +26,7 @@ const TOKEN_COLORS: Record = { property: 'text-cyan-300', variable: 'text-red-300', flag: 'text-zinc-400', - plain: 'text-zinc-300', + plain: 'text-zinc-300' } function tokenizeLine(line: string, language: SnippetLanguage): Token[] { @@ -37,7 +47,8 @@ function tokenizeLine(line: string, language: SnippetLanguage): Token[] { function tokenizeShell(line: string): Token[] { const tokens: Token[] = [] // Pattern: ENVVAR='value' command -flags args - const regex = /([A-Z_]+=)('[^']*'|"[^"]*")|('[^']*'|"[^"]*")|(-[a-zA-Z]\b)|(\b(?:psql|docker|pg_dump|pg_restore)\b)|([A-Z_]{2,}\b)|(\S+)/g + const regex = + /([A-Z_]+=)('[^']*'|"[^"]*")|('[^']*'|"[^"]*")|(-[a-zA-Z]\b)|(\b(?:psql|docker|pg_dump|pg_restore)\b)|([A-Z_]{2,}\b)|(\S+)/g let match: RegExpExecArray | null let lastIndex = 0 @@ -78,7 +89,8 @@ function tokenizeShell(line: string): Token[] { function tokenizeJS(line: string): Token[] { const tokens: Token[] = [] - const regex = /(\/\/.*$)|('[^']*'|"[^"]*"|`[^`]*`)|\b(import|from|const|let|var|await|async|function|new|return)\b|\b(console)\b\.(log|error|warn)\b|(\.\w+)\s*(?=\()|(\b\d+\b)|([{}();,=])|(\w+)/g + const regex = + /(\/\/.*$)|('[^']*'|"[^"]*"|`[^`]*`)|\b(import|from|const|let|var|await|async|function|new|return)\b|\b(console)\b\.(log|error|warn)\b|(\.\w+)\s*(?=\()|(\b\d+\b)|([{}();,=])|(\w+)/g let match: RegExpExecArray | null let lastIndex = 0 @@ -120,7 +132,8 @@ function tokenizeJS(line: string): Token[] { function tokenizePython(line: string): Token[] { const tokens: Token[] = [] - const regex = /(#.*$)|("[^"]*"|'[^']*')|\b(import|from|as|def|class|return|if|else|elif|with|for|in|not|and|or|True|False|None)\b|\b(print|connect|cursor|execute|fetchall|fetchone)\b(?=\s*\()|(\b\d+\b)|([().,=:])|(\w+)/g + const regex = + /(#.*$)|("[^"]*"|'[^']*')|\b(import|from|as|def|class|return|if|else|elif|with|for|in|not|and|or|True|False|None)\b|\b(print|connect|cursor|execute|fetchall|fetchone)\b(?=\s*\()|(\b\d+\b)|([().,=:])|(\w+)/g let match: RegExpExecArray | null let lastIndex = 0 @@ -157,7 +170,8 @@ function tokenizePython(line: string): Token[] { function tokenizePrisma(line: string): Token[] { const tokens: Token[] = [] - const regex = /(\/\/.*$)|("[^"]*")|\b(datasource|generator|model|enum)\b|\b(provider|url|output|db)\b|([={}])|(\w+)/g + const regex = + /(\/\/.*$)|("[^"]*")|\b(datasource|generator|model|enum)\b|\b(provider|url|output|db)\b|([={}])|(\w+)/g let match: RegExpExecArray | null let lastIndex = 0 @@ -196,14 +210,17 @@ type Props = { } export function SnippetHighlight({ code, language }: Props) { - const highlighted = useMemo(function () { - return code.split('\n').map(function (line, i) { - return { - key: i, - tokens: tokenizeLine(line, language) - } - }) - }, [code, language]) + const highlighted = useMemo( + function () { + return code.split('\n').map(function (line, i) { + return { + key: i, + tokens: tokenizeLine(line, language) + } + }) + }, + [code, language] + ) return ( <> diff --git a/apps/desktop/src/features/docker-manager/components/status-badge.tsx b/apps/desktop/src/features/docker-manager/components/status-badge.tsx index c84807a7..a6196683 100644 --- a/apps/desktop/src/features/docker-manager/components/status-badge.tsx +++ b/apps/desktop/src/features/docker-manager/components/status-badge.tsx @@ -1,11 +1,4 @@ -import { - AlertCircle, - CheckCircle2, - CircleDot, - Loader2, - PauseCircle, - Square -} from 'lucide-react' +import { AlertCircle, CheckCircle2, CircleDot, Loader2, PauseCircle, Square } from 'lucide-react' import type { LucideIcon } from 'lucide-react' import type { ContainerHealth, ContainerState } from '../types' import { cn } from '@/shared/utils/cn' diff --git a/apps/desktop/src/features/docker-manager/constants.ts b/apps/desktop/src/features/docker-manager/constants.ts index 5ea335ab..781be8a6 100644 --- a/apps/desktop/src/features/docker-manager/constants.ts +++ b/apps/desktop/src/features/docker-manager/constants.ts @@ -1,4 +1,9 @@ -import type { GeneratorProfile, GeneratorScale, ContainerSortConfig, ContainerFilterConfig } from './types' +import type { + GeneratorProfile, + GeneratorScale, + ContainerSortConfig, + ContainerFilterConfig +} from './types' export const CONTAINER_PREFIX = 'dora_' diff --git a/apps/desktop/src/features/docker-manager/stores/docker-manager-store.ts b/apps/desktop/src/features/docker-manager/stores/docker-manager-store.ts index 87878afc..05a39e9c 100644 --- a/apps/desktop/src/features/docker-manager/stores/docker-manager-store.ts +++ b/apps/desktop/src/features/docker-manager/stores/docker-manager-store.ts @@ -56,7 +56,8 @@ export const useDockerManagerStore = create p.containerPort === 5432) const host = 'localhost' const port = primaryPort?.hostPort ?? 5432 - const user = container.env.find((e) => e.startsWith('POSTGRES_USER='))?.split('=')[1] || 'postgres' - const database = container.env.find((e) => e.startsWith('POSTGRES_DB='))?.split('=')[1] || 'postgres' + const user = + container.env.find((e) => e.startsWith('POSTGRES_USER='))?.split('=')[1] || 'postgres' + const database = + container.env.find((e) => e.startsWith('POSTGRES_DB='))?.split('=')[1] || 'postgres' const envVars = buildConnectionEnvVars(host, port, user, password, database) const url = envVars.DATABASE_URL diff --git a/apps/desktop/src/features/drizzle-runner/components/code-editor.tsx b/apps/desktop/src/features/drizzle-runner/components/code-editor.tsx index 94f09e1f..4fb89415 100644 --- a/apps/desktop/src/features/drizzle-runner/components/code-editor.tsx +++ b/apps/desktop/src/features/drizzle-runner/components/code-editor.tsx @@ -21,7 +21,11 @@ import { isInsideJoinParens } from '../utils/lsp-patterns' import { generateDrizzleTypes } from '../utils/lsp-utils' -import { detectTyposInQuery, getSuggestions, getTableNames as getFuzzyTableNames } from '../utils/fuzzy-match' +import { + detectTyposInQuery, + getSuggestions, + getTableNames as getFuzzyTableNames +} from '../utils/fuzzy-match' import { LspDemoWidget } from './lsp-demo-widget' type Props = { diff --git a/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.ts b/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.ts index 172d3c67..06b9ae33 100644 --- a/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.ts +++ b/apps/desktop/src/features/drizzle-runner/utils/fuzzy-match.ts @@ -1,289 +1,458 @@ export function levenshtein(a: string, b: string): number { - const aLower = a.toLowerCase() - const bLower = b.toLowerCase() - - if (aLower === bLower) return 0 - if (aLower.length === 0) return bLower.length - if (bLower.length === 0) return aLower.length - - const matrix: number[][] = [] - - for (let i = 0; i <= bLower.length; i++) { - matrix[i] = [i] - } - - for (let j = 0; j <= aLower.length; j++) { - matrix[0][j] = j - } - - for (let i = 1; i <= bLower.length; i++) { - for (let j = 1; j <= aLower.length; j++) { - if (bLower.charAt(i - 1) === aLower.charAt(j - 1)) { - matrix[i][j] = matrix[i - 1][j - 1] - } else { - matrix[i][j] = Math.min( - matrix[i - 1][j - 1] + 1, - matrix[i][j - 1] + 1, - matrix[i - 1][j] + 1 - ) - } - } - } - - return matrix[bLower.length][aLower.length] + const aLower = a.toLowerCase() + const bLower = b.toLowerCase() + + if (aLower === bLower) return 0 + if (aLower.length === 0) return bLower.length + if (bLower.length === 0) return aLower.length + + const matrix: number[][] = [] + + for (let i = 0; i <= bLower.length; i++) { + matrix[i] = [i] + } + + for (let j = 0; j <= aLower.length; j++) { + matrix[0][j] = j + } + + for (let i = 1; i <= bLower.length; i++) { + for (let j = 1; j <= aLower.length; j++) { + if (bLower.charAt(i - 1) === aLower.charAt(j - 1)) { + matrix[i][j] = matrix[i - 1][j - 1] + } else { + matrix[i][j] = Math.min( + matrix[i - 1][j - 1] + 1, + matrix[i][j - 1] + 1, + matrix[i - 1][j] + 1 + ) + } + } + } + + return matrix[bLower.length][aLower.length] } export function similarity(a: string, b: string, distance?: number): number { - const maxLen = Math.max(a.length, b.length) - if (maxLen === 0) return 1 - const computedDistance = distance ?? levenshtein(a, b) - return 1 - computedDistance / maxLen + const maxLen = Math.max(a.length, b.length) + if (maxLen === 0) return 1 + const computedDistance = distance ?? levenshtein(a, b) + return 1 - computedDistance / maxLen } type FuzzyMatch = { - value: string - distance: number - similarity: number + value: string + distance: number + similarity: number } export function findClosestMatch( - input: string, - candidates: string[], - maxDistance: number = 2 + input: string, + candidates: string[], + maxDistance: number = 2 ): FuzzyMatch | null { - if (!input || candidates.length === 0) return null - - let bestMatch: FuzzyMatch | null = null - - for (const candidate of candidates) { - const distance = levenshtein(input, candidate) - - if (distance <= maxDistance) { - const sim = similarity(input, candidate, distance) - if (!bestMatch || distance < bestMatch.distance) { - bestMatch = { - value: candidate, - distance: distance, - similarity: sim - } - } - } - } - - return bestMatch + if (!input || candidates.length === 0) return null + + let bestMatch: FuzzyMatch | null = null + + for (const candidate of candidates) { + const distance = levenshtein(input, candidate) + + if (distance <= maxDistance) { + const sim = similarity(input, candidate, distance) + if (!bestMatch || distance < bestMatch.distance) { + bestMatch = { + value: candidate, + distance: distance, + similarity: sim + } + } + } + } + + return bestMatch } export function getSuggestions( - input: string, - candidates: string[], - maxResults: number = 3, - maxDistance: number = 3 + input: string, + candidates: string[], + maxResults: number = 3, + maxDistance: number = 3 ): FuzzyMatch[] { - if (!input || candidates.length === 0) return [] + if (!input || candidates.length === 0) return [] - const matches: FuzzyMatch[] = [] + const matches: FuzzyMatch[] = [] - for (const candidate of candidates) { - const distance = levenshtein(input, candidate) + for (const candidate of candidates) { + const distance = levenshtein(input, candidate) - if (distance <= maxDistance && distance > 0) { - matches.push({ - value: candidate, - distance: distance, - similarity: similarity(input, candidate, distance) - }) - } - } + if (distance <= maxDistance && distance > 0) { + matches.push({ + value: candidate, + distance: distance, + similarity: similarity(input, candidate, distance) + }) + } + } - matches.sort(function (a, b) { - return a.distance - b.distance - }) + matches.sort(function (a, b) { + return a.distance - b.distance + }) - return matches.slice(0, maxResults) + return matches.slice(0, maxResults) } type TypoInfo = { - word: string - startIndex: number - endIndex: number - suggestion: string | null - distance: number + word: string + startIndex: number + endIndex: number + suggestion: string | null + distance: number } type LspConfig = { - reservedWords: Set - identifiers: string[] - tableNames: string[] + reservedWords: Set + identifiers: string[] + tableNames: string[] } export function createTypoDetector(config: LspConfig) { - const reservedLower = new Set( - Array.from(config.reservedWords, function (word) { - return word.toLowerCase() - }) - ) - - return function detectTypos(query: string): TypoInfo[] { - const typos: TypoInfo[] = [] - const identifierPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\b/g - - let match - while ((match = identifierPattern.exec(query)) !== null) { - const word = match[1] - const startIndex = match.index - const endIndex = startIndex + word.length - - if (reservedLower.has(word.toLowerCase())) continue - if (/^\d/.test(word)) continue - - const isDotNotation = word.includes('.') - let candidates: string[] - let isExactMatch: boolean - - if (isDotNotation) { - candidates = config.identifiers - isExactMatch = config.identifiers.includes(word) - } else { - candidates = config.tableNames - isExactMatch = config.tableNames.includes(word) - } - - if (!isExactMatch && candidates.length > 0) { - const closest = findClosestMatch(word, candidates, 2) - if (closest) { - typos.push({ - word: word, - startIndex: startIndex, - endIndex: endIndex, - suggestion: closest.value, - distance: closest.distance - }) - } - } - } - - return typos - } + const reservedLower = new Set( + Array.from(config.reservedWords, function (word) { + return word.toLowerCase() + }) + ) + + return function detectTypos(query: string): TypoInfo[] { + const typos: TypoInfo[] = [] + const identifierPattern = /\b([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\b/g + + let match + while ((match = identifierPattern.exec(query)) !== null) { + const word = match[1] + const startIndex = match.index + const endIndex = startIndex + word.length + + if (reservedLower.has(word.toLowerCase())) continue + if (/^\d/.test(word)) continue + + const isDotNotation = word.includes('.') + let candidates: string[] + let isExactMatch: boolean + + if (isDotNotation) { + candidates = config.identifiers + isExactMatch = config.identifiers.includes(word) + } else { + candidates = config.tableNames + isExactMatch = config.tableNames.includes(word) + } + + if (!isExactMatch && candidates.length > 0) { + const closest = findClosestMatch(word, candidates, 2) + if (closest) { + typos.push({ + word: word, + startIndex: startIndex, + endIndex: endIndex, + suggestion: closest.value, + distance: closest.distance + }) + } + } + } + + return typos + } } const DRIZZLE_RESERVED = new Set([ - 'db', 'tx', 'select', 'insert', 'update', 'delete', 'from', 'where', - 'orderBy', 'groupBy', 'limit', 'offset', 'leftJoin', 'innerJoin', - 'rightJoin', 'fullJoin', 'values', 'set', 'returning', 'execute', - 'eq', 'ne', 'gt', 'gte', 'lt', 'lte', 'like', 'ilike', 'inArray', - 'notInArray', 'isNull', 'isNotNull', 'and', 'or', 'not', 'asc', 'desc', - 'count', 'sum', 'avg', 'min', 'max', 'param', 'true', 'false', 'null', - 'new', 'Date', 'const', 'let', 'var', 'function', 'async', 'await', - 'return', 'if', 'else', 'for', 'while', 'break', 'continue', 'batch', - 'transaction', 'having', 'union', 'unionAll', 'intersect', 'except', - 'sql', 'raw' + 'db', + 'tx', + 'select', + 'insert', + 'update', + 'delete', + 'from', + 'where', + 'orderBy', + 'groupBy', + 'limit', + 'offset', + 'leftJoin', + 'innerJoin', + 'rightJoin', + 'fullJoin', + 'values', + 'set', + 'returning', + 'execute', + 'eq', + 'ne', + 'gt', + 'gte', + 'lt', + 'lte', + 'like', + 'ilike', + 'inArray', + 'notInArray', + 'isNull', + 'isNotNull', + 'and', + 'or', + 'not', + 'asc', + 'desc', + 'count', + 'sum', + 'avg', + 'min', + 'max', + 'param', + 'true', + 'false', + 'null', + 'new', + 'Date', + 'const', + 'let', + 'var', + 'function', + 'async', + 'await', + 'return', + 'if', + 'else', + 'for', + 'while', + 'break', + 'continue', + 'batch', + 'transaction', + 'having', + 'union', + 'unionAll', + 'intersect', + 'except', + 'sql', + 'raw' ]) const PRISMA_RESERVED = new Set([ - 'prisma', 'findMany', 'findUnique', 'findFirst', 'findFirstOrThrow', - 'findUniqueOrThrow', 'create', 'createMany', 'update', 'updateMany', - 'upsert', 'delete', 'deleteMany', 'aggregate', 'groupBy', 'count', - 'where', 'select', 'include', 'orderBy', 'skip', 'take', 'cursor', - 'distinct', 'data', 'contains', 'startsWith', 'endsWith', 'equals', - 'not', 'in', 'notIn', 'lt', 'lte', 'gt', 'gte', 'AND', 'OR', 'NOT', - 'true', 'false', 'null', 'const', 'let', 'var', 'function', 'async', - 'await', 'return', 'if', 'else', 'for', 'while', 'new', 'Date', - 'connect', 'disconnect', 'set', 'push', 'unset', 'increment', 'decrement', - 'multiply', 'divide', 'createManyAndReturn', 'stream' + 'prisma', + 'findMany', + 'findUnique', + 'findFirst', + 'findFirstOrThrow', + 'findUniqueOrThrow', + 'create', + 'createMany', + 'update', + 'updateMany', + 'upsert', + 'delete', + 'deleteMany', + 'aggregate', + 'groupBy', + 'count', + 'where', + 'select', + 'include', + 'orderBy', + 'skip', + 'take', + 'cursor', + 'distinct', + 'data', + 'contains', + 'startsWith', + 'endsWith', + 'equals', + 'not', + 'in', + 'notIn', + 'lt', + 'lte', + 'gt', + 'gte', + 'AND', + 'OR', + 'NOT', + 'true', + 'false', + 'null', + 'const', + 'let', + 'var', + 'function', + 'async', + 'await', + 'return', + 'if', + 'else', + 'for', + 'while', + 'new', + 'Date', + 'connect', + 'disconnect', + 'set', + 'push', + 'unset', + 'increment', + 'decrement', + 'multiply', + 'divide', + 'createManyAndReturn', + 'stream' ]) const SQL_RESERVED = new Set([ - 'SELECT', 'FROM', 'WHERE', 'INSERT', 'UPDATE', 'DELETE', 'INTO', - 'VALUES', 'SET', 'JOIN', 'LEFT', 'RIGHT', 'INNER', 'OUTER', 'FULL', - 'ON', 'AND', 'OR', 'NOT', 'IN', 'LIKE', 'BETWEEN', 'IS', 'NULL', - 'ORDER', 'BY', 'ASC', 'DESC', 'GROUP', 'HAVING', 'LIMIT', 'OFFSET', - 'UNION', 'INTERSECT', 'EXCEPT', 'AS', 'DISTINCT', 'COUNT', 'SUM', - 'AVG', 'MIN', 'MAX', 'CREATE', 'TABLE', 'ALTER', 'DROP', 'INDEX', - 'PRIMARY', 'KEY', 'FOREIGN', 'REFERENCES', 'CONSTRAINT', 'CASCADE', - 'RETURNING', 'WITH', 'RECURSIVE', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END' + 'SELECT', + 'FROM', + 'WHERE', + 'INSERT', + 'UPDATE', + 'DELETE', + 'INTO', + 'VALUES', + 'SET', + 'JOIN', + 'LEFT', + 'RIGHT', + 'INNER', + 'OUTER', + 'FULL', + 'ON', + 'AND', + 'OR', + 'NOT', + 'IN', + 'LIKE', + 'BETWEEN', + 'IS', + 'NULL', + 'ORDER', + 'BY', + 'ASC', + 'DESC', + 'GROUP', + 'HAVING', + 'LIMIT', + 'OFFSET', + 'UNION', + 'INTERSECT', + 'EXCEPT', + 'AS', + 'DISTINCT', + 'COUNT', + 'SUM', + 'AVG', + 'MIN', + 'MAX', + 'CREATE', + 'TABLE', + 'ALTER', + 'DROP', + 'INDEX', + 'PRIMARY', + 'KEY', + 'FOREIGN', + 'REFERENCES', + 'CONSTRAINT', + 'CASCADE', + 'RETURNING', + 'WITH', + 'RECURSIVE', + 'CASE', + 'WHEN', + 'THEN', + 'ELSE', + 'END' ]) type SchemaLike = { - name: string - columns: Array<{ name: string }> + name: string + columns: Array<{ name: string }> } function extractIdentifiers(tables: SchemaLike[]): string[] { - const identifiers: string[] = [] - for (const table of tables) { - identifiers.push(table.name) - for (const column of table.columns) { - identifiers.push(`${table.name}.${column.name}`) - } - } - return identifiers + const identifiers: string[] = [] + for (const table of tables) { + identifiers.push(table.name) + for (const column of table.columns) { + identifiers.push(`${table.name}.${column.name}`) + } + } + return identifiers } function extractTableNames(tables: SchemaLike[]): string[] { - return tables.map(function (table) { - return table.name - }) + return tables.map(function (table) { + return table.name + }) } export function createDrizzleTypoDetector(tables: SchemaLike[]) { - return createTypoDetector({ - reservedWords: DRIZZLE_RESERVED, - identifiers: extractIdentifiers(tables), - tableNames: extractTableNames(tables) - }) + return createTypoDetector({ + reservedWords: DRIZZLE_RESERVED, + identifiers: extractIdentifiers(tables), + tableNames: extractTableNames(tables) + }) } export function createPrismaTypoDetector(models: SchemaLike[]) { - return createTypoDetector({ - reservedWords: PRISMA_RESERVED, - identifiers: extractIdentifiers(models), - tableNames: extractTableNames(models) - }) + return createTypoDetector({ + reservedWords: PRISMA_RESERVED, + identifiers: extractIdentifiers(models), + tableNames: extractTableNames(models) + }) } export function createSqlTypoDetector(tables: SchemaLike[]) { - return createTypoDetector({ - reservedWords: SQL_RESERVED, - identifiers: extractIdentifiers(tables), - tableNames: extractTableNames(tables) - }) + return createTypoDetector({ + reservedWords: SQL_RESERVED, + identifiers: extractIdentifiers(tables), + tableNames: extractTableNames(tables) + }) } export function getTableNames(tables: SchemaLike[]): string[] { - return extractTableNames(tables) + return extractTableNames(tables) } export function getColumnNames(tables: SchemaLike[], tableName: string): string[] { - const table = tables.find(function (t) { - return t.name === tableName - }) - if (!table) return [] - return table.columns.map(function (col) { - return col.name - }) + const table = tables.find(function (t) { + return t.name === tableName + }) + if (!table) return [] + return table.columns.map(function (col) { + return col.name + }) } export function detectTyposInQuery(query: string, tables: SchemaLike[]): TypoInfo[] { - const detector = getOrCreateDrizzleDetector(tables) - return detector(query) + const detector = getOrCreateDrizzleDetector(tables) + return detector(query) } const drizzleDetectorCache = new Map>() function getOrCreateDrizzleDetector(tables: SchemaLike[]) { - const signature = tables - .map(function (table) { - const columnNames = table.columns.map(function (col) { - return col.name - }) - return `${table.name}:${columnNames.join(',')}` - }) - .join('|') - - const cached = drizzleDetectorCache.get(signature) - if (cached) return cached - - const detector = createDrizzleTypoDetector(tables) - drizzleDetectorCache.set(signature, detector) - return detector + const signature = tables + .map(function (table) { + const columnNames = table.columns.map(function (col) { + return col.name + }) + return `${table.name}:${columnNames.join(',')}` + }) + .join('|') + + const cached = drizzleDetectorCache.get(signature) + if (cached) return cached + + const detector = createDrizzleTypoDetector(tables) + drizzleDetectorCache.set(signature, detector) + return detector } diff --git a/apps/desktop/src/features/sidebar/changelog-data.ts b/apps/desktop/src/features/sidebar/changelog-data.ts index bc14671f..6ef68184 100644 --- a/apps/desktop/src/features/sidebar/changelog-data.ts +++ b/apps/desktop/src/features/sidebar/changelog-data.ts @@ -8,13 +8,87 @@ export type ChangelogEntry = { details?: string[] } -export const CURRENT_VERSION = '0.0.97' +export const CURRENT_VERSION = '0.0.102' export const CHANGELOG: ChangelogEntry[] = [ + { + version: '0.0.102', + date: '2026-04-05', + commit: 'v0.0.102', + title: 'Snap Workflow Follow-up & Packaging Release Cleanup', + description: + 'Fixes the Snap CI path and cuts a clean follow-up release tag that includes the packaging automation work already on branch head.', + type: 'fix', + details: [ + 'Fixed Snapcraft CI execution to run correctly in destructive mode on GitHub Actions', + 'Replaced the invalid Tauri bundle flag with a direct Rust release build for Snap packaging', + 'Carries the packaging automation, release guide, and VM lab tooling into a clean release tag' + ] + }, + { + version: '0.0.101', + date: '2026-04-05', + commit: 'v0.0.101', + title: 'Packaging Automation, VM Lab & Desktop Iteration', + description: + 'Adds package-manager release helpers, Ubuntu-host VM workflows, refreshed release surfaces, and folds the current desktop/docs/test iteration into the 0.0.101 release.', + type: 'feature', + details: [ + 'Added release checksum generation plus Winget, AUR, and Snap packaging helpers', + 'Added interactive release guide and Ubuntu-host VM lab tooling', + 'Updated changelog/release surfaces for the 0.0.101 release', + 'Carried forward the current desktop, docs, test, and workflow iteration work on this branch' + ] + }, + { + version: '0.1.0', + date: '2026-04-04', + commit: 'eff2c05', + title: 'Project Foundation & Documentation Audit', + description: + 'Milestone release establishing the 0.1.x baseline with comprehensive installation paths, updated audit snapshots, and visual showcase enhancements.', + type: 'feature', + details: [ + 'Established 0.1.0 version baseline across monorepo', + 'Completed Homebrew installation documentation and README overhaul', + 'Verified all 115/115 tests passing for the new milestone' + ] + }, + { + version: '0.0.99', + date: '2026-04-04', + commit: '852360f', + title: 'Homebrew Support & CI Security Hardening', + description: + 'Launched the official Homebrew Tap for easy installation, and fortified the CI/CD pipeline with pinned action SHAs and build target optimizations.', + type: 'feature', + details: [ + 'Added official Homebrew Tap at remcostoeten/homebrew-dora (brew install dora)', + 'Pinned GitHub Actions to specific commit SHAs for improved supply chain security', + 'Optimized esbuild target to esnext to resolve CI transform issues', + 'Stabilized Rust toolchain branch reference in CI workflows' + ] + }, + { + version: '0.0.98', + date: '2026-04-04', + commit: '7b490a0', + title: 'Live Database Updates & Performance Refactor', + description: + 'Introduced real-time database synchronization through a new backend live monitor manager, replacing legacy frontend polling for significant performance gains.', + type: 'feature', + details: [ + 'Implemented backend-driven live database monitor manager', + 'Removed inefficient frontend polling logic to reduce CPU usage', + 'Added real-time data grid updates on external database changes', + 'Updated documentation with animated WEBP showcase demonstrating live performance', + 'Removed legacy bloat and optimized workspace checkpointing' + ] + }, { version: '0.0.97', date: '2026-02-20', - commit: 'unreleased', + commit: '48e798e', title: 'Type Safety Recovery, Feature-State Cleanup & Docker Enhancements', description: 'Restored strict TypeScript health, aligned sidebar feature states with actual availability, and shipped Docker manager improvements including terminal support and UX polish', diff --git a/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx b/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx index 03fcb62f..f25ecd90 100644 --- a/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx +++ b/apps/desktop/src/features/sidebar/components/bottom-toolbar.tsx @@ -49,7 +49,6 @@ function useCurrentTheme() { return theme } - type Props = { onAction?: (action: ToolbarAction) => void } diff --git a/apps/desktop/src/features/sidebar/components/changelog-panel.tsx b/apps/desktop/src/features/sidebar/components/changelog-panel.tsx index c2b82d84..50f18cd7 100644 --- a/apps/desktop/src/features/sidebar/components/changelog-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/changelog-panel.tsx @@ -1,4 +1,11 @@ -import { Sparkles, Wrench, RefreshCw, AlertTriangle, ChevronRight, ExternalLink } from 'lucide-react' +import { + Sparkles, + Wrench, + RefreshCw, + AlertTriangle, + ChevronRight, + ExternalLink +} from 'lucide-react' import { Badge } from '@/shared/ui/badge' import { cn } from '@/shared/utils/cn' import { CHANGELOG, CURRENT_VERSION, ChangelogEntry } from '../changelog-data' @@ -45,10 +52,17 @@ function formatDate(dateString: string): string { }) } +function getEntryUrl(entry: ChangelogEntry): string { + if (entry.commit.startsWith('v')) { + return `${siteConfig.links.github}/releases/tag/${entry.commit}` + } + return `${siteConfig.links.github}/commit/${entry.commit}` +} + export function ChangelogPanel({ maxHeight = 500 }: Props) { const [expandedVersions, setExpandedVersions] = useState>(new Set()) - const toggleVersion = (version: string) => { + function toggleVersion(version: string) { const newExpanded = new Set(expandedVersions) if (newExpanded.has(version)) { newExpanded.delete(version) @@ -82,8 +96,18 @@ export function ChangelogPanel({ maxHeight = 500 }: Props) { > {/* Main Card Content - Clickable */}
toggleVersion(entry.version)} - className='p-3 cursor-pointer' + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault() + toggleVersion(entry.version) + } + }} + className='p-3 cursor-pointer outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-1 rounded-md transition-all' + aria-expanded={isExpanded} + aria-controls={`changelog-details-${entry.version}`} >
- +
@@ -118,6 +142,7 @@ export function ChangelogPanel({ maxHeight = 500 }: Props) { 'h-3.5 w-3.5 text-muted-foreground transition-transform duration-200', isExpanded && 'rotate-90' )} + aria-hidden='true' /> )}
@@ -137,14 +162,18 @@ export function ChangelogPanel({ maxHeight = 500 }: Props) { {formatDate(entry.date)} • e.stopPropagation()} - className='font-mono bg-muted/50 px-1.5 py-0.5 rounded hover:bg-muted hover:text-primary transition-colors inline-flex items-center gap-1 group/link' + onKeyDown={(e) => e.stopPropagation()} + className='font-mono bg-muted/50 px-1.5 py-0.5 rounded focus-visible:ring-1 focus-visible:ring-ring hover:bg-muted hover:text-primary transition-colors inline-flex items-center gap-1 group/link' > {entry.commit} - +
@@ -153,7 +182,10 @@ export function ChangelogPanel({ maxHeight = 500 }: Props) { {/* Expanded Details */} {isExpanded && entry.details && ( -
+
    {entry.details.map((detail, i) => ( diff --git a/apps/desktop/src/features/sidebar/components/project-info-panel.tsx b/apps/desktop/src/features/sidebar/components/project-info-panel.tsx index 079354e8..94e04d30 100644 --- a/apps/desktop/src/features/sidebar/components/project-info-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/project-info-panel.tsx @@ -1,84 +1,80 @@ -import { Github, ExternalLink, Play, Globe } from "lucide-react" -import { - SidebarPanel, - SidebarPanelHeader, - SidebarPanelContent -} from "./sidebar-panel" -import { siteConfig } from "@/config/site" +import { Github, ExternalLink, Play, Globe } from 'lucide-react' +import { SidebarPanel, SidebarPanelHeader, SidebarPanelContent } from './sidebar-panel' +import { siteConfig } from '@/config/site' type Props = { - className?: string - maxHeight?: number + className?: string + maxHeight?: number } export function ProjectInfoPanel({ className, maxHeight = 400 }: Props) { - return ( - - - -
    -
    - - {siteConfig.demos && siteConfig.demos.length > 0 && ( -
    -

    - Live Demos -

    -
    - {siteConfig.demos.map(demo => ( - - - {demo.name} - - - ))} -
    -
    - )} -
    - - - ) + {siteConfig.demos && siteConfig.demos.length > 0 && ( +
    +

    + Live Demos +

    +
    + {siteConfig.demos.map((demo) => ( + + + {demo.name} + + + ))} +
    +
    + )} +
    +
    +
    + ) } diff --git a/apps/desktop/src/features/sidebar/components/settings-panel.tsx b/apps/desktop/src/features/sidebar/components/settings-panel.tsx index d3e306fd..bb887c4f 100644 --- a/apps/desktop/src/features/sidebar/components/settings-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/settings-panel.tsx @@ -1,10 +1,5 @@ import { useSettings } from '@/core/settings' -import { - useShortcutStore, - useEffectiveShortcuts, - APP_SHORTCUTS, - ShortcutName -} from '@/core/shortcuts' +import { useShortcutStore, useEffectiveShortcuts, ShortcutName } from '@/core/shortcuts' import { Select, SelectContent, @@ -165,7 +160,9 @@ export function SettingsPanel() {
    -
    Connection behavior
    +
    + Connection behavior +
    Auto-connect to last used database (falls back to first), or start on an empty view diff --git a/apps/desktop/src/features/sidebar/components/shortcut-recorder.tsx b/apps/desktop/src/features/sidebar/components/shortcut-recorder.tsx index 654410d0..bdd24177 100644 --- a/apps/desktop/src/features/sidebar/components/shortcut-recorder.tsx +++ b/apps/desktop/src/features/sidebar/components/shortcut-recorder.tsx @@ -5,119 +5,129 @@ import { Button } from '@/shared/ui/button' import { cn } from '@/shared/utils/cn' type Props = { - value: string | string[] - onChange: (value: string) => void - onReset: () => void - isDefault: boolean + value: string | string[] + onChange: (value: string) => void + onReset: () => void + isDefault: boolean } export function ShortcutRecorder({ value, onChange, onReset, isDefault }: Props) { - const [isRecording, setIsRecording] = useState(false) - const [tempCombo, setTempCombo] = useState(null) - const buttonRef = useRef(null) - - useEffect(function () { - if (!isRecording) return - - function handleKeyDown(e: KeyboardEvent) { - e.preventDefault() - e.stopPropagation() - - // Ignore standalone (non-modifier) key releases or just plain modifiers being pressed - if (['Control', 'Shift', 'Alt', 'Meta', 'AltGraph', 'ContextMenu'].includes(e.key)) { - return - } - - const modifiers: string[] = [] - if (e.ctrlKey) modifiers.push('ctrl') - if (e.metaKey) modifiers.push('mod') // map meta to mod for cross-platform - if (e.altKey) modifiers.push('alt') - if (e.shiftKey) modifiers.push('shift') - - let key = e.key.toLowerCase() - - // Map special keys - if (key === ' ') key = 'space' - if (key === 'escape') key = 'escape' - if (key === 'arrowup') key = 'up' - if (key === 'arrowdown') key = 'down' - if (key === 'arrowleft') key = 'left' - if (key === 'arrowright') key = 'right' - - const combo = [...modifiers, key].join('+') - setTempCombo(combo) - } - - function handleKeyUp(e: KeyboardEvent) { - if (tempCombo) { - onChange(tempCombo) - setIsRecording(false) - setTempCombo(null) - } - } - - window.addEventListener('keydown', handleKeyDown) - window.addEventListener('keyup', handleKeyUp) - - return function () { - window.removeEventListener('keydown', handleKeyDown) - window.removeEventListener('keyup', handleKeyUp) - } - }, [isRecording, tempCombo, onChange]) - - // Handle clicking outside to cancel - useEffect(function () { - if (!isRecording) return - - function handleClickOutside(e: MouseEvent) { - if (buttonRef.current && !buttonRef.current.contains(e.target as Node)) { - setIsRecording(false) - setTempCombo(null) - } - } - - window.addEventListener('mousedown', handleClickOutside) - return function () { - window.removeEventListener('mousedown', handleClickOutside) - } - }, [isRecording]) - - const displayValue = tempCombo || (Array.isArray(value) ? value[0] : value) - const formatted = formatShortcut(displayValue || '') - - return ( -
    - - - {!isDefault && ( - - )} -
    - ) + const [isRecording, setIsRecording] = useState(false) + const [tempCombo, setTempCombo] = useState(null) + const buttonRef = useRef(null) + + useEffect( + function () { + if (!isRecording) return + + function handleKeyDown(e: KeyboardEvent) { + e.preventDefault() + e.stopPropagation() + + // Ignore standalone (non-modifier) key releases or just plain modifiers being pressed + if ( + ['Control', 'Shift', 'Alt', 'Meta', 'AltGraph', 'ContextMenu'].includes(e.key) + ) { + return + } + + const modifiers: string[] = [] + if (e.ctrlKey) modifiers.push('ctrl') + if (e.metaKey) modifiers.push('mod') // map meta to mod for cross-platform + if (e.altKey) modifiers.push('alt') + if (e.shiftKey) modifiers.push('shift') + + let key = e.key.toLowerCase() + + // Map special keys + if (key === ' ') key = 'space' + if (key === 'escape') key = 'escape' + if (key === 'arrowup') key = 'up' + if (key === 'arrowdown') key = 'down' + if (key === 'arrowleft') key = 'left' + if (key === 'arrowright') key = 'right' + + const combo = [...modifiers, key].join('+') + setTempCombo(combo) + } + + function handleKeyUp(_e: KeyboardEvent) { + if (tempCombo) { + onChange(tempCombo) + setIsRecording(false) + setTempCombo(null) + } + } + + window.addEventListener('keydown', handleKeyDown) + window.addEventListener('keyup', handleKeyUp) + + return function () { + window.removeEventListener('keydown', handleKeyDown) + window.removeEventListener('keyup', handleKeyUp) + } + }, + [isRecording, tempCombo, onChange] + ) + + // Handle clicking outside to cancel + useEffect( + function () { + if (!isRecording) return + + function handleClickOutside(e: MouseEvent) { + if (buttonRef.current && !buttonRef.current.contains(e.target as Node)) { + setIsRecording(false) + setTempCombo(null) + } + } + + window.addEventListener('mousedown', handleClickOutside) + return function () { + window.removeEventListener('mousedown', handleClickOutside) + } + }, + [isRecording] + ) + + const displayValue = tempCombo || (Array.isArray(value) ? value[0] : value) + const formatted = formatShortcut(displayValue || '') + + return ( +
    + + + {!isDefault && ( + + )} +
    + ) } 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 da2b1eff..6de28a30 100644 --- a/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx +++ b/apps/desktop/src/features/sidebar/components/sidebar-bottom-panel.tsx @@ -139,15 +139,23 @@ export function SidebarBottomPanel({ table }: Props) { className='flex flex-col gap-1 px-3 py-2 border-b border-border/50 last:border-0 hover:bg-muted/30' >
    - {idx.name} + + {idx.name} +
    {idx.is_primary && ( - + PK )} {idx.is_unique && !idx.is_primary && ( - + UQ )} diff --git a/apps/desktop/src/features/sidebar/database-sidebar.tsx b/apps/desktop/src/features/sidebar/database-sidebar.tsx index 46be4082..9369d6a5 100644 --- a/apps/desktop/src/features/sidebar/database-sidebar.tsx +++ b/apps/desktop/src/features/sidebar/database-sidebar.tsx @@ -68,9 +68,9 @@ export function DatabaseSidebar({ onAutoSelectComplete, connections = [], activeConnectionId, - onConnectionSelect = function () { }, - onAddConnection = function () { }, - onManageConnections = function () { }, + onConnectionSelect = function () {}, + onAddConnection = function () {}, + onManageConnections = function () {}, onViewConnection, onEditConnection, onDeleteConnection @@ -375,7 +375,10 @@ export function DatabaseSidebar({ if (activeTableId === targetTableName) { setInternalTableId(undefined) } - toast({ title: 'Table dropped', description: `"${targetTableName}" has been removed.` }) + toast({ + title: 'Table dropped', + description: `"${targetTableName}" has been removed.` + }) } else { const errorMessage = getAdapterError(result) console.error('Failed to drop table:', errorMessage) @@ -387,7 +390,11 @@ export function DatabaseSidebar({ } } catch (error) { console.error('Failed to drop table:', error) - toast({ title: 'Failed to drop table', description: String(error), variant: 'destructive' }) + toast({ + title: 'Failed to drop table', + description: String(error), + variant: 'destructive' + }) } finally { setIsDdlLoading(false) } @@ -404,7 +411,8 @@ export function DatabaseSidebar({ if (activeConnection?.type !== 'postgres') { toast({ title: 'Duplicate table not available', - description: 'Table duplication is currently supported for PostgreSQL connections only.', + description: + 'Table duplication is currently supported for PostgreSQL connections only.', variant: 'destructive' }) return @@ -623,19 +631,19 @@ export function DatabaseSidebar({ useEffect(() => { if (!isResizing) return - const handleMouseMove = (e: MouseEvent) => { + function 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? + // 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 = () => { + function handleMouseUp() { setIsResizing(false) document.body.style.cursor = '' } @@ -731,7 +739,9 @@ export function DatabaseSidebar({
    -

    Connection failed

    +

    + Connection failed +

    {schemaError}

    @@ -860,7 +870,9 @@ export function DatabaseSidebar({ - {bulkActionConfirm.action === 'drop' ? 'Drop Tables' : 'Truncate Tables'} + {bulkActionConfirm.action === 'drop' + ? 'Drop Tables' + : 'Truncate Tables'} {bulkActionConfirm.action === 'drop' diff --git a/apps/desktop/src/features/sql-console/components/console-toolbar.tsx b/apps/desktop/src/features/sql-console/components/console-toolbar.tsx index bbaed542..23e3ee34 100644 --- a/apps/desktop/src/features/sql-console/components/console-toolbar.tsx +++ b/apps/desktop/src/features/sql-console/components/console-toolbar.tsx @@ -42,7 +42,6 @@ type Props = { onSave?: () => void } - function Kbd({ children, className }: { children: React.ReactNode; className?: string }) { return ( {/* Left side - sidebar toggles */} @@ -157,7 +155,6 @@ export function ConsoleToolbar({
    {onRun && ( - - )} -
    + return ( +
    +
    +
    + + History +
    + {filteredHistory.length > 0 && ( + + )} +
    - - {filteredHistory.length === 0 ? ( -
    - - No query history -
    - ) : ( -
    - {filteredHistory.map(function (item) { - return ( -
    -
    - {item.success ? ( - - ) : ( - - )} - - {truncateQuery(item.query)} - - -
    -
    - - {formatTimestamp(item.timestamp)} - - • - - {formatDuration(item.executionTimeMs)} - - {item.rowCount !== undefined && ( - <> - • - - {item.rowCount} rows - - - )} -
    -
    - ) - })} -
    - )} -
    -
    - ) + + {filteredHistory.length === 0 ? ( +
    + + No query history +
    + ) : ( +
    + {filteredHistory.map(function (item) { + return ( +
    +
    + {item.success ? ( + + ) : ( + + )} + + {truncateQuery(item.query)} + + +
    +
    + + {formatTimestamp(item.timestamp)} + + • + + {formatDuration(item.executionTimeMs)} + + {item.rowCount !== undefined && ( + <> + + • + + + {item.rowCount} rows + + + )} +
    +
    + ) + })} +
    + )} +
    +
    + ) } diff --git a/apps/desktop/src/features/sql-console/components/sql-editor.tsx b/apps/desktop/src/features/sql-console/components/sql-editor.tsx index fe51386b..3c355e54 100644 --- a/apps/desktop/src/features/sql-console/components/sql-editor.tsx +++ b/apps/desktop/src/features/sql-console/components/sql-editor.tsx @@ -59,7 +59,8 @@ type MonacoApi = Parameters[1] function getAliases(text: string): Map { const aliases = new Map() - const regex = /\b(?:from|join|update|into)\s+([a-zA-Z_][\w$]*)(?:\s+(?:as\s+)?([a-zA-Z_][\w$]*))?/gi + const regex = + /\b(?:from|join|update|into)\s+([a-zA-Z_][\w$]*)(?:\s+(?:as\s+)?([a-zA-Z_][\w$]*))?/gi for (const match of text.matchAll(regex)) { const table = match[1] @@ -285,8 +286,9 @@ export function SqlEditor({ value, onChange, onExecute, isExecuting, tables }: P return { suggestions } } - const wantsTables = - /\b(from|join|update|into|table)\s+[a-zA-Z_0-9]*$/i.test(beforeCursor) + const wantsTables = /\b(from|join|update|into|table)\s+[a-zA-Z_0-9]*$/i.test( + beforeCursor + ) if (wantsTables) { for (const table of tables) { suggestions.push({ diff --git a/apps/desktop/src/features/sql-console/components/sql-results.tsx b/apps/desktop/src/features/sql-console/components/sql-results.tsx index b1b206c1..345ab8d1 100644 --- a/apps/desktop/src/features/sql-console/components/sql-results.tsx +++ b/apps/desktop/src/features/sql-console/components/sql-results.tsx @@ -297,7 +297,9 @@ export function SqlResults({ {successMessage}{' '} - {filteredRows.length !== result.rowCount ? `${filteredRows.length} / ` : ''} + {filteredRows.length !== result.rowCount + ? `${filteredRows.length} / ` + : ''} {result.rowCount} row{result.rowCount !== 1 ? 's' : ''} •{' '} {result.executionTime}ms backend • {filterTime}ms filtering @@ -305,7 +307,9 @@ export function SqlResults({ )} {mutationDisabledReason && result && !result.error && result.rows.length > 0 && ( -
    {mutationDisabledReason}
    +
    + {mutationDisabledReason} +
    )}
    ) : viewMode === 'json' ? ( @@ -465,7 +471,9 @@ export function SqlResults({ ) : (
    {formatCellValue(cellValue)}
    @@ -513,7 +521,8 @@ export function SqlResults({ Delete Row? - This action cannot be undone. This will permanently delete the selected row. + This action cannot be undone. This will permanently delete the selected + row. diff --git a/apps/desktop/src/features/sql-console/sql-console.tsx b/apps/desktop/src/features/sql-console/sql-console.tsx index 60eb0e2d..ff6f506b 100644 --- a/apps/desktop/src/features/sql-console/sql-console.tsx +++ b/apps/desktop/src/features/sql-console/sql-console.tsx @@ -108,7 +108,7 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { // Currently SavedQuery doesn't strictly have parent_id in types but we might need to handle if it did // For now assuming scripts are flat or we need to check if SavedQuery has folder_id // Wait, SavedQuery type in bindings.ts has NO folder_id or parent_id. - // So scripts can't be in folders yet? + // So scripts can't be in folders yet? // Ah, updateScript binding shows `folderId` param! // saved_query table has folder_id. // Bindings for SavedQuery TYPE seem missing folder_id? @@ -120,14 +120,14 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { // If bindings is missing it, we can't see it. // But wait, getSnippets command returns SavedQuery. // getScripts returns SavedQuery. - // Maybe I should use getSnippets if it returns more data? + // Maybe I should use getSnippets if it returns more data? // No, getScripts is what was used. // Let's assume for now scripts are root only until we fix bindings type. // OR, maybe `category` is used as folder? No, snippet logic usually uses ID relationships. // Re-reading bindings.ts: // export type SavedQuery = { ... tags, category ... } - // It DOES NOT have folder_id. + // It DOES NOT have folder_id. // This suggests scripts can't technically be in folders on the frontend-view of the struct yet. // However, I need to implement what I can. @@ -143,11 +143,9 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { }) } - setSnippets(newSnippets) }, [adapter, activeConnectionId]) - useEffect( function () { if (activeConnectionId) { @@ -229,10 +227,7 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { window.addEventListener('dora-schema-refresh', onSchemaRefresh as EventListener) return function () { - window.removeEventListener( - 'dora-schema-refresh', - onSchemaRefresh as EventListener - ) + window.removeEventListener('dora-schema-refresh', onSchemaRefresh as EventListener) } }, [activeConnectionId, refreshSchema] @@ -258,22 +253,22 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { const rows = Array.isArray(res.data.rows) ? res.data.rows.map((row: any) => { - if ( - typeof row === 'object' && - row !== null && - !Array.isArray(row) - ) { - return row - } - if (Array.isArray(row)) { - const obj: Record = {} - columns.forEach((col: string, i: number) => { - obj[col] = row[i] - }) - return obj - } - return {} - }) + if ( + typeof row === 'object' && + row !== null && + !Array.isArray(row) + ) { + return row + } + if (Array.isArray(row)) { + const obj: Record = {} + columns.forEach((col: string, i: number) => { + obj[col] = row[i] + }) + return obj + } + return {} + }) : [] const queryType = getQueryType(queryToRun) @@ -381,14 +376,7 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { setIsExecuting(false) } }, - [ - mode, - currentSqlQuery, - currentDrizzleQuery, - isExecuting, - activeConnectionId, - adapter - ] + [mode, currentSqlQuery, currentDrizzleQuery, isExecuting, activeConnectionId, adapter] ) function getQueryType(query: string): 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'OTHER' { @@ -417,44 +405,58 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { } } - const handleExport = useCallback(function () { - if (!result || result.rows.length === 0) return - - const jsonString = JSON.stringify(result.rows, null, 2) - const blob = new Blob([jsonString], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = 'query-results.json' - a.click() - URL.revokeObjectURL(url) - }, [result]) - - const handleExportCsv = useCallback(function () { - if (!result || result.rows.length === 0) return - - const headers = result.columns.join(',') - const rows = result.rows.map(function (row) { - return result.columns.map(function (col) { - const value = row[col] - if (value === null || value === undefined) return '' - const stringValue = String(value) - if (stringValue.includes(',') || stringValue.includes('"') || stringValue.includes('\n')) { - return '"' + stringValue.replace(/"/g, '""') + '"' - } - return stringValue - }).join(',') - }).join('\n') - - const csvContent = headers + '\n' + rows - const blob = new Blob([csvContent], { type: 'text/csv' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = 'query-results.csv' - a.click() - URL.revokeObjectURL(url) - }, [result]) + const handleExport = useCallback( + function () { + if (!result || result.rows.length === 0) return + + const jsonString = JSON.stringify(result.rows, null, 2) + const blob = new Blob([jsonString], { type: 'application/json' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'query-results.json' + a.click() + URL.revokeObjectURL(url) + }, + [result] + ) + + const handleExportCsv = useCallback( + function () { + if (!result || result.rows.length === 0) return + + const headers = result.columns.join(',') + const rows = result.rows + .map(function (row) { + return result.columns + .map(function (col) { + const value = row[col] + if (value === null || value === undefined) return '' + const stringValue = String(value) + if ( + stringValue.includes(',') || + stringValue.includes('"') || + stringValue.includes('\n') + ) { + return '"' + stringValue.replace(/"/g, '""') + '"' + } + return stringValue + }) + .join(',') + }) + .join('\n') + + const csvContent = headers + '\n' + rows + const blob = new Blob([csvContent], { type: 'text/csv' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'query-results.csv' + a.click() + URL.revokeObjectURL(url) + }, + [result] + ) // Unified snippet handling - works for both SQL and Drizzle const handleSnippetSelect = useCallback( @@ -489,7 +491,7 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { const res = await adapter.saveScript( name, currentContent || - (mode === 'sql' ? '-- New SQL query' : '// New Drizzle query'), + (mode === 'sql' ? '-- New SQL query' : '// New Drizzle query'), activeConnectionId, null, parentFolderId @@ -512,7 +514,6 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { ] ) - const handleNewFolder = useCallback( async (parentId?: string | null) => { // Extract integer ID if parent is a folder @@ -534,7 +535,6 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { [adapter, loadSnippets] ) - const handleRenameSnippet = useCallback( async (id: string, newName: string) => { if (!activeConnectionId && !id.startsWith('folder-')) return @@ -571,12 +571,10 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { console.error('Failed to rename snippet:', error) } } - }, [activeConnectionId, snippets, adapter, loadSnippets] ) - const handleDeleteSnippet = useCallback( async (id: string) => { if (!activeConnectionId && !id.startsWith('folder-')) return @@ -602,7 +600,6 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { [activeConnectionId, adapter, loadSnippets] ) - function handleTableSelect(tableName: string) { if (mode === 'sql') { setCurrentSqlQuery(`SELECT * FROM ${tableName} LIMIT 100;`) @@ -694,7 +691,9 @@ export function SqlConsole({ onToggleSidebar, activeConnectionId }: Props) { minSize={10} maxSize={25} collapsible - onCollapse={function () { setShowHistory(false) }} + onCollapse={function () { + setShowHistory(false) + }} > {/* Editor and Results */} diff --git a/apps/desktop/src/features/sql-console/stores/query-history-store.tsx b/apps/desktop/src/features/sql-console/stores/query-history-store.tsx index d550383d..1b561c88 100644 --- a/apps/desktop/src/features/sql-console/stores/query-history-store.tsx +++ b/apps/desktop/src/features/sql-console/stores/query-history-store.tsx @@ -1,26 +1,26 @@ import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react' type QueryHistoryItem = { - id: string - query: string - connectionId: string | null - timestamp: number - executionTimeMs: number - success: boolean - error?: string - rowCount?: number + id: string + query: string + connectionId: string | null + timestamp: number + executionTimeMs: number + success: boolean + error?: string + rowCount?: number } type QueryHistoryState = { - items: QueryHistoryItem[] - maxItems: number + items: QueryHistoryItem[] + maxItems: number } type QueryHistoryContextValue = { - history: QueryHistoryItem[] - addToHistory: (entry: Omit) => void - clearHistory: () => void - removeFromHistory: (id: string) => void + history: QueryHistoryItem[] + addToHistory: (entry: Omit) => void + clearHistory: () => void + removeFromHistory: (id: string) => void } const STORAGE_KEY = 'dora-query-history' @@ -29,76 +29,80 @@ const MAX_HISTORY_ITEMS = 50 const QueryHistoryContext = createContext(null) function loadHistoryFromStorage(): QueryHistoryItem[] { - try { - const stored = localStorage.getItem(STORAGE_KEY) - if (stored) { - const parsed = JSON.parse(stored) - return Array.isArray(parsed) ? parsed : [] - } - } catch (e) { - console.warn('Failed to load query history:', e) - } - return [] + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored) { + const parsed = JSON.parse(stored) + return Array.isArray(parsed) ? parsed : [] + } + } catch (e) { + console.warn('Failed to load query history:', e) + } + return [] } function saveHistoryToStorage(items: QueryHistoryItem[]): void { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(items)) - } catch (e) { - console.warn('Failed to save query history:', e) - } + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(items)) + } catch (e) { + console.warn('Failed to save query history:', e) + } } type Props = { - children: ReactNode + children: ReactNode } export function QueryHistoryProvider({ children }: Props) { - const [history, setHistory] = useState([]) - - useEffect(function loadHistory() { - const loaded = loadHistoryFromStorage() - setHistory(loaded) - }, []) - - const addToHistory = useCallback(function (entry: Omit) { - setHistory(function (prev) { - const newItem: QueryHistoryItem = { - ...entry, - id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, - timestamp: Date.now() - } - - const updated = [newItem, ...prev].slice(0, MAX_HISTORY_ITEMS) - saveHistoryToStorage(updated) - return updated - }) - }, []) - - const clearHistory = useCallback(function () { - setHistory([]) - saveHistoryToStorage([]) - }, []) - - const removeFromHistory = useCallback(function (id: string) { - setHistory(function (prev) { - const updated = prev.filter(function (item) { return item.id !== id }) - saveHistoryToStorage(updated) - return updated - }) - }, []) - - return ( - - {children} - - ) + const [history, setHistory] = useState([]) + + useEffect(function loadHistory() { + const loaded = loadHistoryFromStorage() + setHistory(loaded) + }, []) + + const addToHistory = useCallback(function (entry: Omit) { + setHistory(function (prev) { + const newItem: QueryHistoryItem = { + ...entry, + id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: Date.now() + } + + const updated = [newItem, ...prev].slice(0, MAX_HISTORY_ITEMS) + saveHistoryToStorage(updated) + return updated + }) + }, []) + + const clearHistory = useCallback(function () { + setHistory([]) + saveHistoryToStorage([]) + }, []) + + const removeFromHistory = useCallback(function (id: string) { + setHistory(function (prev) { + const updated = prev.filter(function (item) { + return item.id !== id + }) + saveHistoryToStorage(updated) + return updated + }) + }, []) + + return ( + + {children} + + ) } export function useQueryHistory(): QueryHistoryContextValue { - const context = useContext(QueryHistoryContext) - if (!context) { - throw new Error('useQueryHistory must be used within a QueryHistoryProvider') - } - return context + const context = useContext(QueryHistoryContext) + if (!context) { + throw new Error('useQueryHistory must be used within a QueryHistoryProvider') + } + return context } diff --git a/apps/desktop/src/index.css b/apps/desktop/src/index.css index 4089cf08..09cec629 100644 --- a/apps/desktop/src/index.css +++ b/apps/desktop/src/index.css @@ -1,4 +1,5 @@ -@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap') layer(base); +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600&display=swap') +layer(base); @import 'tailwindcss'; @@ -116,7 +117,6 @@ color utility to any element that depends on these defaults. */ @layer base { - *, ::after, ::before, @@ -672,7 +672,6 @@ } @keyframes statusShake { - 0%, 100% { transform: translateX(0); diff --git a/apps/desktop/src/lib/bindings.ts b/apps/desktop/src/lib/bindings.ts index eed2e007..15eead14 100644 --- a/apps/desktop/src/lib/bindings.ts +++ b/apps/desktop/src/lib/bindings.ts @@ -1,812 +1,1279 @@ // @ts-nocheck // Auto-generated by tauri-specta. DO NOT EDIT. - // This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually. /** user-defined commands **/ - export const commands = { -async minimizeWindow() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("minimize_window") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async maximizeWindow() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("maximize_window") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async closeWindow() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("close_window") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async openSqliteDb() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("open_sqlite_db") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async saveSqliteDb() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("save_sqlite_db") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async openFile(title: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("open_file", { title }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async addConnection(name: string, databaseInfo: DatabaseInfo, color: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("add_connection", { name, databaseInfo, color }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateConnection(connId: string, name: string, databaseInfo: DatabaseInfo, color: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_connection", { connId, name, databaseInfo, color }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateConnectionColor(connectionId: string, color: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_connection_color", { connectionId, color }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async connectToDatabase(connectionId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("connect_to_database", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async disconnectFromDatabase(connectionId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("disconnect_from_database", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getConnections() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_connections") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async removeConnection(connectionId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("remove_connection", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async testConnection(databaseInfo: DatabaseInfo) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("test_connection", { databaseInfo }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async initializeConnections() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("initialize_connections") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getRecentConnections(limit: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_recent_connections", { limit }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getConnectionHistory(dbTypeFilter: string | null, successFilter: boolean | null, limit: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_connection_history", { dbTypeFilter, successFilter, limit }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async setConnectionPin(connectionId: string, pin: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_connection_pin", { connectionId, pin }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async verifyPinAndGetCredentials(connectionId: string, pin: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("verify_pin_and_get_credentials", { connectionId, pin }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async startQuery(connectionId: string, query: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("start_query", { connectionId, query }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async fetchQuery(queryId: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("fetch_query", { queryId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async fetchPage(queryId: number, pageIndex: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("fetch_page", { queryId, pageIndex }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getQueryStatus(queryId: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_query_status", { queryId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getPageCount(queryId: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_page_count", { queryId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getColumns(queryId: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_columns", { queryId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async saveQueryToHistory(connectionId: string, query: string, durationMs: number | null, status: string, rowCount: number, errorMessage: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("save_query_to_history", { connectionId, query, durationMs, status, rowCount, errorMessage }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getQueryHistory(connectionId: string, limit: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_query_history", { connectionId, limit }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async startLiveMonitor(connectionId: string, tableName: string, intervalMs: number, changeTypes: LiveMonitorChangeType[]) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("start_live_monitor", { connectionId, tableName, intervalMs, changeTypes }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async stopLiveMonitor(monitorId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("stop_live_monitor", { monitorId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getRecentQueries(connectionId: string | null, limit: number | null, statusFilter: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_recent_queries", { connectionId, limit, statusFilter }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async saveScript(name: string, content: string, connectionId: string | null, description: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("save_script", { name, content, connectionId, description }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateScript(id: number, name: string, content: string, connectionId: string | null, description: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_script", { id, name, content, connectionId, description }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getScripts(connectionId: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_scripts", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async deleteScript(id: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_script", { id }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getSnippets(languageFilter: string | null, isSystemFilter: boolean | null, categoryFilter: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_snippets", { languageFilter, isSystemFilter, categoryFilter }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async saveSnippet(name: string, content: string, language: string | null, tags: string | null, category: string | null, connectionId: string | null, description: string | null, folderId: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("save_snippet", { name, content, language, tags, category, connectionId, description, folderId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateSnippet(id: number, name: string, content: string, language: string | null, tags: string | null, category: string | null, description: string | null, folderId: number | null, connectionId: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_snippet", { id, name, content, language, tags, category, description, folderId, connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async deleteSnippet(id: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_snippet", { id }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async seedSystemSnippets() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("seed_system_snippets") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getSnippetFolders() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_snippet_folders") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async createSnippetFolder(name: string, parentId: number | null, color: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("create_snippet_folder", { name, parentId, color }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateSnippetFolder(id: number, name: string, color: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_snippet_folder", { id, name, color }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async deleteSnippetFolder(id: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_snippet_folder", { id }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async saveSessionState(sessionData: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("save_session_state", { sessionData }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getSessionState() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_session_state") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getSetting(key: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_setting", { key }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async setSetting(key: string, value: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("set_setting", { key, value }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async insertRow(connectionId: string, tableName: string, schemaName: string | null, rowData: Partial<{ [key in string]: JsonValue }>) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("insert_row", { connectionId, tableName, schemaName, rowData }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async updateCell(connectionId: string, tableName: string, schemaName: string | null, primaryKeyColumn: string, primaryKeyValue: JsonValue, columnName: string, newValue: JsonValue) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("update_cell", { connectionId, tableName, schemaName, primaryKeyColumn, primaryKeyValue, columnName, newValue }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async deleteRows(connectionId: string, tableName: string, schemaName: string | null, primaryKeyColumn: string, primaryKeyValues: JsonValue[]) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("delete_rows", { connectionId, tableName, schemaName, primaryKeyColumn, primaryKeyValues }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async duplicateRow(connectionId: string, tableName: string, schemaName: string | null, primaryKeyColumn: string, primaryKeyValue: JsonValue) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("duplicate_row", { connectionId, tableName, schemaName, primaryKeyColumn, primaryKeyValue }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async exportTable(connectionId: string, tableName: string, schemaName: string | null, format: ExportFormat, limit: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("export_table", { connectionId, tableName, schemaName, format, limit }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async softDeleteRows(connectionId: string, tableName: string, schemaName: string | null, primaryKeyColumn: string, primaryKeyValues: JsonValue[], softDeleteColumn: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("soft_delete_rows", { connectionId, tableName, schemaName, primaryKeyColumn, primaryKeyValues, softDeleteColumn }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async undoSoftDelete(connectionId: string, tableName: string, schemaName: string | null, primaryKeyColumn: string, primaryKeyValues: JsonValue[], softDeleteColumn: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("undo_soft_delete", { connectionId, tableName, schemaName, primaryKeyColumn, primaryKeyValues, softDeleteColumn }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async truncateTable(connectionId: string, tableName: string, schemaName: string | null, cascade: boolean | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("truncate_table", { connectionId, tableName, schemaName, cascade }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async truncateDatabase(connectionId: string, schemaName: string | null, confirm: boolean) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("truncate_database", { connectionId, schemaName, confirm }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async dumpDatabase(connectionId: string, outputPath: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("dump_database", { connectionId, outputPath }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async executeBatch(connectionId: string, statements: string[]) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("execute_batch", { connectionId, statements }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getDatabaseSchema(connectionId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_database_schema", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async getDatabaseMetadata(connectionId: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("get_database_metadata", { connectionId }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async seedTable(connectionId: string, tableName: string, schemaName: string | null, count: number) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("seed_table", { connectionId, tableName, schemaName, count }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async parseSql(sql: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("parse_sql", { sql }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -async buildSql(ast: JsonValue) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("build_sql", { ast }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Export database schema to SQL DDL format - * - * # Arguments - * * `connection_id` - UUID of the connected database - * * `dialect` - Target SQL dialect: "postgresql" or "sqlite" - */ -async exportSchemaSql(connectionId: string, dialect: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("export_schema_sql", { connectionId, dialect }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Export database schema to Drizzle ORM TypeScript format - * - * # Arguments - * * `connection_id` - UUID of the connected database - * * `dialect` - Target Drizzle dialect: "postgresql" or "sqlite" - */ -async exportSchemaDrizzle(connectionId: string, dialect: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("export_schema_drizzle", { connectionId, dialect }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Complete a prompt using the configured AI provider - */ -async aiComplete(prompt: string, connectionId: string | null, maxTokens: number | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_complete", { prompt, connectionId, maxTokens }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Set the AI provider (gemini or ollama) - */ -async aiSetProvider(provider: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_set_provider", { provider }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Get the current AI provider - */ -async aiGetProvider() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_get_provider") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Set the Gemini API key (BYOK) - */ -async aiSetGeminiKey(apiKey: string) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_set_gemini_key", { apiKey }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * Configure Ollama endpoint and model - */ -async aiConfigureOllama(endpoint: string | null, model: string | null) : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_configure_ollama", { endpoint, model }) }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -}, -/** - * List available Ollama models - */ -async aiListOllamaModels() : Promise> { - try { - return { status: "ok", data: await TAURI_INVOKE("ai_list_ollama_models") }; -} catch (e) { - if(e instanceof Error) throw e; - else return { status: "error", error: e as any }; -} -} + async minimizeWindow(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('minimize_window') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async maximizeWindow(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('maximize_window') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async closeWindow(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('close_window') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async openSqliteDb(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('open_sqlite_db') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async saveSqliteDb(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('save_sqlite_db') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async openFile(title: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('open_file', { title }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async addConnection( + name: string, + databaseInfo: DatabaseInfo, + color: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('add_connection', { name, databaseInfo, color }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateConnection( + connId: string, + name: string, + databaseInfo: DatabaseInfo, + color: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_connection', { connId, name, databaseInfo, color }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateConnectionColor( + connectionId: string, + color: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_connection_color', { connectionId, color }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async connectToDatabase(connectionId: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('connect_to_database', { connectionId }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async disconnectFromDatabase(connectionId: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('disconnect_from_database', { connectionId }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getConnections(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_connections') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async removeConnection(connectionId: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('remove_connection', { connectionId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async testConnection(databaseInfo: DatabaseInfo): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('test_connection', { databaseInfo }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async initializeConnections(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('initialize_connections') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getRecentConnections(limit: number | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_recent_connections', { limit }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getConnectionHistory( + dbTypeFilter: string | null, + successFilter: boolean | null, + limit: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_connection_history', { + dbTypeFilter, + successFilter, + limit + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async setConnectionPin(connectionId: string, pin: string | null): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('set_connection_pin', { connectionId, pin }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async verifyPinAndGetCredentials( + connectionId: string, + pin: string + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('verify_pin_and_get_credentials', { connectionId, pin }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async startQuery(connectionId: string, query: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('start_query', { connectionId, query }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async fetchQuery(queryId: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('fetch_query', { queryId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async fetchPage(queryId: number, pageIndex: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('fetch_page', { queryId, pageIndex }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getQueryStatus(queryId: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_query_status', { queryId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getPageCount(queryId: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_page_count', { queryId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getColumns(queryId: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_columns', { queryId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async saveQueryToHistory( + connectionId: string, + query: string, + durationMs: number | null, + status: string, + rowCount: number, + errorMessage: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('save_query_to_history', { + connectionId, + query, + durationMs, + status, + rowCount, + errorMessage + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getQueryHistory( + connectionId: string, + limit: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_query_history', { connectionId, limit }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async startLiveMonitor( + connectionId: string, + tableName: string, + intervalMs: number, + changeTypes: LiveMonitorChangeType[] + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('start_live_monitor', { + connectionId, + tableName, + intervalMs, + changeTypes + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async stopLiveMonitor(monitorId: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('stop_live_monitor', { monitorId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getRecentQueries( + connectionId: string | null, + limit: number | null, + statusFilter: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_recent_queries', { + connectionId, + limit, + statusFilter + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async saveScript( + name: string, + content: string, + connectionId: string | null, + description: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('save_script', { + name, + content, + connectionId, + description + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateScript( + id: number, + name: string, + content: string, + connectionId: string | null, + description: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_script', { + id, + name, + content, + connectionId, + description + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getScripts(connectionId: string | null): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_scripts', { connectionId }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async deleteScript(id: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('delete_script', { id }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getSnippets( + languageFilter: string | null, + isSystemFilter: boolean | null, + categoryFilter: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_snippets', { + languageFilter, + isSystemFilter, + categoryFilter + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async saveSnippet( + name: string, + content: string, + language: string | null, + tags: string | null, + category: string | null, + connectionId: string | null, + description: string | null, + folderId: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('save_snippet', { + name, + content, + language, + tags, + category, + connectionId, + description, + folderId + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateSnippet( + id: number, + name: string, + content: string, + language: string | null, + tags: string | null, + category: string | null, + description: string | null, + folderId: number | null, + connectionId: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_snippet', { + id, + name, + content, + language, + tags, + category, + description, + folderId, + connectionId + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async deleteSnippet(id: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('delete_snippet', { id }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async seedSystemSnippets(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('seed_system_snippets') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getSnippetFolders(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_snippet_folders') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async createSnippetFolder( + name: string, + parentId: number | null, + color: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('create_snippet_folder', { name, parentId, color }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateSnippetFolder( + id: number, + name: string, + color: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_snippet_folder', { id, name, color }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async deleteSnippetFolder(id: number): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('delete_snippet_folder', { id }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async saveSessionState(sessionData: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('save_session_state', { sessionData }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getSessionState(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_session_state') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getSetting(key: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('get_setting', { key }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async setSetting(key: string, value: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('set_setting', { key, value }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async insertRow( + connectionId: string, + tableName: string, + schemaName: string | null, + rowData: Partial<{ [key in string]: JsonValue }> + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('insert_row', { + connectionId, + tableName, + schemaName, + rowData + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async updateCell( + connectionId: string, + tableName: string, + schemaName: string | null, + primaryKeyColumn: string, + primaryKeyValue: JsonValue, + columnName: string, + newValue: JsonValue + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('update_cell', { + connectionId, + tableName, + schemaName, + primaryKeyColumn, + primaryKeyValue, + columnName, + newValue + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async deleteRows( + connectionId: string, + tableName: string, + schemaName: string | null, + primaryKeyColumn: string, + primaryKeyValues: JsonValue[] + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('delete_rows', { + connectionId, + tableName, + schemaName, + primaryKeyColumn, + primaryKeyValues + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async duplicateRow( + connectionId: string, + tableName: string, + schemaName: string | null, + primaryKeyColumn: string, + primaryKeyValue: JsonValue + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('duplicate_row', { + connectionId, + tableName, + schemaName, + primaryKeyColumn, + primaryKeyValue + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async exportTable( + connectionId: string, + tableName: string, + schemaName: string | null, + format: ExportFormat, + limit: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('export_table', { + connectionId, + tableName, + schemaName, + format, + limit + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async softDeleteRows( + connectionId: string, + tableName: string, + schemaName: string | null, + primaryKeyColumn: string, + primaryKeyValues: JsonValue[], + softDeleteColumn: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('soft_delete_rows', { + connectionId, + tableName, + schemaName, + primaryKeyColumn, + primaryKeyValues, + softDeleteColumn + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async undoSoftDelete( + connectionId: string, + tableName: string, + schemaName: string | null, + primaryKeyColumn: string, + primaryKeyValues: JsonValue[], + softDeleteColumn: string + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('undo_soft_delete', { + connectionId, + tableName, + schemaName, + primaryKeyColumn, + primaryKeyValues, + softDeleteColumn + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async truncateTable( + connectionId: string, + tableName: string, + schemaName: string | null, + cascade: boolean | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('truncate_table', { + connectionId, + tableName, + schemaName, + cascade + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async truncateDatabase( + connectionId: string, + schemaName: string | null, + confirm: boolean + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('truncate_database', { connectionId, schemaName, confirm }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async dumpDatabase(connectionId: string, outputPath: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('dump_database', { connectionId, outputPath }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async executeBatch( + connectionId: string, + statements: string[] + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('execute_batch', { connectionId, statements }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getDatabaseSchema(connectionId: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_database_schema', { connectionId }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async getDatabaseMetadata(connectionId: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('get_database_metadata', { connectionId }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async seedTable( + connectionId: string, + tableName: string, + schemaName: string | null, + count: number + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('seed_table', { + connectionId, + tableName, + schemaName, + count + }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async parseSql(sql: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('parse_sql', { sql }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + async buildSql(ast: JsonValue): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('build_sql', { ast }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Export database schema to SQL DDL format + * + * # Arguments + * * `connection_id` - UUID of the connected database + * * `dialect` - Target SQL dialect: "postgresql" or "sqlite" + */ + async exportSchemaSql(connectionId: string, dialect: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('export_schema_sql', { connectionId, dialect }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Export database schema to Drizzle ORM TypeScript format + * + * # Arguments + * * `connection_id` - UUID of the connected database + * * `dialect` - Target Drizzle dialect: "postgresql" or "sqlite" + */ + async exportSchemaDrizzle(connectionId: string, dialect: string): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('export_schema_drizzle', { connectionId, dialect }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Complete a prompt using the configured AI provider + */ + async aiComplete( + prompt: string, + connectionId: string | null, + maxTokens: number | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('ai_complete', { prompt, connectionId, maxTokens }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Set the AI provider (gemini or ollama) + */ + async aiSetProvider(provider: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('ai_set_provider', { provider }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Get the current AI provider + */ + async aiGetProvider(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('ai_get_provider') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Set the Gemini API key (BYOK) + */ + async aiSetGeminiKey(apiKey: string): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('ai_set_gemini_key', { apiKey }) } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * Configure Ollama endpoint and model + */ + async aiConfigureOllama( + endpoint: string | null, + model: string | null + ): Promise> { + try { + return { + status: 'ok', + data: await TAURI_INVOKE('ai_configure_ollama', { endpoint, model }) + } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + }, + /** + * List available Ollama models + */ + async aiListOllamaModels(): Promise> { + try { + return { status: 'ok', data: await TAURI_INVOKE('ai_list_ollama_models') } + } catch (e) { + if (e instanceof Error) throw e + else return { status: 'error', error: e as any } + } + } } /** user-defined events **/ - - /** user-defined constants **/ - - /** user-defined types **/ -export type AIResponse = { content: string; suggested_queries: string[] | null; tokens_used: number | null; provider: string } -export type ColumnInfo = { name: string; data_type: string; is_nullable: boolean; default_value: string | null; -/** - * Whether this column is part of the primary key - */ -is_primary_key?: boolean; -/** - * Whether this column auto-increments (SERIAL, AUTOINCREMENT, etc.) - */ -is_auto_increment?: boolean; -/** - * Foreign key relationship, if any - */ -foreign_key?: ForeignKeyInfo | null } -export type ConnectionHistoryEntry = { id: number; connection_id: string; connection_name: string; database_type: string; attempted_at: number; success: boolean; error_message: string | null; duration_ms: number | null } -export type ConnectionInfo = { id: string; name: string; connected: boolean; database_type: DatabaseInfo; last_connected_at: number | null; created_at: number | null; updated_at: number | null; pin_hash: string | null; favorite: boolean | null; color: string | null; sort_order: number | null } -export type DatabaseInfo = { Postgres: { connection_string: string; ssh_config: SshConfig | null } } | { SQLite: { db_path: string } } | -/** - * LibSQL/Turso database - can be local path or remote URL with auth token - */ -{ LibSQL: { -/** - * Either a local file path or remote URL (libsql://...) - */ -url: string; -/** - * Auth token for remote connections (optional for local) - */ -auth_token: string | null } } +export type AIResponse = { + content: string + suggested_queries: string[] | null + tokens_used: number | null + provider: string +} +export type ColumnInfo = { + name: string + data_type: string + is_nullable: boolean + default_value: string | null + /** + * Whether this column is part of the primary key + */ + is_primary_key?: boolean + /** + * Whether this column auto-increments (SERIAL, AUTOINCREMENT, etc.) + */ + is_auto_increment?: boolean + /** + * Foreign key relationship, if any + */ + foreign_key?: ForeignKeyInfo | null +} +export type ConnectionHistoryEntry = { + id: number + connection_id: string + connection_name: string + database_type: string + attempted_at: number + success: boolean + error_message: string | null + duration_ms: number | null +} +export type ConnectionInfo = { + id: string + name: string + connected: boolean + database_type: DatabaseInfo + last_connected_at: number | null + created_at: number | null + updated_at: number | null + pin_hash: string | null + favorite: boolean | null + color: string | null + sort_order: number | null +} +export type DatabaseInfo = + | { Postgres: { connection_string: string; ssh_config: SshConfig | null } } + | { SQLite: { db_path: string } } + /** + * LibSQL/Turso database - can be local path or remote URL with auth token + */ + | { + LibSQL: { + /** + * Either a local file path or remote URL (libsql://...) + */ + url: string + /** + * Auth token for remote connections (optional for local) + */ + auth_token: string | null + } + } /** * Database-level metadata information */ -export type DatabaseMetadata = { -/** - * Total size of the database in bytes - */ -size_bytes: number; -/** - * Database creation timestamp (if available) - */ -created_at: number | null; -/** - * Last modification timestamp (if available) - */ -last_updated: number | null; -/** - * Total number of rows across all tables - */ -row_count_total: number; -/** - * Number of tables in the database - */ -table_count: number; -/** - * Database host or file path - */ -host: string; -/** - * Database name - */ -database_name: string | null } +export type DatabaseMetadata = { + /** + * Total size of the database in bytes + */ + size_bytes: number + /** + * Database creation timestamp (if available) + */ + created_at: number | null + /** + * Last modification timestamp (if available) + */ + last_updated: number | null + /** + * Total number of rows across all tables + */ + row_count_total: number + /** + * Number of tables in the database + */ + table_count: number + /** + * Database host or file path + */ + host: string + /** + * Database name + */ + database_name: string | null +} export type DatabaseSchema = { tables: TableInfo[]; schemas: string[]; unique_columns: string[] } /** * Result of a database dump operation */ -export type DumpResult = { success: boolean; file_path: string; size_bytes: number; tables_dumped: number; rows_dumped: number; message: string | null } +export type DumpResult = { + success: boolean + file_path: string + size_bytes: number + tables_dumped: number + rows_dumped: number + message: string | null +} /** * Export format options */ -export type ExportFormat = "json" | "sql_insert" | "csv" +export type ExportFormat = 'json' | 'sql_insert' | 'csv' /** * Information about a foreign key relationship */ -export type ForeignKeyInfo = { -/** - * The table that this foreign key references - */ -referenced_table: string; -/** - * The column in the referenced table - */ -referenced_column: string; -/** - * The schema of the referenced table (empty for SQLite) - */ -referenced_schema: string } -export type IndexInfo = { name: string; column_names: string[]; is_unique: boolean; is_primary: boolean } -export type JsonValue = null | boolean | number | string | JsonValue[] | Partial<{ [key in string]: JsonValue }> -export type LiveMonitorChangeType = "insert" | "update" | "delete" +export type ForeignKeyInfo = { + /** + * The table that this foreign key references + */ + referenced_table: string + /** + * The column in the referenced table + */ + referenced_column: string + /** + * The schema of the referenced table (empty for SQLite) + */ + referenced_schema: string +} +export type IndexInfo = { + name: string + column_names: string[] + is_unique: boolean + is_primary: boolean +} +export type JsonValue = + | null + | boolean + | number + | string + | JsonValue[] + | Partial<{ [key in string]: JsonValue }> +export type LiveMonitorChangeType = 'insert' | 'update' | 'delete' export type LiveMonitorSession = { monitorId: string; eventName: string } /** * Result of a mutation operation */ export type MutationResult = { success: boolean; affected_rows: number; message: string | null } -export type QueryHistoryEntry = { id: number; connection_id: string; query_text: string; executed_at: number; duration_ms: number | null; status: string; row_count: number; error_message: string | null } -export type QueryStatus = "Pending" | "Running" | "Completed" | "Error" -export type SavedQuery = { id: number; name: string; description: string | null; query_text: string; connection_id: string | null; tags: string | null; category: string | null; created_at: number; updated_at: number; favorite: boolean; is_snippet: boolean; is_system: boolean; language: string | null; folder_id: number | null } +export type QueryHistoryEntry = { + id: number + connection_id: string + query_text: string + executed_at: number + duration_ms: number | null + status: string + row_count: number + error_message: string | null +} +export type QueryStatus = 'Pending' | 'Running' | 'Completed' | 'Error' +export type SavedQuery = { + id: number + name: string + description: string | null + query_text: string + connection_id: string | null + tags: string | null + category: string | null + created_at: number + updated_at: number + favorite: boolean + is_snippet: boolean + is_system: boolean + language: string | null + folder_id: number | null +} export type SeedResult = { rows_inserted: number; table: string } -export type SnippetFolder = { id: number; name: string; parent_id: number | null; color: string | null; created_at: number; updated_at: number } +export type SnippetFolder = { + id: number + name: string + parent_id: number | null + color: string | null + created_at: number + updated_at: number +} /** * Result of a soft delete operation */ -export type SoftDeleteResult = { success: boolean; affected_rows: number; message: string | null; -/** - * Unix timestamp when deletion happened (for undo window) - */ -deleted_at: number; -/** - * How many seconds the undo window lasts - */ -undo_window_seconds: number } -export type SshConfig = { host: string; port: number; username: string; private_key_path: string | null; password: string | null } -export type StatementInfo = { returns_values: boolean; status: QueryStatus; first_page: JsonValue; affected_rows: number | null; error: string | null } -export type TableInfo = { name: string; schema: string; columns: ColumnInfo[]; -/** - * Names of columns that form the primary key (supports composite keys) - */ -primary_key_columns?: string[]; -/** - * List of indexes on this table - */ -indexes?: IndexInfo[]; -/** - * Estimated row count (may be approximate for performance) - */ -row_count_estimate?: number | null } +export type SoftDeleteResult = { + success: boolean + affected_rows: number + message: string | null + /** + * Unix timestamp when deletion happened (for undo window) + */ + deleted_at: number + /** + * How many seconds the undo window lasts + */ + undo_window_seconds: number +} +export type SshConfig = { + host: string + port: number + username: string + private_key_path: string | null + password: string | null +} +export type StatementInfo = { + returns_values: boolean + status: QueryStatus + first_page: JsonValue + affected_rows: number | null + error: string | null +} +export type TableInfo = { + name: string + schema: string + columns: ColumnInfo[] + /** + * Names of columns that form the primary key (supports composite keys) + */ + primary_key_columns?: string[] + /** + * List of indexes on this table + */ + indexes?: IndexInfo[] + /** + * Estimated row count (may be approximate for performance) + */ + row_count_estimate?: number | null +} /** * Result of a truncate operation */ -export type TruncateResult = { success: boolean; affected_rows: number; tables_truncated: string[]; message: string | null } +export type TruncateResult = { + success: boolean + affected_rows: number + tables_truncated: string[] + message: string | null +} /** tauri-specta globals **/ -import { - invoke as TAURI_INVOKE, - Channel as TAURI_CHANNEL, -} from "@tauri-apps/api/core"; -import * as TAURI_API_EVENT from "@tauri-apps/api/event"; -import { type WebviewWindow as __WebviewWindow__ } from "@tauri-apps/api/webviewWindow"; +import { invoke as TAURI_INVOKE, Channel as TAURI_CHANNEL } from '@tauri-apps/api/core' +import * as TAURI_API_EVENT from '@tauri-apps/api/event' +import { type WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow' type __EventObj__ = { - listen: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; - once: ( - cb: TAURI_API_EVENT.EventCallback, - ) => ReturnType>; + listen: (cb: TAURI_API_EVENT.EventCallback) => ReturnType> + once: (cb: TAURI_API_EVENT.EventCallback) => ReturnType> emit: null extends T ? (payload?: T) => ReturnType - : (payload: T) => ReturnType; -}; + : (payload: T) => ReturnType +} -export type Result = - | { status: "ok"; data: T } - | { status: "error"; error: E }; +export type Result = { status: 'ok'; data: T } | { status: 'error'; error: E } -function __makeEvents__>( - mappings: Record, -) { +function __makeEvents__>(mappings: Record) { return new Proxy( {} as unknown as { [K in keyof T]: __EventObj__ & { - (handle: __WebviewWindow__): __EventObj__; - }; + (handle: __WebviewWindow__): __EventObj__ + } }, { get: (_, event) => { - const name = mappings[event as keyof T]; + const name = mappings[event as keyof T] return new Proxy((() => {}) as any, { apply: (_, __, [window]: [__WebviewWindow__]) => ({ listen: (arg: any) => window.listen(name, arg), once: (arg: any) => window.once(name, arg), - emit: (arg: any) => window.emit(name, arg), + emit: (arg: any) => window.emit(name, arg) }), get: (_, command: keyof __EventObj__) => { switch (command) { - case "listen": - return (arg: any) => TAURI_API_EVENT.listen(name, arg); - case "once": - return (arg: any) => TAURI_API_EVENT.once(name, arg); - case "emit": - return (arg: any) => TAURI_API_EVENT.emit(name, arg); + case 'listen': + return (arg: any) => TAURI_API_EVENT.listen(name, arg) + case 'once': + return (arg: any) => TAURI_API_EVENT.once(name, arg) + case 'emit': + return (arg: any) => TAURI_API_EVENT.emit(name, arg) } - }, - }); - }, - }, - ); + } + }) + } + } + ) } diff --git a/apps/desktop/src/monaco-workers.ts b/apps/desktop/src/monaco-workers.ts index 1b7a7373..acd4d81b 100644 --- a/apps/desktop/src/monaco-workers.ts +++ b/apps/desktop/src/monaco-workers.ts @@ -6,21 +6,21 @@ 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() - } + 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 as any).languages.typescript.typescriptDefaults.setEagerModelSync(true) diff --git a/apps/desktop/src/pages/Index.tsx b/apps/desktop/src/pages/Index.tsx index 70b8cbbb..7ad61199 100644 --- a/apps/desktop/src/pages/Index.tsx +++ b/apps/desktop/src/pages/Index.tsx @@ -126,7 +126,9 @@ export default function Index() { } catch (error) { toast({ title: 'Failed to Load Connections', - description: mapConnectionError(error instanceof Error ? error : new Error('Unknown error')), + description: mapConnectionError( + error instanceof Error ? error : new Error('Unknown error') + ), variant: 'destructive' }) } finally { @@ -293,7 +295,9 @@ export default function Index() { } catch (error) { toast({ title: 'Failed to Add Connection', - description: mapConnectionError(error instanceof Error ? error : new Error('Unknown error')), + description: mapConnectionError( + error instanceof Error ? error : new Error('Unknown error') + ), variant: 'destructive' }) } @@ -338,7 +342,9 @@ export default function Index() { } catch (error) { toast({ title: 'Failed to Update Connection', - description: mapConnectionError(error instanceof Error ? error : new Error('Unknown error')), + description: mapConnectionError( + error instanceof Error ? error : new Error('Unknown error') + ), variant: 'destructive' }) } @@ -419,7 +425,9 @@ export default function Index() { } catch (error) { toast({ title: 'Failed to Delete Connection', - description: mapConnectionError(error instanceof Error ? error : new Error('Unknown error')), + description: mapConnectionError( + error instanceof Error ? error : new Error('Unknown error') + ), variant: 'destructive' }) } finally { @@ -494,7 +502,9 @@ export default function Index() { )}
    - {connections.length === 0 && !isLoading && (activeNavId === 'database-studio' || activeNavId === 'sql-console') ? ( + {connections.length === 0 && + !isLoading && + (activeNavId === 'database-studio' || activeNavId === 'sql-console') ? ( } title='No Connections' @@ -531,41 +541,45 @@ export default function Index() { ) : activeNavId === 'docker' ? ( + onOpenInDataViewer={async function (container) { + const userEnv = container.env.find(function (e) { + return e.startsWith('POSTGRES_USER=') + }) + const passEnv = container.env.find(function (e) { + return e.startsWith('POSTGRES_PASSWORD=') + }) + const dbEnv = container.env.find(function (e) { + return e.startsWith('POSTGRES_DB=') + }) + const primaryPort = container.ports.find(function (p) { + return p.containerPort === 5432 + }) + + const user = userEnv + ? userEnv.split('=')[1] + : 'postgres' + const password = passEnv + ? passEnv.split('=')[1] + : 'postgres' + const database = dbEnv + ? dbEnv.split('=')[1] + : 'postgres' + const port = primaryPort ? primaryPort.hostPort : 5432 + + const connectionData = { + name: container.name, + type: 'postgres' as const, + host: 'localhost', + port, + user, + password, + database + } + + await handleAddConnection(connectionData) + setActiveNavId('database-studio') + }} + /> ) : ( diff --git a/apps/desktop/src/shared/lib/use-shortcut/builder.ts b/apps/desktop/src/shared/lib/use-shortcut/builder.ts index af013aec..b8373afe 100644 --- a/apps/desktop/src/shared/lib/use-shortcut/builder.ts +++ b/apps/desktop/src/shared/lib/use-shortcut/builder.ts @@ -286,7 +286,7 @@ function createSingleBinding( entry.attemptCallbacks.add(callback) return () => entry.attemptCallbacks.delete(callback) } - return () => { } + return () => {} } } } @@ -333,7 +333,7 @@ function createBinding( enable: () => validResults.forEach((r) => r.enable()), disable: () => validResults.forEach((r) => r.disable()), onAttempt: (callback) => { - const unbinds = validResults.map((r) => r.onAttempt?.(callback) || (() => { })) + const unbinds = validResults.map((r) => r.onAttempt?.(callback) || (() => {})) return () => unbinds.forEach((u) => u()) } } @@ -396,7 +396,10 @@ export function createShortcutBuilder(options: UseShortcutOptions = {}): { ...currentState, combos } - debugLog(currentState.options.debug, `Chain: .bind("${combos.join('", "')}")`) + debugLog( + currentState.options.debug, + `Chain: .bind("${combos.join('", "')}")` + ) return createProxy(newState) } } diff --git a/apps/desktop/src/shared/ui/disabled-feature.tsx b/apps/desktop/src/shared/ui/disabled-feature.tsx index 47f20ebb..d67d8421 100644 --- a/apps/desktop/src/shared/ui/disabled-feature.tsx +++ b/apps/desktop/src/shared/ui/disabled-feature.tsx @@ -4,40 +4,40 @@ import { Button } from './button' import type { ComponentProps, ReactNode } from 'react' type Props = { - feature: string - children: ReactNode - variant?: ComponentProps['variant'] - size?: ComponentProps['size'] - className?: string - showToast?: boolean + feature: string + children: ReactNode + variant?: ComponentProps['variant'] + size?: ComponentProps['size'] + className?: string + showToast?: boolean } export function DisabledFeature({ - feature, - children, - variant = 'ghost', - size = 'sm', - className, - showToast = true + feature, + children, + variant = 'ghost', + size = 'sm', + className, + showToast = true }: Props) { - function handleClick() { - if (showToast) { - toast.info(`${feature} is coming soon`, { - description: 'This feature is not yet implemented' - }) - } - } + function handleClick() { + if (showToast) { + toast.info(`${feature} is coming soon`, { + description: 'This feature is not yet implemented' + }) + } + } - return ( - - ) + return ( + + ) } diff --git a/apps/desktop/src/shared/ui/empty-state.tsx b/apps/desktop/src/shared/ui/empty-state.tsx index fad70045..9734f533 100644 --- a/apps/desktop/src/shared/ui/empty-state.tsx +++ b/apps/desktop/src/shared/ui/empty-state.tsx @@ -3,34 +3,34 @@ import { Button } from '@/shared/ui/button' import { cn } from '@/shared/utils/cn' type Props = { - icon?: ReactNode - title: string - description?: string - action?: { - label: string - onClick: () => void - } - className?: string + icon?: ReactNode + title: string + description?: string + action?: { + label: string + onClick: () => void + } + className?: string } export function EmptyState({ icon, title, description, action, className }: Props) { - return ( -
    - {icon &&
    {icon}
    } -

    {title}

    - {description && ( -

    {description}

    - )} - {action && ( - - )} -
    - ) + return ( +
    + {icon &&
    {icon}
    } +

    {title}

    + {description && ( +

    {description}

    + )} + {action && ( + + )} +
    + ) } diff --git a/apps/desktop/src/shared/ui/error-fallback.tsx b/apps/desktop/src/shared/ui/error-fallback.tsx index dd0566fe..978e1280 100644 --- a/apps/desktop/src/shared/ui/error-fallback.tsx +++ b/apps/desktop/src/shared/ui/error-fallback.tsx @@ -22,11 +22,16 @@ function mapError(error: Error | null): ErrorMapping { return { icon: Server, title: 'Connection Refused', - message: 'Unable to connect to the database server. Make sure the server is running and accessible.' + message: + 'Unable to connect to the database server. Make sure the server is running and accessible.' } } - if (msg.includes('authentication') || msg.includes('password') || msg.includes('access denied')) { + if ( + msg.includes('authentication') || + msg.includes('password') || + msg.includes('access denied') + ) { return { icon: Lock, title: 'Authentication Failed', @@ -50,11 +55,16 @@ function mapError(error: Error | null): ErrorMapping { } } - if (msg.includes('does not exist') || msg.includes('unknown database') || msg.includes('no such table')) { + if ( + msg.includes('does not exist') || + msg.includes('unknown database') || + msg.includes('no such table') + ) { return { icon: Database, title: 'Not Found', - message: 'The requested database or table does not exist. Verify the name and try again.' + message: + 'The requested database or table does not exist. Verify the name and try again.' } } @@ -70,7 +80,8 @@ function mapError(error: Error | null): ErrorMapping { return { icon: Lock, title: 'Permission Denied', - message: 'You do not have permission to perform this action. Contact your database administrator.' + message: + 'You do not have permission to perform this action. Contact your database administrator.' } } @@ -100,9 +111,7 @@ export function ErrorFallback({ error, feature, onRetry, className }: Props) { {feature ? `${feature}: ${mapping.title}` : mapping.title} -

    - {mapping.message} -

    +

    {mapping.message}

    {error && (
    diff --git a/apps/desktop/src/shared/ui/error-state.tsx b/apps/desktop/src/shared/ui/error-state.tsx index c53d68e0..8fe4dbf8 100644 --- a/apps/desktop/src/shared/ui/error-state.tsx +++ b/apps/desktop/src/shared/ui/error-state.tsx @@ -3,28 +3,33 @@ import { Button } from './button' import { cn } from '@/shared/utils/cn' type Props = { - title?: string - message: string - onRetry?: () => void - className?: string + title?: string + message: string + onRetry?: () => void + className?: string } export function ErrorState({ title, message, onRetry, className }: Props) { - return ( -
    -
    - -
    -
    - {title &&

    {title}

    } -

    {message}

    -
    - {onRetry && ( - - )} -
    - ) + return ( +
    +
    + +
    +
    + {title &&

    {title}

    } +

    {message}

    +
    + {onRetry && ( + + )} +
    + ) } diff --git a/apps/desktop/src/shared/ui/not-implemented.tsx b/apps/desktop/src/shared/ui/not-implemented.tsx index 9600b481..3b2ccba7 100644 --- a/apps/desktop/src/shared/ui/not-implemented.tsx +++ b/apps/desktop/src/shared/ui/not-implemented.tsx @@ -1,22 +1,22 @@ import { AlertTriangle } from 'lucide-react' type Props = { - feature: string - description?: string + feature: string + description?: string } export function NotImplemented({ feature, description }: Props) { - return ( -
    -
    - -
    -
    -

    {feature}

    -

    - {description || 'This feature is coming soon'} -

    -
    -
    - ) + return ( +
    +
    + +
    +
    +

    {feature}

    +

    + {description || 'This feature is coming soon'} +

    +
    +
    + ) } diff --git a/apps/desktop/src/shared/ui/number-input.tsx b/apps/desktop/src/shared/ui/number-input.tsx index 18c6d703..30f2292d 100644 --- a/apps/desktop/src/shared/ui/number-input.tsx +++ b/apps/desktop/src/shared/ui/number-input.tsx @@ -3,105 +3,105 @@ import { ChevronUp, ChevronDown } from 'lucide-react' import { cn } from '@/shared/utils/cn' type TProps = { - value?: string | number - onChange?: (e: React.ChangeEvent) => void - min?: number - max?: number - step?: number - className?: string - placeholder?: string - disabled?: boolean - title?: string + value?: string | number + onChange?: (e: React.ChangeEvent) => void + min?: number + max?: number + step?: number + className?: string + placeholder?: string + disabled?: boolean + title?: string } export function NumberInput({ - value, - onChange, - min, - max, - step = 1, - className, - placeholder, - disabled, - title, + value, + onChange, + min, + max, + step = 1, + className, + placeholder, + disabled, + title }: TProps) { - const inputRef = React.useRef(null) + const inputRef = React.useRef(null) - function handleIncrement() { - if (inputRef.current) { - inputRef.current.stepUp(step) - triggerChange() - } - } + function handleIncrement() { + if (inputRef.current) { + inputRef.current.stepUp(step) + triggerChange() + } + } - function handleDecrement() { - if (inputRef.current) { - inputRef.current.stepDown(step) - triggerChange() - } - } + function handleDecrement() { + if (inputRef.current) { + inputRef.current.stepDown(step) + triggerChange() + } + } - function triggerChange() { - if (!inputRef.current) return + function triggerChange() { + if (!inputRef.current) return - const input = inputRef.current - // Dispatch input event for native listeners - const nativeEvent = new Event('input', { bubbles: true }) - input.dispatchEvent(nativeEvent) + const input = inputRef.current + // Dispatch input event for native listeners + const nativeEvent = new Event('input', { bubbles: true }) + input.dispatchEvent(nativeEvent) - // Dispatch change event for React - if (onChange) { - const event = { - ...nativeEvent, - target: input, - currentTarget: input, - bubbles: true, - cancelable: false, - type: 'change', - } as unknown as React.ChangeEvent - onChange(event) - } - } + // Dispatch change event for React + if (onChange) { + const event = { + ...nativeEvent, + target: input, + currentTarget: input, + bubbles: true, + cancelable: false, + type: 'change' + } as unknown as React.ChangeEvent + onChange(event) + } + } - return ( -
    - -
    - - -
    -
    - ) + return ( +
    + +
    + + +
    +
    + ) } diff --git a/apps/desktop/src/shared/ui/skeleton.tsx b/apps/desktop/src/shared/ui/skeleton.tsx index 8b395cbf..059a07ba 100644 --- a/apps/desktop/src/shared/ui/skeleton.tsx +++ b/apps/desktop/src/shared/ui/skeleton.tsx @@ -1,71 +1,64 @@ import { cn } from '@/shared/utils/cn' type Props = { - className?: string - rows?: number - columns?: number + className?: string + rows?: number + columns?: number } export function Skeleton({ className }: { className?: string }) { - return ( -
    - ) + return
    } export function SkeletonText({ className }: { className?: string }) { - return + return } export function SkeletonCard({ className }: { className?: string }) { - return ( -
    - - - -
    - ) + return ( +
    + + + +
    + ) } export function SkeletonTable({ rows = 5, columns = 4, className }: Props) { - return ( -
    -
    - {Array.from({ length: columns }).map(function (_, i) { - return - })} -
    - {Array.from({ length: rows }).map(function (_, rowIndex) { - return ( -
    - {Array.from({ length: columns }).map(function (_, colIndex) { - return - })} -
    - ) - })} -
    - ) + return ( +
    +
    + {Array.from({ length: columns }).map(function (_, i) { + return + })} +
    + {Array.from({ length: rows }).map(function (_, rowIndex) { + return ( +
    + {Array.from({ length: columns }).map(function (_, colIndex) { + return + })} +
    + ) + })} +
    + ) } export function SkeletonList({ rows = 5, className }: { rows?: number; className?: string }) { - return ( -
    - {Array.from({ length: rows }).map(function (_, i) { - return ( -
    - -
    - - -
    -
    - ) - })} -
    - ) + return ( +
    + {Array.from({ length: rows }).map(function (_, i) { + return ( +
    + +
    + + +
    +
    + ) + })} +
    + ) } diff --git a/apps/desktop/src/shared/ui/table.tsx b/apps/desktop/src/shared/ui/table.tsx index dd353501..931123b6 100644 --- a/apps/desktop/src/shared/ui/table.tsx +++ b/apps/desktop/src/shared/ui/table.tsx @@ -3,91 +3,92 @@ import * as React from 'react' import { cn } from '@/shared/utils/cn' const Table = React.forwardRef>( - ({ className, ...props }, ref) => ( -
    - - - ) + ({ className, ...props }, ref) => ( +
    +
    + + ) ) Table.displayName = 'Table' const TableHeader = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )) TableHeader.displayName = 'TableHeader' const TableBody = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - + )) TableBody.displayName = 'TableBody' const TableFooter = React.forwardRef< - HTMLTableSectionElement, - React.HTMLAttributes + HTMLTableSectionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( - tr]:last:border-b-0', className)} - {...props} - /> + tr]:last:border-b-0', className)} + {...props} + /> )) TableFooter.displayName = 'TableFooter' const TableRow = React.forwardRef>( - ({ className, ...props }, ref) => ( - - ) + ({ className, ...props }, ref) => ( + + ) ) TableRow.displayName = 'TableRow' -const TableHead = React.forwardRef>( - ({ className, ...props }, ref) => ( -
    - ) -) +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( + +)) TableHead.displayName = 'TableHead' const TableCell = React.forwardRef< - HTMLTableCellElement, - React.TdHTMLAttributes + HTMLTableCellElement, + React.TdHTMLAttributes >(({ className, ...props }, ref) => ( - + )) TableCell.displayName = 'TableCell' const TableCaption = React.forwardRef< - HTMLTableCaptionElement, - React.HTMLAttributes + HTMLTableCaptionElement, + React.HTMLAttributes >(({ className, ...props }, ref) => ( -
    + )) TableCaption.displayName = 'TableCaption' diff --git a/apps/desktop/src/shared/utils/error-messages.ts b/apps/desktop/src/shared/utils/error-messages.ts index 7fe14098..d887bf5a 100644 --- a/apps/desktop/src/shared/utils/error-messages.ts +++ b/apps/desktop/src/shared/utils/error-messages.ts @@ -8,7 +8,11 @@ export function mapConnectionError(error: Error | string): string { return 'Connection refused. Make sure the database server is running and accessible.' } - if (msg.includes('authentication') || msg.includes('password') || msg.includes('access denied')) { + if ( + msg.includes('authentication') || + msg.includes('password') || + msg.includes('access denied') + ) { return 'Authentication failed. Please check your username and password.' } @@ -75,7 +79,7 @@ export function mapQueryError(error: Error | string): string { return 'Column not found. The specified column does not exist in this table.' } - if (msg.includes('no such table') || msg.includes('table') && msg.includes('not exist')) { + if (msg.includes('no such table') || (msg.includes('table') && msg.includes('not exist'))) { return 'Table not found. The specified table does not exist.' } diff --git a/apps/desktop/stats.html b/apps/desktop/stats.html index c71c2303..edffeedb 100644 --- a/apps/desktop/stats.html +++ b/apps/desktop/stats.html @@ -1,4949 +1,80861 @@ - - + - - - - - Rollup Visualizer - - - -
    - - - + + + + + Rollup Visualizer + + + +
    + + + - diff --git a/apps/desktop/tools/scripts/auto-lsp-showcase.js b/apps/desktop/tools/scripts/auto-lsp-showcase.js index 2ba29ee4..8db03d6b 100644 --- a/apps/desktop/tools/scripts/auto-lsp-showcase.js +++ b/apps/desktop/tools/scripts/auto-lsp-showcase.js @@ -1,393 +1,398 @@ -/** +/** * What is this? - * + * * This is a script that will automatically type out a complex Drizzle query with autocomplete suggestions. * It will then trigger the autocomplete suggestions and select them. - * + * * How to use it? - * + * * 1. Open dora, navigate to the Query runner * 2. Open the browser console (F12) * 3. Paste this script into the console and press Enter * 4. Watch the magic happen */ - -(function () { - function sleep(ms) { - return new Promise(function (r) { - setTimeout(r, ms) - }) - } - - function getEd() { - var g = globalThis - - if (g.editor && typeof g.editor.trigger === "function") return g.editor - if (g.monaco && g.monaco.editor) { - if (typeof g.monaco.editor.getEditors === "function") { - var list = g.monaco.editor.getEditors() - if (list && list[0]) return list[0] - } - if (typeof g.monaco.editor.getFocusedEditor === "function") { - var fe = g.monaco.editor.getFocusedEditor() - if (fe) return fe - } - } - - var el = document.querySelector(".monaco-editor") - if (el) { - var keys = Object.keys(el) - for (var i = 0; i < keys.length; i++) { - var k = keys[i] - var v = el[k] - if (v && typeof v.trigger === "function" && typeof v.getModel === "function") return v - } - } - - return null - } - - async function typeEd(ed, txt) { - ed.focus() - ed.trigger("kbd", "type", { text: txt }) - } - - async function acceptSug(ed) { - ed.focus() - ed.trigger("kbd", "acceptSelectedSuggestion", {}) - } - - async function triggerSuggest(ed) { - ed.focus() - ed.trigger("kbd", "editor.action.triggerSuggest", {}) - } - - async function selectNextSuggestion(ed) { - ed.focus() - ed.trigger("kbd", "selectNextSuggestion", {}) - } - - async function selectPrevSuggestion(ed) { - ed.focus() - ed.trigger("kbd", "selectPrevSuggestion", {}) - } - - async function run(config) { - config = config || {} - var baseDelay = config.delay || 800 - var pauseBetweenSteps = config.pauseBetweenSteps || 1200 - - var ed = getEd() - if (!ed) { - console.log("No Monaco editor instance found.") - console.log("If the page exposes it, set globalThis.editor = and run doraLSP.run() again.") - return - } - - console.log("Starting LSP Showcase Demo...") - console.log("Building complex Drizzle query with autocomplete...") - - ed.focus() - if (typeof ed.getModel === "function") { - var m = ed.getModel() - if (m && typeof m.setValue === "function") m.setValue("") - } - - await sleep(500) - - console.log("Step 1: Typing db. to trigger method suggestions...") - await typeEd(ed, "db.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - console.log("Step 2: Selecting 'select' method...") - await acceptSug(ed) - await sleep(baseDelay) - - console.log("Step 3: Opening select() with column selection...") - await typeEd(ed, "({") - await sleep(baseDelay) - - console.log("Step 4: Typing column aliases with table references...") - await typeEd(ed, "\n userId: users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - console.log("Step 5: Selecting 'id' column...") - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ",") - await sleep(300) - - console.log("Step 6: Adding userName alias...") - await typeEd(ed, "\n userName: users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await selectNextSuggestion(ed) - await sleep(400) - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ",") - await sleep(300) - - console.log("Step 7: Adding email with function wrapper...") - await typeEd(ed, "\n email: sql`LOWER(${users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await selectNextSuggestion(ed) - await sleep(400) - await selectNextSuggestion(ed) - await sleep(400) - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, "})`") - await sleep(300) - - console.log("Step 8: Adding order count subquery...") - await typeEd(ed, ",\n orderCount: sql`(SELECT COUNT(*) FROM orders WHERE orders.user_id = ${users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, "})`") - await sleep(300) - - console.log("Step 9: Closing select object...") - await typeEd(ed, "\n})") - await sleep(baseDelay) - - console.log("Step 10: Adding .from() clause...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "from(") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - console.log("Step 11: Selecting 'users' table...") - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ")") - await sleep(300) - - console.log("Step 12: Adding .leftJoin() with related table...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "leftJoin(") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await selectNextSuggestion(ed) - await sleep(400) - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ", eq(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ", orders.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await selectNextSuggestion(ed) - await sleep(400) - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, "))") - await sleep(300) - - console.log("Step 13: Adding .where() clause with complex condition...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "where(") - await sleep(baseDelay) - - await typeEd(ed, "and(\n gt(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "createdAt") - await sleep(baseDelay) - - await typeEd(ed, ", sql`NOW() - INTERVAL '30 days'`),\n isNotNull(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "email") - await sleep(baseDelay) - - await typeEd(ed, "),\n or(\n eq(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "status") - await sleep(baseDelay) - - await typeEd(ed, ", 'active'),\n eq(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "status") - await sleep(baseDelay) - - await typeEd(ed, ", 'pending')\n )\n))") - await sleep(300) - - console.log("Step 14: Adding .groupBy() clause...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "groupBy(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await acceptSug(ed) - await sleep(baseDelay) - - await typeEd(ed, ")") - await sleep(300) - - console.log("Step 15: Adding .orderBy() with desc...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "orderBy(desc(users.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "createdAt") - await sleep(baseDelay) - - await typeEd(ed, "))") - await sleep(300) - - console.log("Step 16: Adding .limit() and .offset()...") - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "limit(50)") - await sleep(baseDelay) - - await typeEd(ed, "\n.") - await sleep(baseDelay) - await triggerSuggest(ed) - await sleep(pauseBetweenSteps) - - await typeEd(ed, "offset(0);") - await sleep(baseDelay) - - console.log("") - console.log("LSP Showcase Demo Complete!") - console.log("Final query demonstrates:") - console.log(" - Complex column selection with aliases") - console.log(" - SQL template literals for raw expressions") - console.log(" - Subqueries with correlated references") - console.log(" - JOIN clauses with equality conditions") - console.log(" - Compound WHERE conditions (and, or, gt, isNotNull, eq)") - console.log(" - GROUP BY, ORDER BY with direction") - console.log(" - Pagination with LIMIT/OFFSET") - } - - async function runSimple() { - var ed = getEd() - if (!ed) { - console.log("No Monaco editor instance found.") - return - } - - console.log("Starting Simple LSP Demo...") - - ed.focus() - if (typeof ed.getModel === "function") { - var m = ed.getModel() - if (m && typeof m.setValue === "function") m.setValue("") - } - - await sleep(500) - - await typeEd(ed, "db.") - await sleep(1000) - await triggerSuggest(ed) - await sleep(1200) - - await acceptSug(ed) - await sleep(800) - - await typeEd(ed, ".from(") - await sleep(800) - await triggerSuggest(ed) - await sleep(1200) - - await acceptSug(ed) - await sleep(800) - - await typeEd(ed, ").where(eq(users.") - await sleep(800) - await triggerSuggest(ed) - await sleep(1200) - - await acceptSug(ed) - await sleep(800) - - await typeEd(ed, ", 1)).limit(100);") - - console.log("Simple LSP Demo Complete!") - } - - globalThis.doraLSP = { - run: run, - runSimple: runSimple, - getEditor: getEd - } - - console.log("LSP Showcase Script Loaded") - console.log("") - console.log("Commands:") - console.log(" doraLSP.run() - Run full complex query demo") - console.log(" doraLSP.run({delay: 500, pauseBetweenSteps: 800}) - Faster demo") - console.log(" doraLSP.runSimple() - Run simple SELECT demo") - console.log(" doraLSP.getEditor() - Get Monaco editor instance") +;(function () { + function sleep(ms) { + return new Promise(function (r) { + setTimeout(r, ms) + }) + } + + function getEd() { + var g = globalThis + + if (g.editor && typeof g.editor.trigger === 'function') return g.editor + if (g.monaco && g.monaco.editor) { + if (typeof g.monaco.editor.getEditors === 'function') { + var list = g.monaco.editor.getEditors() + if (list && list[0]) return list[0] + } + if (typeof g.monaco.editor.getFocusedEditor === 'function') { + var fe = g.monaco.editor.getFocusedEditor() + if (fe) return fe + } + } + + var el = document.querySelector('.monaco-editor') + if (el) { + var keys = Object.keys(el) + for (var i = 0; i < keys.length; i++) { + var k = keys[i] + var v = el[k] + if (v && typeof v.trigger === 'function' && typeof v.getModel === 'function') + return v + } + } + + return null + } + + async function typeEd(ed, txt) { + ed.focus() + ed.trigger('kbd', 'type', { text: txt }) + } + + async function acceptSug(ed) { + ed.focus() + ed.trigger('kbd', 'acceptSelectedSuggestion', {}) + } + + async function triggerSuggest(ed) { + ed.focus() + ed.trigger('kbd', 'editor.action.triggerSuggest', {}) + } + + async function selectNextSuggestion(ed) { + ed.focus() + ed.trigger('kbd', 'selectNextSuggestion', {}) + } + + async function selectPrevSuggestion(ed) { + ed.focus() + ed.trigger('kbd', 'selectPrevSuggestion', {}) + } + + async function run(config) { + config = config || {} + var baseDelay = config.delay || 800 + var pauseBetweenSteps = config.pauseBetweenSteps || 1200 + + var ed = getEd() + if (!ed) { + console.log('No Monaco editor instance found.') + console.log( + 'If the page exposes it, set globalThis.editor = and run doraLSP.run() again.' + ) + return + } + + console.log('Starting LSP Showcase Demo...') + console.log('Building complex Drizzle query with autocomplete...') + + ed.focus() + if (typeof ed.getModel === 'function') { + var m = ed.getModel() + if (m && typeof m.setValue === 'function') m.setValue('') + } + + await sleep(500) + + console.log('Step 1: Typing db. to trigger method suggestions...') + await typeEd(ed, 'db.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + console.log("Step 2: Selecting 'select' method...") + await acceptSug(ed) + await sleep(baseDelay) + + console.log('Step 3: Opening select() with column selection...') + await typeEd(ed, '({') + await sleep(baseDelay) + + console.log('Step 4: Typing column aliases with table references...') + await typeEd(ed, '\n userId: users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + console.log("Step 5: Selecting 'id' column...") + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ',') + await sleep(300) + + console.log('Step 6: Adding userName alias...') + await typeEd(ed, '\n userName: users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await selectNextSuggestion(ed) + await sleep(400) + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ',') + await sleep(300) + + console.log('Step 7: Adding email with function wrapper...') + await typeEd(ed, '\n email: sql`LOWER(${users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await selectNextSuggestion(ed) + await sleep(400) + await selectNextSuggestion(ed) + await sleep(400) + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, '})`') + await sleep(300) + + console.log('Step 8: Adding order count subquery...') + await typeEd( + ed, + ',\n orderCount: sql`(SELECT COUNT(*) FROM orders WHERE orders.user_id = ${users.' + ) + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, '})`') + await sleep(300) + + console.log('Step 9: Closing select object...') + await typeEd(ed, '\n})') + await sleep(baseDelay) + + console.log('Step 10: Adding .from() clause...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'from(') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + console.log("Step 11: Selecting 'users' table...") + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ')') + await sleep(300) + + console.log('Step 12: Adding .leftJoin() with related table...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'leftJoin(') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await selectNextSuggestion(ed) + await sleep(400) + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ', eq(users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ', orders.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await selectNextSuggestion(ed) + await sleep(400) + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, '))') + await sleep(300) + + console.log('Step 13: Adding .where() clause with complex condition...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'where(') + await sleep(baseDelay) + + await typeEd(ed, 'and(\n gt(users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'createdAt') + await sleep(baseDelay) + + await typeEd(ed, ", sql`NOW() - INTERVAL '30 days'`),\n isNotNull(users.") + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'email') + await sleep(baseDelay) + + await typeEd(ed, '),\n or(\n eq(users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'status') + await sleep(baseDelay) + + await typeEd(ed, ", 'active'),\n eq(users.") + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'status') + await sleep(baseDelay) + + await typeEd(ed, ", 'pending')\n )\n))") + await sleep(300) + + console.log('Step 14: Adding .groupBy() clause...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'groupBy(users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await acceptSug(ed) + await sleep(baseDelay) + + await typeEd(ed, ')') + await sleep(300) + + console.log('Step 15: Adding .orderBy() with desc...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'orderBy(desc(users.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'createdAt') + await sleep(baseDelay) + + await typeEd(ed, '))') + await sleep(300) + + console.log('Step 16: Adding .limit() and .offset()...') + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'limit(50)') + await sleep(baseDelay) + + await typeEd(ed, '\n.') + await sleep(baseDelay) + await triggerSuggest(ed) + await sleep(pauseBetweenSteps) + + await typeEd(ed, 'offset(0);') + await sleep(baseDelay) + + console.log('') + console.log('LSP Showcase Demo Complete!') + console.log('Final query demonstrates:') + console.log(' - Complex column selection with aliases') + console.log(' - SQL template literals for raw expressions') + console.log(' - Subqueries with correlated references') + console.log(' - JOIN clauses with equality conditions') + console.log(' - Compound WHERE conditions (and, or, gt, isNotNull, eq)') + console.log(' - GROUP BY, ORDER BY with direction') + console.log(' - Pagination with LIMIT/OFFSET') + } + + async function runSimple() { + var ed = getEd() + if (!ed) { + console.log('No Monaco editor instance found.') + return + } + + console.log('Starting Simple LSP Demo...') + + ed.focus() + if (typeof ed.getModel === 'function') { + var m = ed.getModel() + if (m && typeof m.setValue === 'function') m.setValue('') + } + + await sleep(500) + + await typeEd(ed, 'db.') + await sleep(1000) + await triggerSuggest(ed) + await sleep(1200) + + await acceptSug(ed) + await sleep(800) + + await typeEd(ed, '.from(') + await sleep(800) + await triggerSuggest(ed) + await sleep(1200) + + await acceptSug(ed) + await sleep(800) + + await typeEd(ed, ').where(eq(users.') + await sleep(800) + await triggerSuggest(ed) + await sleep(1200) + + await acceptSug(ed) + await sleep(800) + + await typeEd(ed, ', 1)).limit(100);') + + console.log('Simple LSP Demo Complete!') + } + + globalThis.doraLSP = { + run: run, + runSimple: runSimple, + getEditor: getEd + } + + console.log('LSP Showcase Script Loaded') + console.log('') + console.log('Commands:') + console.log(' doraLSP.run() - Run full complex query demo') + console.log(' doraLSP.run({delay: 500, pauseBetweenSteps: 800}) - Faster demo') + console.log(' doraLSP.runSimple() - Run simple SELECT demo') + console.log(' doraLSP.getEditor() - Get Monaco editor instance') })() diff --git a/apps/desktop/vite.config.ts b/apps/desktop/vite.config.ts index 71d65b75..8948c5bf 100644 --- a/apps/desktop/vite.config.ts +++ b/apps/desktop/vite.config.ts @@ -24,17 +24,32 @@ export default defineConfig({ 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('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('@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'; + return 'vendor-editor' } - if (id.includes('date-fns') || id.includes('zustand') || id.includes('@tanstack/react-query')) { - return 'vendor-utils'; + if ( + id.includes('date-fns') || + id.includes('zustand') || + id.includes('@tanstack/react-query') + ) { + return 'vendor-utils' } } } diff --git a/app_demo.gif b/assets/app_demo.gif similarity index 100% rename from app_demo.gif rename to assets/app_demo.gif diff --git a/app_demo.webp b/assets/app_demo.webp similarity index 100% rename from app_demo.webp rename to assets/app_demo.webp diff --git a/docker.png b/assets/docker.png similarity index 100% rename from docker.png rename to assets/docker.png diff --git a/dora-backgroundless.png b/assets/dora-backgroundless.png similarity index 100% rename from dora-backgroundless.png rename to assets/dora-backgroundless.png diff --git a/home.png b/assets/home.png similarity index 100% rename from home.png rename to assets/home.png diff --git a/majin-render.png b/assets/majin-render.png similarity index 100% rename from majin-render.png rename to assets/majin-render.png diff --git a/showcase.gif b/assets/showcase.gif similarity index 100% rename from showcase.gif rename to assets/showcase.gif diff --git a/convert.log b/convert.log deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md index 53ad438f..088832b7 100644 --- a/docs/RELEASE_NOTES.md +++ b/docs/RELEASE_NOTES.md @@ -1,3 +1,48 @@ +# Version 0.0.102 + +**Date:** 2026-04-05 +**Tag:** `v0.0.102` +**Range:** `v0.0.101..feature/package-distribution` + +## Why this release exists + +`v0.0.101` produced the main release assets, but the Snap workflow fixes landed immediately afterward on branch head. `v0.0.102` is the clean follow-up tag that includes those fixes. + +## Changes + +- Fixed Snap GitHub Actions builds to run Snapcraft correctly in destructive mode. +- Replaced the invalid Tauri `--bundles none` step with a direct Rust release build for Snap packaging. +- Keeps the packaging automation, release guidance, and VM lab tooling on the release tag itself. + +## Expected assets + +- Linux: `.deb`, `.AppImage`, `.rpm` +- macOS: `.dmg` +- Windows: `.exe`, `.msi` + +# Version 0.0.101 + +**Date:** 2026-04-05 +**Tag:** `v0.0.101` +**Range:** `v0.1.0..feature/package-distribution` + +## Why this release exists + +This release bundles packaging automation work, VM-based packaging workflows, release tooling, and the current round of desktop/docs/test iteration into `v0.0.101`. + +## Changes + +- Added release checksum generation for Windows and Linux assets. +- Added repo-native helpers for Winget, AUR, Snap, release guidance, and VM management. +- Updated the in-app changelog and release-facing documentation for the new release milestone. +- Carried forward the current desktop and documentation iteration work already present on this branch. + +## Expected assets + +- Linux: `.deb`, `.AppImage`, `.rpm` +- macOS: `.dmg` +- Windows: `.exe`, `.msi` + # Version 0.0.95 **Date:** 2026-02-09 diff --git a/docs/app-audit-2026-02-20.md b/docs/app-audit-2026-02-20.md index e0cbf17c..c741e63d 100644 --- a/docs/app-audit-2026-02-20.md +++ b/docs/app-audit-2026-02-20.md @@ -14,6 +14,7 @@ Audit target: desktop application under `apps/desktop` (React + Tauri), plus top ## Findings 1. **High: Frontend type safety gate is currently broken.** + - `apps/desktop/src/components/ui/sonner.tsx:6` uses `ToasterProps` without defining/importing it. - `apps/desktop/src/core/data-generation/schema-analyzer.ts:11` uses `faker.internet.userName`, which is invalid for current faker typings. - `apps/desktop/src/features/database-studio/database-studio.tsx:1490` passes props (`pagination`, `onPaginationChange`, etc.) that are not in `StudioToolbar` props. @@ -21,11 +22,13 @@ Audit target: desktop application under `apps/desktop` (React + Tauri), plus top - Impact: strict TypeScript build/release gates are blocked until corrected. 2. **High: Runtime analytics is enabled while docs previously implied fully offline/private behavior.** + - `apps/desktop/src/App.tsx:14` imports `@vercel/analytics/react`. - `apps/desktop/src/App.tsx:32` renders `` globally. - Impact: privacy/telemetry messaging must stay aligned with actual runtime behavior. 3. **Medium: Several user-facing features are intentionally incomplete but still appear in the product surface.** + - MySQL entry is present but disabled: `apps/desktop/src/features/connections/components/connection-dialog/database-type-selector.tsx:20`. - SSH tunnel support is now end to end for Postgres desktop connections, including save/update and tunnel startup. The browser/Vercel mock path still omits Tauri tunneling by design. - SQL Console filter action currently shows ā€œComing Soonā€: `apps/desktop/src/features/sql-console/sql-console.tsx:663`. @@ -33,10 +36,12 @@ Audit target: desktop application under `apps/desktop` (React + Tauri), plus top - Impact: capability discoverability and docs/UI consistency drift. 4. **Medium: Bundle size is large for core client chunks.** + - Build output reports multi-megabyte bundles (`index` and editor/vendor chunks) and Vite chunk warnings. - Impact: startup/load performance risk and larger distribution artifacts. 5. **Low: Rust backend compiles cleanly but emits warning debt.** + - Warnings include lifetime syntax in vendored `libsql-sys` and dead-code/unfulfilled lint expectations in app modules. - Impact: not release-blocking, but adds maintenance noise. diff --git a/docs/distribution/aur.md b/docs/distribution/aur.md new file mode 100644 index 00000000..45e9c3af --- /dev/null +++ b/docs/distribution/aur.md @@ -0,0 +1,44 @@ +# AUR Distribution Guide + +`AUR` support for Dora is set up as a binary package named `dora-bin`. + +The package installs the published AppImage from GitHub Releases into `/opt/dora` and exposes a `dora` wrapper in `/usr/bin`. The wrapper uses `--appimage-extract-and-run`, which avoids requiring users to have FUSE configured. + +## Generate package files + +After a tagged Linux release exists: + +```bash +bun run release:aur -- \ + --version=0.1.0 \ + --checksums-file=apps/desktop/src-tauri/target/release/bundle/checksums-linux.txt \ + --appimage-file=Dora_0.1.0_amd64.AppImage +``` + +This writes: + +- `packaging/aur/PKGBUILD` +- `packaging/aur/.SRCINFO` + +## Validate locally + +On an Arch-based machine: + +```bash +cd packaging/aur +makepkg -si +``` + +That should install `dora-bin` and provide the `dora` command. + +## Publish to AUR + +1. Create or use the `dora-bin` package in AUR. +2. Clone the AUR git repo for `dora-bin`. +3. Copy in the generated `PKGBUILD` and `.SRCINFO`. +4. Commit and push to the AUR remote. + +## Notes + +- The remaining manual step is publishing the generated files to the AUR git repository. +- If Dora later ships a tarball instead of only an AppImage, the package can be tightened further. diff --git a/docs/distribution/one-machine-playbook.md b/docs/distribution/one-machine-playbook.md new file mode 100644 index 00000000..1ef88193 --- /dev/null +++ b/docs/distribution/one-machine-playbook.md @@ -0,0 +1,171 @@ +# One-Machine Packaging Playbook + +This is the shortest path if you only have one main machine and need to use Docker or VMs for the ecosystem-specific steps. + +## VM bootstrap on Ubuntu + +If you want the repo to manage the packaging VMs for you, start here: + +```bash +bash tools/scripts/vm-lab.sh +``` + +Or directly: + +```bash +bash tools/scripts/vm-lab.sh setup-host +bash tools/scripts/vm-lab.sh create ubuntu +bash tools/scripts/vm-lab.sh create arch +``` + +Details live in `docs/distribution/vm-lab.md`. + +## First milestone: create a real tagged release + +Package-manager publishing should start only after a real tagged release exists +and its assets are visible on the GitHub release page. + +From the main repo: + +```bash +git tag v0.1.0 +git push origin v0.1.0 +``` + +Then wait for the `Release` GitHub Actions workflow to finish. That workflow now uploads: + +- release installers +- `checksums-linux.txt` +- `checksums-windows.txt` + +Verify the release page first. If the release assets are missing, stop there and fix the release before touching Winget, AUR, or Snap. + +## Winget from a Windows VM + +Use a Windows 11 VM because `wingetcreate` is Windows-native. You need this VM +for the first public package submission only. + +### In the VM + +1. Install GitHub CLI and sign in if needed. +2. Install `wingetcreate`: + +```powershell +winget install wingetcreate +``` + +3. Create the first package submission: + +```powershell +wingetcreate new "https://github.com/remcostoeten/dora/releases/download/v0.1.0/Dora_0.1.0_x64_en-US.msi" +``` + +4. Review the detected metadata carefully. +5. Let `wingetcreate` create the PR to `microsoft/winget-pkgs`. + +### On later releases + +Use: + +```powershell +wingetcreate update RemcoStoeten.Dora --urls "https://github.com/remcostoeten/dora/releases/download/v0.1.1/Dora_0.1.1_x64_en-US.msi" +``` + +After the package exists in `microsoft/winget-pkgs`, you can stop doing that by +hand. Set `WINGET_CREATE_GITHUB_TOKEN` in GitHub Actions, add the repository +variable `WINGET_PACKAGE_READY=true`, and let `.github/workflows/winget.yml` +submit update PRs automatically from published releases. + +## AUR from Docker on your main machine + +You do not need a full Arch VM just to validate the package. + +### Generate the package files + +```bash +bun run release:aur -- \ + --version=0.1.0 \ + --checksums-file=apps/desktop/src-tauri/target/release/bundle/checksums-linux.txt \ + --appimage-file=Dora_0.1.0_amd64.AppImage +``` + +### Validate in Docker + +```bash +bash tools/scripts/test-aur-docker.sh +``` + +That spins up `archlinux:latest`, installs `base-devel`, and runs `makepkg` against `packaging/aur`. + +### Publish to the real AUR repo + +1. Create an AUR account at `aur.archlinux.org`. +2. Add your SSH key there. +3. Clone the AUR repo: + +```bash +git clone ssh://aur@aur.archlinux.org/dora-bin.git ~/code/dora-bin-aur +``` + +4. Sync the generated files: + +```bash +bash tools/scripts/sync-aur-repo.sh ~/code/dora-bin-aur +``` + +5. Commit and push: + +```bash +cd ~/code/dora-bin-aur +git add PKGBUILD .SRCINFO +git commit -m "Update dora-bin to 0.1.0" +git push +``` + +## Snap from your main machine or GitHub Actions + +Snap does not need a separate repo. + +### Local build path + +If your machine is Ubuntu or another Linux system with Snap available: + +```bash +sudo snap install snapcraft --classic +sudo /snap/bin/snapcraft pack --destructive-mode +``` + +### CI build path + +The repo now has: + +- `.github/workflows/snap.yml` +- `snap/snapcraft.yaml` + +When a GitHub release is published, the workflow builds the `.snap`, uploads it +to the release, and publishes it to the Snap Store if the store credential +secret exists. Manual dispatch still works for artifact-only or test runs. + +### Store publish path + +1. Create or log into your Snapcraft account. +2. Register the `dora` snap name. +3. Export store credentials: + +```bash +snapcraft export-login --snaps=dora \ + --acls package_access,package_push,package_update,package_release \ + exported.txt +``` + +4. Add the contents of `exported.txt` as the GitHub Actions secret + `SNAPCRAFT_STORE_CREDENTIALS`. +5. Publish a GitHub release and let `.github/workflows/snap.yml` handle the + build and store upload. + +## Recommended order + +1. Make the tagged GitHub release actually publish assets. +2. Submit Winget from a Windows VM. +3. Validate and publish AUR from Docker plus your normal host shell. +4. Register the Snap name and add `SNAPCRAFT_STORE_CREDENTIALS`. diff --git a/docs/distribution/release-guide.md b/docs/distribution/release-guide.md new file mode 100644 index 00000000..23bbd3d3 --- /dev/null +++ b/docs/distribution/release-guide.md @@ -0,0 +1,19 @@ +# Interactive Release Guide + +Run this from your normal shell, including `fish`: + +```bash +bash tools/scripts/release-guide.sh +``` + +The script gives you an interactive menu that: + +- checks branch, tag, versions, dirty worktree, and GitHub auth +- generates a release notes draft from `CHANGELOG.md` +- creates the local tag when the tree is clean +- pushes the tag +- creates the GitHub release with the generated notes +- prints the next AUR command and the one-time bootstrap steps for Winget and + Snap if those channels are not configured yet + +It intentionally refuses to create a tag while the worktree is dirty. diff --git a/docs/distribution/snap.md b/docs/distribution/snap.md new file mode 100644 index 00000000..dbf3a011 --- /dev/null +++ b/docs/distribution/snap.md @@ -0,0 +1,57 @@ +# Snap Distribution Guide + +Snap support is fully wired in-repo. The GitHub Actions workflow now builds +the snap, uploads it as a workflow artifact, uploads it to the GitHub release, +and publishes it to the Snap Store when store credentials are configured. + +## What is in the repo + +- `snap/snapcraft.yaml` +- `snap/gui/dora.desktop` +- `snap/local/launch` +- `.github/workflows/snap.yml` + +## Build locally + +On Ubuntu with Snapcraft installed: + +```bash +sudo snap install snapcraft --classic +sudo /snap/bin/snapcraft pack --destructive-mode +``` + +That should produce a `.snap` artifact in the repository root. + +The snap version is set during the build from the release tag when CI runs on a +published release. Local builds fall back to the version in `package.json`. + +## CI build and publish + +The GitHub Actions workflow at `.github/workflows/snap.yml` runs in two modes: + +- On `release.published`, it builds the snap, uploads the `.snap` file to the + GitHub release, and publishes it to the Snap Store if + `SNAPCRAFT_STORE_CREDENTIALS` exists. +- On manual dispatch, it builds the snap as an artifact. You can optionally + provide an existing release tag and turn on store publishing. + +## Required one-time setup + +You must complete the Snapcraft account setup once before GitHub Actions can +publish automatically. + +1. Create or log into your Snapcraft account. +2. Register the `dora` snap name. +3. Export store credentials with the required ACLs: + +```bash +snapcraft export-login --snaps=dora \ + --acls package_access,package_push,package_update,package_release \ + exported.txt +``` + +4. Save the contents of `exported.txt` as the GitHub Actions secret + `SNAPCRAFT_STORE_CREDENTIALS`. + +After that, every published GitHub release can publish the snap to the chosen +channel without additional manual steps. diff --git a/docs/distribution/vm-lab.md b/docs/distribution/vm-lab.md new file mode 100644 index 00000000..b71c548b --- /dev/null +++ b/docs/distribution/vm-lab.md @@ -0,0 +1,44 @@ +# VM Lab + +Use the VM lab script on an Ubuntu LTS host to bootstrap and manage the guest OSes needed for package-manager work. + +Run it with: + +```bash +bash tools/scripts/vm-lab.sh +``` + +Or call subcommands directly: + +```bash +bash tools/scripts/vm-lab.sh setup-host +bash tools/scripts/vm-lab.sh create ubuntu +bash tools/scripts/vm-lab.sh create arch +bash tools/scripts/vm-lab.sh create windows --iso ~/Downloads/Win11.iso +``` + +What it covers: + +- installs KVM/libvirt/virt-install/cloud image tooling on Ubuntu +- downloads official Ubuntu and Arch cloud images +- creates Linux VMs with cloud-init, generated credentials, and optional SSH key injection +- scaffolds a Windows 11 VM from a local ISO +- starts, stops, opens, deletes, and SSHes into managed VMs + +Managed files live under `.cache/dora-vm-lab/`. + +Useful commands: + +```bash +bash tools/scripts/vm-lab.sh list +bash tools/scripts/vm-lab.sh status dora-ubuntu-noble +bash tools/scripts/vm-lab.sh ip dora-archlinux +bash tools/scripts/vm-lab.sh creds dora-archlinux +bash tools/scripts/vm-lab.sh ssh dora-archlinux +``` + +Notes: + +- `setup-host` may add your user to the `libvirt` and `kvm` groups. If so, log out and back in once. +- Windows creation is intentionally ISO-based rather than auto-download because Microsoft download URLs and licensing flows are not stable enough to hardcode. +- Snap does not require a dedicated VM if your Ubuntu host can run `snapcraft`. diff --git a/docs/distribution/winget.md b/docs/distribution/winget.md new file mode 100644 index 00000000..030d2533 --- /dev/null +++ b/docs/distribution/winget.md @@ -0,0 +1,114 @@ +# Winget Distribution Guide + +Winget is now partly automated in-repo. Dora can generate versioned manifests on +every published GitHub release, attach those manifests to the release, and +submit update PRs with `wingetcreate` after the package has been bootstrapped in +`microsoft/winget-pkgs`. + +## Before releasing + +1. **Pick the canonical installer** – prefer the `.msi` bundle because Winget handles it cleanly. Keep the `Dora_${version}_x64_en-US.msi` naming pattern stable so a manifest can point to a predictable URL. +2. **Build and upload assets** – the GitHub release must include: + - `Dora_${version}_x64_en-US.msi` (or the defined installer) + - `Dora_${version}_x64-setup.exe` (if we keep the NSIS installer for fallback) + - `checksums.txt` that lists SHA256 hashes for every Windows artifact +3. **Publish release** – confirm that `docs/distribution/winget.md` remains aligned with the release tag (`vX.Y.Z`), because Winget manifests need the exact version string. + +## First submission + +The first public submission is still a one-time manual step. Microsoft documents +`wingetcreate new` as the starting point for creating the package entry in +`microsoft/winget-pkgs`. + +```powershell +winget install wingetcreate +wingetcreate new "https://github.com/remcostoeten/dora/releases/download/v0.1.0/Dora_0.1.0_x64_en-US.msi" +``` + +During the prompts: + +1. Confirm the detected package metadata. +2. Keep the package identifier stable. +3. Let `wingetcreate` submit the PR to `microsoft/winget-pkgs`. + +After that PR is merged, Dora's `winget.yml` workflow can handle later updates. + +## Repo-native manifest generation + +The repo contains a manifest generator and a dedicated workflow: + +- `tools/scripts/generate-winget-manifest.ts` +- `.github/workflows/winget.yml` + +On every published GitHub release, the workflow: + +1. Downloads `checksums-windows.txt` from the release. +2. Generates the three Winget manifest files. +3. Uploads a `winget-manifests-.tar.gz` artifact. +4. Uploads that manifest archive to the GitHub release. +5. Optionally runs `wingetcreate update --submit` when automation is enabled. + +You can still generate the manifests locally: + +1. Build and publish the tagged Windows release. +2. Generate the Windows checksums file from the release bundle: + +```bash +bun run release:checksums -- \ + --input-dir=apps/desktop/src-tauri/target/release/bundle \ + --output=apps/desktop/src-tauri/target/release/bundle/checksums-windows.txt \ + --extensions=.msi,.exe +``` + +3. Generate the three Winget manifest files: + +```bash +bun run release:winget -- \ + --version=0.1.0 \ + --installer-url=https://github.com/remcostoeten/dora/releases/download/v0.1.0/Dora_0.1.0_x64_en-US.msi \ + --checksums-file=apps/desktop/src-tauri/target/release/bundle/checksums-windows.txt \ + --installer-file=Dora_0.1.0_x64_en-US.msi +``` + +4. The repo writes: + - `packaging/winget/manifests//RemcoStoeten.Dora.yaml` + - `packaging/winget/manifests//RemcoStoeten.Dora.installer.yaml` + - `packaging/winget/manifests//RemcoStoeten.Dora.locale.en-US.yaml` +5. Validate locally with `winget validate --manifest ` or `winget install --manifest `. +6. Either copy the generated files into a `winget-pkgs` fork manually, or use + them as the source of truth when scripting `wingetcreate update`. + +## Automated update setup + +After the first package PR has merged, add the following repository settings: + +1. Add the GitHub Actions secret `WINGET_CREATE_GITHUB_TOKEN`. +2. Use a classic GitHub personal access token with the `public_repo` scope. +3. Add the repository variable `WINGET_PACKAGE_READY=true`. + +With that in place, each published GitHub release can submit a Winget update PR +automatically. Manual dispatch also supports a forced update submission for an +existing release tag. + +## Testing locally + +After the manifest is merged: + +1. `winget install Dora` – confirm the install succeeds and creates Start menu shortcuts. +2. `winget upgrade Dora` – test against a previous version to make sure upgrades are smooth. +3. `winget uninstall Dora` – ensure Dora is fully removed, including desktop icon and registry entries. + +## Rolling out the manifest + +1. Submit the PR to `microsoft/winget-pkgs`; `wingetcreate` can do this for you, or you can open the PR manually with the generated manifests. +2. Once the PR is merged, Winget users can run `winget install Dora` immediately. Track the manifest commit so we can update it each release. +3. Document the commands in our own README so users know how to install and keep the app updated. + +## Automating in CI + +- `release.yml` publishes `checksums-windows.txt` on every tagged Windows release. +- `winget.yml` consumes that checksum file and creates deterministic manifests. +- `winget.yml` can also submit update PRs through `wingetcreate` once the + package already exists in `microsoft/winget-pkgs`. + +The only remaining manual boundary is the very first public package submission. diff --git a/docs/docker-manager-improvements.md b/docs/docker-manager-improvements.md index 1d6c2370..8ceae130 100644 --- a/docs/docker-manager-improvements.md +++ b/docs/docker-manager-improvements.md @@ -8,31 +8,37 @@ Each item includes the target file(s), what to change, and acceptance criteria. ## UX/UI ### 1. Resizable details panel + - **Files:** `container-details-panel.tsx`, `docker-view.tsx` - **What:** Replace the fixed `w-80` panel with a resizable split pane (drag handle between list and details). Persist width to Zustand store. - **AC:** User can drag the divider. Min width 280px, max 50% of viewport. Width persists across sessions. ### 2. Empty state for search vs no containers + - **Files:** `container-list.tsx` - **What:** Accept a `searchQuery` prop. When `containers.length === 0 && searchQuery`, render "No containers match `{query}`" with a clear-search button. Keep existing empty state for genuinely zero containers. - **AC:** Searching a term that matches nothing shows a distinct message with a button to reset the search. ### 3. Add "created" status filter chip + - **Files:** `docker-view.tsx` - **What:** Add `'created'` to the status filter badge array at line 275. Currently only renders `all | running | stopped`. - **AC:** Four filter chips visible: All, Running, Stopped, Created. ### 4. Toggleable sort direction + - **Files:** `docker-view.tsx` - **What:** Add an ascending/descending toggle button next to the sort dropdown. Store direction in state. - **AC:** Clicking the toggle flips sort order. Arrow icon reflects current direction. ### 5. Make seed drop zone clickable + - **Files:** `seed-view.tsx` - **What:** Attach `onClick={() => fileInputRef.current?.click()}` to the outer drop zone `
    `, not just the button. The "or click to browse" text currently lies. - **AC:** Clicking anywhere in the dashed drop zone opens the file picker. ### 6. Keyboard navigation for container list + - **Files:** `container-list.tsx`, `container-card.tsx` - **What:** Add ArrowUp/ArrowDown key handlers on the list wrapper. Maintain a focused index. When a card is focused and user presses arrow keys, move focus to the adjacent card. - **AC:** User can navigate the container list with arrow keys without tabbing through every card. @@ -42,37 +48,43 @@ Each item includes the target file(s), what to change, and acceptance criteria. ## Accessibility ### 7. `aria-label` on all icon-only buttons + - **Files:** `container-card.tsx`, `connection-details.tsx`, `create-container-dialog.tsx`, `compose-export-dialog.tsx` - **What:** Every `